mirror of
https://github.com/sasjs/server.git
synced 2025-12-14 04:34:35 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfc5ac6a4f | ||
|
|
6376173de0 | ||
|
|
3130fbeff0 | ||
|
|
01e9a1d9e9 | ||
|
|
2119e9de9a | ||
|
|
87dbab98f6 | ||
|
|
1bf122a0a2 | ||
|
|
5d5d6ce326 | ||
|
|
620eddb713 | ||
|
|
3c92034da3 | ||
|
|
f6dc74f16b | ||
|
|
8c48d00d21 | ||
|
|
48ff8d73d4 | ||
| eb397b15c2 | |||
| eb569c7b82 | |||
| 99a1107364 | |||
| 91d29cb127 |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,3 +1,33 @@
|
|||||||
|
# [0.8.0](https://github.com/sasjs/server/compare/v0.7.3...v0.8.0) (2022-06-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **certs:** ENV variables updated and set CA Root for HTTPS server ([2119e9d](https://github.com/sasjs/server/commit/2119e9de9ab1e5ce1222658f554ac74f4f35cf4d))
|
||||||
|
|
||||||
|
## [0.7.3](https://github.com/sasjs/server/compare/v0.7.2...v0.7.3) (2022-06-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* path descriptions and defaults ([5d5d6ce](https://github.com/sasjs/server/commit/5d5d6ce3265a43af2e22bcd38cda54fafaf7b3ef))
|
||||||
|
|
||||||
|
## [0.7.2](https://github.com/sasjs/server/compare/v0.7.1...v0.7.2) (2022-06-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* removing UTF-8 options from commandline. There appears to be no reliable way to enforce ([f6dc74f](https://github.com/sasjs/server/commit/f6dc74f16bddafa1de9c83c2f27671a241abdad4))
|
||||||
|
|
||||||
|
## [0.7.1](https://github.com/sasjs/server/compare/v0.7.0...v0.7.1) (2022-06-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* default runtime should be sas ([91d29cb](https://github.com/sasjs/server/commit/91d29cb1272c28afbceaf39d1e0a87e17fbfdcd6))
|
||||||
|
* **Studio:** default selection of runtime fixed ([eb569c7](https://github.com/sasjs/server/commit/eb569c7b827c872ed2c4bc114559b97d87fd2aa0))
|
||||||
|
* webout path fixed in code.js when running on windows ([99a1107](https://github.com/sasjs/server/commit/99a110736448f66f99a512396b268fc31a3feef0))
|
||||||
|
|
||||||
# [0.7.0](https://github.com/sasjs/server/compare/v0.6.1...v0.7.0) (2022-06-19)
|
# [0.7.0](https://github.com/sasjs/server/compare/v0.6.1...v0.7.0) (2022-06-19)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -99,9 +99,10 @@ SASV9_OPTIONS= -NOXCMD
|
|||||||
## Additional Web Server Options
|
## Additional Web Server Options
|
||||||
#
|
#
|
||||||
|
|
||||||
# ENV variables required for PROTOCOL: `https`
|
# ENV variables for PROTOCOL: `https`
|
||||||
PRIVATE_KEY=privkey.pem
|
PRIVATE_KEY=privkey.pem (required)
|
||||||
FULL_CHAIN=fullchain.pem
|
CERT_CHAIN=certificate.pem (required)
|
||||||
|
CA_ROOT=fullchain.pem (optional)
|
||||||
|
|
||||||
# ENV variables required for MODE: `server`
|
# ENV variables required for MODE: `server`
|
||||||
ACCESS_TOKEN_SECRET=<secret>
|
ACCESS_TOKEN_SECRET=<secret>
|
||||||
@@ -140,10 +141,10 @@ HELMET_CSP_CONFIG_PATH=./csp.config.json
|
|||||||
LOG_FORMAT_MORGAN=
|
LOG_FORMAT_MORGAN=
|
||||||
|
|
||||||
# A comma separated string that defines the available runTimes.
|
# A comma separated string that defines the available runTimes.
|
||||||
# Priority is given to the runtime that cSAS_PATHomes first in string.
|
# Priority is given to the runtime that comes first in the string.
|
||||||
# Possible options at the moment are sas and js
|
# Possible options at the moment are sas and js
|
||||||
|
|
||||||
# options: [sas,js|js,sas|sas|js] default:sas,js
|
# options: [sas,js|js,sas|sas|js] default:sas
|
||||||
RUN_TIMES=
|
RUN_TIMES=
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ WHITELIST=<space separated urls, each starting with protocol `http` or `https`>
|
|||||||
|
|
||||||
PROTOCOL=[http|https] default considered as http
|
PROTOCOL=[http|https] default considered as http
|
||||||
PRIVATE_KEY=privkey.pem
|
PRIVATE_KEY=privkey.pem
|
||||||
FULL_CHAIN=fullchain.pem
|
CERT_CHAIN=certificate.pem
|
||||||
|
CA_ROOT=fullchain.pem
|
||||||
|
|
||||||
PORT=[5000] default value is 5000
|
PORT=[5000] default value is 5000
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ AUTH_CODE_SECRET=<secret>
|
|||||||
SESSION_SECRET=<secret>
|
SESSION_SECRET=<secret>
|
||||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||||
|
|
||||||
RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas,js
|
RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas
|
||||||
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
|
||||||
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
NODE_PATH=~/.nvm/versions/node/v16.14.0/bin/node
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ components:
|
|||||||
- sas
|
- sas
|
||||||
- js
|
- js
|
||||||
type: string
|
type: string
|
||||||
ExecuteSASCodePayload:
|
ExecuteCodePayload:
|
||||||
properties:
|
properties:
|
||||||
code:
|
code:
|
||||||
type: string
|
type: string
|
||||||
@@ -666,7 +666,7 @@ paths:
|
|||||||
$ref: '#/components/schemas/ClientPayload'
|
$ref: '#/components/schemas/ClientPayload'
|
||||||
/SASjsApi/code/execute:
|
/SASjsApi/code/execute:
|
||||||
post:
|
post:
|
||||||
operationId: ExecuteSASCode
|
operationId: ExecuteCode
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Ok
|
description: Ok
|
||||||
@@ -687,7 +687,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteSASCodePayload'
|
$ref: '#/components/schemas/ExecuteCodePayload'
|
||||||
/SASjsApi/drive/deploy:
|
/SASjsApi/drive/deploy:
|
||||||
post:
|
post:
|
||||||
operationId: Deploy
|
operationId: Deploy
|
||||||
@@ -763,7 +763,7 @@ paths:
|
|||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {status: failure, message: 'Deployment failed!'}
|
value: {status: failure, message: 'Deployment failed!'}
|
||||||
description: "Accepts JSON file and zipped compressed JSON file as well.\nCompressed file should only contain one JSON file and should have same name\nas of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip\nAny other file or JSON file in zipped will be ignored!"
|
description: "Accepts JSON file and zipped compressed JSON file as well.\r\nCompressed file should only contain one JSON file and should have same name\r\nas of compressed file e.g. deploy.JSON should be compressed to deploy.JSON.zip\r\nAny other file or JSON file in zipped will be ignored!"
|
||||||
summary: 'Creates/updates files within SASjs Drive using uploaded JSON/compressed JSON file.'
|
summary: 'Creates/updates files within SASjs Drive using uploaded JSON/compressed JSON file.'
|
||||||
tags:
|
tags:
|
||||||
- Drive
|
- Drive
|
||||||
@@ -851,7 +851,7 @@ paths:
|
|||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {status: failure, message: 'File request failed.'}
|
value: {status: failure, message: 'File request failed.'}
|
||||||
description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request."
|
description: "It's optional to either provide `_filePath` in url as query parameter\r\nOr provide `filePath` in body as form field.\r\nBut it's required to provide else API will respond with Bad Request."
|
||||||
summary: 'Create a file in SASjs Drive'
|
summary: 'Create a file in SASjs Drive'
|
||||||
tags:
|
tags:
|
||||||
- Drive
|
- Drive
|
||||||
@@ -902,7 +902,7 @@ paths:
|
|||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {status: failure, message: 'File request failed.'}
|
value: {status: failure, message: 'File request failed.'}
|
||||||
description: "It's optional to either provide `_filePath` in url as query parameter\nOr provide `filePath` in body as form field.\nBut it's required to provide else API will respond with Bad Request."
|
description: "It's optional to either provide `_filePath` in url as query parameter\r\nOr provide `filePath` in body as form field.\r\nBut it's required to provide else API will respond with Bad Request."
|
||||||
summary: 'Modify a file in SASjs Drive'
|
summary: 'Modify a file in SASjs Drive'
|
||||||
tags:
|
tags:
|
||||||
- Drive
|
- Drive
|
||||||
@@ -1454,7 +1454,7 @@ paths:
|
|||||||
anyOf:
|
anyOf:
|
||||||
- {type: string}
|
- {type: string}
|
||||||
- {type: string, format: byte}
|
- {type: string, format: byte}
|
||||||
description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms"
|
description: "Trigger a SAS or JS program using the _program URL parameter.\r\n\r\nAccepts URL parameters and file uploads. For more details, see docs:\r\n\r\nhttps://server.sasjs.io/storedprograms"
|
||||||
summary: 'Execute a Stored Program, returns raw _webout content.'
|
summary: 'Execute a Stored Program, returns raw _webout content.'
|
||||||
tags:
|
tags:
|
||||||
- STP
|
- STP
|
||||||
@@ -1482,7 +1482,7 @@ paths:
|
|||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}}
|
value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}}
|
||||||
description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms\n\nThe response will be a JSON object with the following root attributes:\nlog, webout, headers.\n\nThe webout attribute will be nested JSON ONLY if the response-header\ncontains a content-type of application/json AND it is valid JSON.\nOtherwise it will be a stringified version of the webout content."
|
description: "Trigger a SAS or JS program using the _program URL parameter.\r\n\r\nAccepts URL parameters and file uploads. For more details, see docs:\r\n\r\nhttps://server.sasjs.io/storedprograms\r\n\r\nThe response will be a JSON object with the following root attributes:\r\nlog, webout, headers.\r\n\r\nThe webout attribute will be nested JSON ONLY if the response-header\r\ncontains a content-type of application/json AND it is valid JSON.\r\nOtherwise it will be a stringified version of the webout content."
|
||||||
summary: 'Execute a Stored Program, return a JSON object'
|
summary: 'Execute a Stored Program, return a JSON object'
|
||||||
tags:
|
tags:
|
||||||
- STP
|
- STP
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import {
|
import { getSessionController, processProgram } from './'
|
||||||
getSASSessionController,
|
|
||||||
getJSSessionController,
|
|
||||||
processProgram
|
|
||||||
} from './'
|
|
||||||
import { readFile, fileExists, createFile, readFileBinary } from '@sasjs/utils'
|
import { readFile, fileExists, createFile, readFileBinary } from '@sasjs/utils'
|
||||||
import { PreProgramVars, Session, TreeNode } from '../../types'
|
import { PreProgramVars, Session, TreeNode } from '../../types'
|
||||||
import {
|
import {
|
||||||
@@ -76,10 +72,7 @@ export class ExecutionController {
|
|||||||
session: sessionByFileUpload,
|
session: sessionByFileUpload,
|
||||||
runTime
|
runTime
|
||||||
}: ExecuteProgramParams): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
|
}: ExecuteProgramParams): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
|
||||||
const sessionController =
|
const sessionController = getSessionController(runTime)
|
||||||
runTime === RunTimeType.SAS
|
|
||||||
? getSASSessionController()
|
|
||||||
: getJSSessionController()
|
|
||||||
|
|
||||||
const session =
|
const session =
|
||||||
sessionByFileUpload ?? (await sessionController.getSession())
|
sessionByFileUpload ?? (await sessionController.getSession())
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Request, RequestHandler } from 'express'
|
import { Request, RequestHandler } from 'express'
|
||||||
import multer from 'multer'
|
import multer from 'multer'
|
||||||
import { uuidv4 } from '@sasjs/utils'
|
import { uuidv4 } from '@sasjs/utils'
|
||||||
import { getSASSessionController, getJSSessionController } from '.'
|
import { getSessionController } from '.'
|
||||||
import {
|
import {
|
||||||
executeProgramRawValidation,
|
executeProgramRawValidation,
|
||||||
getRunTimeAndFilePath,
|
getRunTimeAndFilePath,
|
||||||
@@ -37,17 +37,23 @@ export class FileUploadController {
|
|||||||
try {
|
try {
|
||||||
;({ runTime } = await getRunTimeAndFilePath(programPath))
|
;({ runTime } = await getRunTimeAndFilePath(programPath))
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(400).send({
|
return res.status(400).send({
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
message: 'Job execution failed',
|
message: 'Job execution failed',
|
||||||
error: typeof err === 'object' ? err.toString() : err
|
error: typeof err === 'object' ? err.toString() : err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionController =
|
let sessionController
|
||||||
runTime === RunTimeType.SAS
|
try {
|
||||||
? getSASSessionController()
|
sessionController = getSessionController(runTime)
|
||||||
: getJSSessionController()
|
} catch (err: any) {
|
||||||
|
return res.status(400).send({
|
||||||
|
status: 'failure',
|
||||||
|
message: err.message,
|
||||||
|
error: typeof err === 'object' ? err.toString() : err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const session = await sessionController.getSession()
|
const session = await sessionController.getSession()
|
||||||
// marking consumed true, so that it's not available
|
// marking consumed true, so that it's not available
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import { execFile } from 'child_process'
|
|||||||
import {
|
import {
|
||||||
getSessionsFolder,
|
getSessionsFolder,
|
||||||
generateUniqueFileName,
|
generateUniqueFileName,
|
||||||
sysInitCompiledPath
|
sysInitCompiledPath,
|
||||||
|
RunTimeType
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
import {
|
import {
|
||||||
deleteFolder,
|
deleteFolder,
|
||||||
createFile,
|
createFile,
|
||||||
fileExists,
|
fileExists,
|
||||||
generateTimestamp,
|
generateTimestamp,
|
||||||
readFile
|
readFile,
|
||||||
|
isWindows
|
||||||
} from '@sasjs/utils'
|
} from '@sasjs/utils'
|
||||||
|
|
||||||
const execFilePromise = promisify(execFile)
|
const execFilePromise = promisify(execFile)
|
||||||
@@ -88,7 +90,7 @@ ${autoExecContent}`
|
|||||||
|
|
||||||
// Additional windows specific options to avoid the desktop popups.
|
// Additional windows specific options to avoid the desktop popups.
|
||||||
|
|
||||||
execFilePromise(process.sasLoc, [
|
execFilePromise(process.sasLoc!, [
|
||||||
'-SYSIN',
|
'-SYSIN',
|
||||||
codePath,
|
codePath,
|
||||||
'-LOG',
|
'-LOG',
|
||||||
@@ -99,11 +101,9 @@ ${autoExecContent}`
|
|||||||
session.path,
|
session.path,
|
||||||
'-AUTOEXEC',
|
'-AUTOEXEC',
|
||||||
autoExecPath,
|
autoExecPath,
|
||||||
'-ENCODING',
|
isWindows() ? '-nosplash' : '',
|
||||||
'UTF-8',
|
isWindows() ? '-icon' : '',
|
||||||
process.platform === 'win32' ? '-nosplash' : '',
|
isWindows() ? '-nologo' : ''
|
||||||
process.platform === 'win32' ? '-icon' : '',
|
|
||||||
process.platform === 'win32' ? '-nologo' : ''
|
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
session.completed = true
|
session.completed = true
|
||||||
@@ -194,7 +194,21 @@ export class JSSessionController extends SessionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSASSessionController = (): SASSessionController => {
|
export const getSessionController = (
|
||||||
|
runTime: RunTimeType
|
||||||
|
): SASSessionController | JSSessionController => {
|
||||||
|
if (runTime === RunTimeType.SAS) {
|
||||||
|
return getSASSessionController()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runTime === RunTimeType.JS) {
|
||||||
|
return getJSSessionController()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('No Runtime is configured')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSASSessionController = (): SASSessionController => {
|
||||||
if (process.sasSessionController) return process.sasSessionController
|
if (process.sasSessionController) return process.sasSessionController
|
||||||
|
|
||||||
process.sasSessionController = new SASSessionController()
|
process.sasSessionController = new SASSessionController()
|
||||||
@@ -202,7 +216,7 @@ export const getSASSessionController = (): SASSessionController => {
|
|||||||
return process.sasSessionController
|
return process.sasSessionController
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getJSSessionController = (): JSSessionController => {
|
const getJSSessionController = (): JSSessionController => {
|
||||||
if (process.jsSessionController) return process.jsSessionController
|
if (process.jsSessionController) return process.jsSessionController
|
||||||
|
|
||||||
process.jsSessionController = new JSSessionController()
|
process.jsSessionController = new JSSessionController()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { isWindows } from '@sasjs/utils'
|
||||||
import { PreProgramVars, Session } from '../../types'
|
import { PreProgramVars, Session } from '../../types'
|
||||||
import { generateFileUploadJSCode } from '../../utils'
|
import { generateFileUploadJSCode } from '../../utils'
|
||||||
import { ExecutionVars } from './'
|
import { ExecutionVars } from './'
|
||||||
@@ -19,7 +20,9 @@ export const createJSProgram = async (
|
|||||||
|
|
||||||
const preProgramVarStatments = `
|
const preProgramVarStatments = `
|
||||||
let _webout = '';
|
let _webout = '';
|
||||||
const weboutPath = '${weboutPath}';
|
const weboutPath = '${
|
||||||
|
isWindows() ? weboutPath.replace(/\\/g, '\\\\') : weboutPath
|
||||||
|
}';
|
||||||
const _sasjs_tokenfile = '${tokenFile}';
|
const _sasjs_tokenfile = '${tokenFile}';
|
||||||
const _sasjs_username = '${preProgramVariables?.username}';
|
const _sasjs_username = '${preProgramVariables?.username}';
|
||||||
const _sasjs_userid = '${preProgramVariables?.userId}';
|
const _sasjs_userid = '${preProgramVariables?.userId}';
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const processProgram = async (
|
|||||||
// waiting for the open event so that we can have underlying file descriptor
|
// waiting for the open event so that we can have underlying file descriptor
|
||||||
await once(writeStream, 'open')
|
await once(writeStream, 'open')
|
||||||
|
|
||||||
execFileSync(process.nodeLoc, [codePath], {
|
execFileSync(process.nodeLoc!, [codePath], {
|
||||||
stdio: ['ignore', writeStream, writeStream]
|
stdio: ['ignore', writeStream, writeStream]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ appPromise.then(async (app) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const { key, cert } = await getCertificates()
|
const { key, cert, ca } = await getCertificates()
|
||||||
|
|
||||||
const httpsServer = createServer({ key, cert }, app)
|
const httpsServer = createServer({ key, cert, ca }, app)
|
||||||
httpsServer.listen(sasJsPort, () => {
|
httpsServer.listen(sasJsPort, () => {
|
||||||
console.log(
|
console.log(
|
||||||
`⚡️[server]: Server is running at https://localhost:${sasJsPort}`
|
`⚡️[server]: Server is running at https://localhost:${sasJsPort}`
|
||||||
|
|||||||
4
api/src/types/system/process.d.ts
vendored
4
api/src/types/system/process.d.ts
vendored
@@ -1,7 +1,7 @@
|
|||||||
declare namespace NodeJS {
|
declare namespace NodeJS {
|
||||||
export interface Process {
|
export interface Process {
|
||||||
sasLoc: string
|
sasLoc?: string
|
||||||
nodeLoc: string
|
nodeLoc?: string
|
||||||
driveLoc: string
|
driveLoc: string
|
||||||
sasSessionController?: import('../../controllers/internal').SASSessionController
|
sasSessionController?: import('../../controllers/internal').SASSessionController
|
||||||
jsSessionController?: import('../../controllers/internal').JSSessionController
|
jsSessionController?: import('../../controllers/internal').JSSessionController
|
||||||
|
|||||||
@@ -2,22 +2,30 @@ import path from 'path'
|
|||||||
import { fileExists, getString, readFile } from '@sasjs/utils'
|
import { fileExists, getString, readFile } from '@sasjs/utils'
|
||||||
|
|
||||||
export const getCertificates = async () => {
|
export const getCertificates = async () => {
|
||||||
const { PRIVATE_KEY, FULL_CHAIN } = process.env
|
const { PRIVATE_KEY, CERT_CHAIN, CA_ROOT } = process.env
|
||||||
|
|
||||||
const keyPath = PRIVATE_KEY ?? (await getFileInput('Private Key (PEM)'))
|
const keyPath = PRIVATE_KEY ?? (await getFileInput('Private Key (PEM)'))
|
||||||
const certPath = FULL_CHAIN ?? (await getFileInput('Full Chain (PEM)'))
|
const certPath = CERT_CHAIN ?? (await getFileInput('Certificate Chain (PEM)'))
|
||||||
|
const caPath = CA_ROOT ?? (await getFileInput('CA ROOT (PEM)'))
|
||||||
|
|
||||||
console.log('keyPath: ', keyPath)
|
console.log('keyPath: ', keyPath)
|
||||||
console.log('certPath: ', certPath)
|
console.log('certPath: ', certPath)
|
||||||
|
console.log('caPath: ', caPath)
|
||||||
|
|
||||||
const key = await readFile(keyPath)
|
const key = await readFile(keyPath)
|
||||||
const cert = await readFile(certPath)
|
const cert = await readFile(certPath)
|
||||||
|
const ca = await readFile(caPath)
|
||||||
|
|
||||||
return { key, cert }
|
return { key, cert, ca }
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileInput = async (filename: string): Promise<string> => {
|
const getFileInput = async (
|
||||||
|
filename: string,
|
||||||
|
required: boolean = true
|
||||||
|
): Promise<string> => {
|
||||||
const validator = async (filePath: string) => {
|
const validator = async (filePath: string) => {
|
||||||
|
if (!required) return true
|
||||||
|
|
||||||
if (!filePath) return `Path to ${filename} is required.`
|
if (!filePath) return `Path to ${filename} is required.`
|
||||||
|
|
||||||
if (!(await fileExists(path.join(process.cwd(), filePath)))) {
|
if (!(await fileExists(path.join(process.cwd(), filePath)))) {
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { getString } from '@sasjs/utils/input'
|
import { getString } from '@sasjs/utils/input'
|
||||||
import { createFolder, fileExists, folderExists } from '@sasjs/utils'
|
import { createFolder, fileExists, folderExists, isWindows } from '@sasjs/utils'
|
||||||
|
import { RunTimeType } from './verifyEnvVariables'
|
||||||
const isWindows = () => process.platform === 'win32'
|
|
||||||
|
|
||||||
export const getDesktopFields = async () => {
|
export const getDesktopFields = async () => {
|
||||||
const { SAS_PATH, NODE_PATH } = process.env
|
const { SAS_PATH, NODE_PATH } = process.env
|
||||||
|
|
||||||
const sasLoc = SAS_PATH ?? (await getSASLocation())
|
let sasLoc, nodeLoc
|
||||||
const nodeLoc = NODE_PATH ?? (await getNodeLocation())
|
|
||||||
// const driveLoc = DRIVE_PATH ?? (await getDriveLocation())
|
if (process.runTimes.includes(RunTimeType.SAS)) {
|
||||||
|
sasLoc = SAS_PATH ?? (await getSASLocation())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.runTimes.includes(RunTimeType.JS)) {
|
||||||
|
nodeLoc = NODE_PATH ?? (await getNodeLocation())
|
||||||
|
}
|
||||||
|
|
||||||
return { sasLoc, nodeLoc }
|
return { sasLoc, nodeLoc }
|
||||||
}
|
}
|
||||||
@@ -55,7 +60,7 @@ const getSASLocation = async (): Promise<string> => {
|
|||||||
: '/opt/sas/sas9/SASHome/SASFoundation/9.4/sasexe/sas'
|
: '/opt/sas/sas9/SASHome/SASFoundation/9.4/sasexe/sas'
|
||||||
|
|
||||||
const targetName = await getString(
|
const targetName = await getString(
|
||||||
'Please enter path to SAS executable (absolute path): ',
|
'Please enter full path to a SAS executable with UTF-8 encoding: ',
|
||||||
validator,
|
validator,
|
||||||
defaultLocation
|
defaultLocation
|
||||||
)
|
)
|
||||||
@@ -75,11 +80,11 @@ const getNodeLocation = async (): Promise<string> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultLocation = isWindows()
|
const defaultLocation = isWindows()
|
||||||
? 'C:\\Program Files\\nodejs\\'
|
? 'C:\\Program Files\\nodejs\\node.exe'
|
||||||
: '/usr/local/nodejs/bin'
|
: '/usr/local/nodejs/bin/node.sh'
|
||||||
|
|
||||||
const targetName = await getString(
|
const targetName = await getString(
|
||||||
'Please enter path to nodejs executable (absolute path): ',
|
'Please enter full path to a NodeJS executable: ',
|
||||||
validator,
|
validator,
|
||||||
defaultLocation
|
defaultLocation
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ export const setProcessVariables = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { MODE } = process.env
|
const { MODE, RUN_TIMES } = process.env
|
||||||
|
|
||||||
|
process.runTimes = (RUN_TIMES?.split(',') as RunTimeType[]) ?? []
|
||||||
|
|
||||||
if (MODE === ModeType.Server) {
|
if (MODE === ModeType.Server) {
|
||||||
process.sasLoc = process.env.SAS_PATH as string
|
process.sasLoc = process.env.SAS_PATH
|
||||||
process.nodeLoc = process.env.NODE_PATH as string
|
process.nodeLoc = process.env.NODE_PATH
|
||||||
} else {
|
} else {
|
||||||
const { sasLoc, nodeLoc } = await getDesktopFields()
|
const { sasLoc, nodeLoc } = await getDesktopFields()
|
||||||
|
|
||||||
@@ -26,9 +28,6 @@ export const setProcessVariables = async () => {
|
|||||||
await createFolder(absPath)
|
await createFolder(absPath)
|
||||||
process.driveLoc = getRealPath(absPath)
|
process.driveLoc = getRealPath(absPath)
|
||||||
|
|
||||||
const { RUN_TIMES } = process.env
|
|
||||||
process.runTimes = (RUN_TIMES as string).split(',') as RunTimeType[]
|
|
||||||
|
|
||||||
console.log('sasLoc: ', process.sasLoc)
|
console.log('sasLoc: ', process.sasLoc)
|
||||||
console.log('sasDrive: ', process.driveLoc)
|
console.log('sasDrive: ', process.driveLoc)
|
||||||
console.log('runTimes: ', process.runTimes)
|
console.log('runTimes: ', process.runTimes)
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export const folderParamValidation = (data: any): Joi.ValidationResult =>
|
|||||||
export const runCodeValidation = (data: any): Joi.ValidationResult =>
|
export const runCodeValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
code: Joi.string().required(),
|
code: Joi.string().required(),
|
||||||
runTime: Joi.string().valid(...Object.values(RunTimeType))
|
runTime: Joi.string().valid(...process.runTimes)
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
export const executeProgramRawValidation = (data: any): Joi.ValidationResult =>
|
export const executeProgramRawValidation = (data: any): Joi.ValidationResult =>
|
||||||
|
|||||||
@@ -129,16 +129,16 @@ const verifyPROTOCOL = (): string[] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.PROTOCOL === ProtocolType.HTTPS) {
|
if (process.env.PROTOCOL === ProtocolType.HTTPS) {
|
||||||
const { PRIVATE_KEY, FULL_CHAIN } = process.env
|
const { PRIVATE_KEY, CERT_CHAIN } = process.env
|
||||||
|
|
||||||
if (!PRIVATE_KEY)
|
if (!PRIVATE_KEY)
|
||||||
errors.push(
|
errors.push(
|
||||||
`- PRIVATE_KEY is required for PROTOCOL '${ProtocolType.HTTPS}'`
|
`- PRIVATE_KEY is required for PROTOCOL '${ProtocolType.HTTPS}'`
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!FULL_CHAIN)
|
if (!CERT_CHAIN)
|
||||||
errors.push(
|
errors.push(
|
||||||
`- FULL_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'`
|
`- CERT_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,5 +258,5 @@ const DEFAULTS = {
|
|||||||
PORT: '5000',
|
PORT: '5000',
|
||||||
HELMET_COEP: HelmetCoepType.TRUE,
|
HELMET_COEP: HelmetCoepType.TRUE,
|
||||||
LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common,
|
LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common,
|
||||||
RUN_TIMES: `${RunTimeType.SAS},${RunTimeType.JS}`
|
RUN_TIMES: RunTimeType.SAS
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,16 @@ const Studio = () => {
|
|||||||
const [ctrlPressed, setCtrlPressed] = useState(false)
|
const [ctrlPressed, setCtrlPressed] = useState(false)
|
||||||
const [webout, setWebout] = useState('')
|
const [webout, setWebout] = useState('')
|
||||||
const [tab, setTab] = useState('1')
|
const [tab, setTab] = useState('1')
|
||||||
const [selectedRunTime, setSelectedRunTime] = useState(RunTimeType.SAS)
|
const [runTimes, setRunTimes] = useState<string[]>([])
|
||||||
|
const [selectedRunTime, setSelectedRunTime] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRunTimes(Object.values(appContext.runTimes))
|
||||||
|
}, [appContext.runTimes])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (runTimes.length) setSelectedRunTime(runTimes[0])
|
||||||
|
}, [runTimes])
|
||||||
|
|
||||||
const handleTabChange = (_e: any, newValue: string) => {
|
const handleTabChange = (_e: any, newValue: string) => {
|
||||||
setTab(newValue)
|
setTab(newValue)
|
||||||
@@ -153,7 +162,7 @@ const Studio = () => {
|
|||||||
</TabList>
|
</TabList>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TabPanel style={{ paddingBottom: 0 }} value="1">
|
<TabPanel sx={{ paddingBottom: 0 }} value="1">
|
||||||
<div className={classes.subMenu}>
|
<div className={classes.subMenu}>
|
||||||
<Tooltip title="CTRL+ENTER will also run SAS code">
|
<Tooltip title="CTRL+ENTER will also run SAS code">
|
||||||
<Button onClick={handleRunBtnClick} className={classes.runButton}>
|
<Button onClick={handleRunBtnClick} className={classes.runButton}>
|
||||||
@@ -174,8 +183,10 @@ const Studio = () => {
|
|||||||
value={selectedRunTime}
|
value={selectedRunTime}
|
||||||
onChange={handleChangeRunTime}
|
onChange={handleChangeRunTime}
|
||||||
>
|
>
|
||||||
{appContext.runTimes.map((runTime) => (
|
{runTimes.map((runTime) => (
|
||||||
<MenuItem value={runTime}>{runTime}</MenuItem>
|
<MenuItem key={runTime} value={runTime}>
|
||||||
|
{runTime}
|
||||||
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user