mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb4f3442d5 | ||
|
|
09d1b7d5d4 | ||
|
|
99839ae62f | ||
|
|
f700561e1a | ||
|
|
8b4b4b91ab | ||
|
|
acb3ae0493 | ||
|
|
f48aeb1b0b | ||
|
|
5c0e8e5344 | ||
|
|
0ac9e4af7d | ||
|
|
ee80f3f968 | ||
|
|
7f4201ba85 | ||
|
|
f830bbc058 | ||
|
|
f8e1522a5a | ||
|
|
0a5aeceab5 | ||
|
|
6dc39c0d91 | ||
|
|
117a53ceea | ||
|
|
dd56a95314 | ||
|
|
c5117abe71 | ||
|
|
84c632a861 | ||
|
|
3ddd09eba0 | ||
|
|
0c0301433c | ||
|
|
954b2e3e2e | ||
|
|
5655311b96 | ||
|
|
9ace33d783 |
43
CHANGELOG.md
43
CHANGELOG.md
@@ -2,6 +2,49 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [0.0.47](https://github.com/sasjs/server/compare/v0.0.46...v0.0.47) (2022-03-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **web:** updated STUDIO log and webout ([f700561](https://github.com/sasjs/server/commit/f700561e1a8d06c18ca2bdbe4605d7ab34f7a761))
|
||||||
|
|
||||||
|
### [0.0.46](https://github.com/sasjs/server/compare/v0.0.45...v0.0.46) (2022-03-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **drive:** GET folder contents API added ([0ac9e4a](https://github.com/sasjs/server/commit/0ac9e4af7d67c4431053e80eb2384bf5bdc3f8b3))
|
||||||
|
|
||||||
|
### [0.0.45](https://github.com/sasjs/server/compare/v0.0.43...v0.0.45) (2022-03-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* DELETE req cannot have body ([0a5aece](https://github.com/sasjs/server/commit/0a5aeceab560b022197d0c30c3da7f091b261b1e))
|
||||||
|
* increased req body size ([6dc39c0](https://github.com/sasjs/server/commit/6dc39c0d91ac13d6d9b8c0a2240446bfc45bdd7f))
|
||||||
|
* proving a PRINT destination during SAS invocation. ([7f4201b](https://github.com/sasjs/server/commit/7f4201ba855743144fa6d3efac2b11e816d4696e)), closes [#111](https://github.com/sasjs/server/issues/111)
|
||||||
|
* **session:** increased session + bug fixed ([117a53c](https://github.com/sasjs/server/commit/117a53ceeadf487a6326384ae11c10e98646631f))
|
||||||
|
* **stp:** use same session from file upload ([dd56a95](https://github.com/sasjs/server/commit/dd56a95314f0b61480489118734e45877e1745ef))
|
||||||
|
|
||||||
|
### [0.0.44](https://github.com/sasjs/server/compare/v0.0.43...v0.0.44) (2022-03-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* DELETE req cannot have body ([0a5aece](https://github.com/sasjs/server/commit/0a5aeceab560b022197d0c30c3da7f091b261b1e))
|
||||||
|
* increased req body size ([6dc39c0](https://github.com/sasjs/server/commit/6dc39c0d91ac13d6d9b8c0a2240446bfc45bdd7f))
|
||||||
|
* **session:** increased session + bug fixed ([117a53c](https://github.com/sasjs/server/commit/117a53ceeadf487a6326384ae11c10e98646631f))
|
||||||
|
* **stp:** use same session from file upload ([dd56a95](https://github.com/sasjs/server/commit/dd56a95314f0b61480489118734e45877e1745ef))
|
||||||
|
|
||||||
|
### [0.0.43](https://github.com/sasjs/server/compare/v0.0.42...v0.0.43) (2022-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deploy:** user can deploy to same appName with different/same appLoc ([9ace33d](https://github.com/sasjs/server/commit/9ace33d7830a9def42d741c23b46090afe0c5510))
|
||||||
|
* fallback logo on AppStream ([5655311](https://github.com/sasjs/server/commit/5655311b9663225823c192b39a03f39d17dda730))
|
||||||
|
|
||||||
### [0.0.42](https://github.com/sasjs/server/compare/v0.0.41...v0.0.42) (2022-03-23)
|
### [0.0.42](https://github.com/sasjs/server/compare/v0.0.41...v0.0.42) (2022-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -48,11 +48,21 @@ When launching the app, it will make use of specific environment variables. Thes
|
|||||||
Example contents of a `.env` file:
|
Example contents of a `.env` file:
|
||||||
|
|
||||||
```
|
```
|
||||||
MODE=desktop # options: [desktop|server] default: `desktop`
|
# options: [desktop|server] default: `desktop`
|
||||||
CORS=disable # options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
MODE=
|
||||||
WHITELIST= # options: <http://localhost:3000 https://abc.com ...> space separated urls
|
|
||||||
PROTOCOL=http # options: [http|https] default: http
|
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
||||||
PORT=5000 # default: 5000
|
CORS=
|
||||||
|
|
||||||
|
# options: <http://localhost:3000 https://abc.com ...> space separated urls
|
||||||
|
WHITELIST=
|
||||||
|
|
||||||
|
# options: [http|https] default: http
|
||||||
|
PROTOCOL=
|
||||||
|
|
||||||
|
# default: 5000
|
||||||
|
PORT=
|
||||||
|
|
||||||
|
|
||||||
# optional
|
# optional
|
||||||
# for MODE: `desktop`, prompts user
|
# for MODE: `desktop`, prompts user
|
||||||
|
|||||||
12
api/package-lock.json
generated
12
api/package-lock.json
generated
@@ -7107,9 +7107,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimist": {
|
"node_modules/minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||||
},
|
},
|
||||||
"node_modules/mkdirp": {
|
"node_modules/mkdirp": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
@@ -15624,9 +15624,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||||
},
|
},
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
|
|||||||
@@ -612,7 +612,6 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: 'No content'
|
description: 'No content'
|
||||||
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."
|
|
||||||
summary: 'Get file from SASjs Drive'
|
summary: 'Get file from SASjs Drive'
|
||||||
tags:
|
tags:
|
||||||
- Drive
|
- Drive
|
||||||
@@ -623,19 +622,10 @@ paths:
|
|||||||
-
|
-
|
||||||
in: query
|
in: query
|
||||||
name: _filePath
|
name: _filePath
|
||||||
required: false
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: /Public/somefolder/some.file
|
example: /Public/somefolder/some.file
|
||||||
requestBody:
|
|
||||||
required: false
|
|
||||||
content:
|
|
||||||
multipart/form-data:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
filePath:
|
|
||||||
type: string
|
|
||||||
delete:
|
delete:
|
||||||
operationId: DeleteFile
|
operationId: DeleteFile
|
||||||
responses:
|
responses:
|
||||||
@@ -649,7 +639,6 @@ paths:
|
|||||||
required:
|
required:
|
||||||
- status
|
- status
|
||||||
type: object
|
type: object
|
||||||
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."
|
|
||||||
summary: 'Delete file from SASjs Drive'
|
summary: 'Delete file from SASjs Drive'
|
||||||
tags:
|
tags:
|
||||||
- Drive
|
- Drive
|
||||||
@@ -660,19 +649,10 @@ paths:
|
|||||||
-
|
-
|
||||||
in: query
|
in: query
|
||||||
name: _filePath
|
name: _filePath
|
||||||
required: false
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: /Public/somefolder/some.file
|
example: /Public/somefolder/some.file
|
||||||
requestBody:
|
|
||||||
required: false
|
|
||||||
content:
|
|
||||||
multipart/form-data:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
filePath:
|
|
||||||
type: string
|
|
||||||
post:
|
post:
|
||||||
operationId: SaveFile
|
operationId: SaveFile
|
||||||
responses:
|
responses:
|
||||||
@@ -775,6 +755,36 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- file
|
- file
|
||||||
|
/SASjsApi/drive/folder:
|
||||||
|
get:
|
||||||
|
operationId: GetFolder
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
folders: {items: {type: string}, type: array}
|
||||||
|
files: {items: {type: string}, type: array}
|
||||||
|
required:
|
||||||
|
- folders
|
||||||
|
- files
|
||||||
|
type: object
|
||||||
|
summary: 'Get folder contents from SASjs Drive'
|
||||||
|
tags:
|
||||||
|
- Drive
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
in: query
|
||||||
|
name: _folderPath
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: /Public/somefolder
|
||||||
/SASjsApi/drive/filetree:
|
/SASjsApi/drive/filetree:
|
||||||
get:
|
get:
|
||||||
operationId: GetFileTree
|
operationId: GetFileTree
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import {
|
|||||||
copySASjsCore,
|
copySASjsCore,
|
||||||
getWebBuildFolderPath,
|
getWebBuildFolderPath,
|
||||||
loadAppStreamConfig,
|
loadAppStreamConfig,
|
||||||
sasJSCoreMacros,
|
setProcessVariables,
|
||||||
setProcessVariables
|
setupFolders
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@@ -34,7 +34,7 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
|
|||||||
|
|
||||||
app.use(cookieParser())
|
app.use(cookieParser())
|
||||||
app.use(morgan('tiny'))
|
app.use(morgan('tiny'))
|
||||||
app.use(express.json({ limit: '50mb' }))
|
app.use(express.json({ limit: '100mb' }))
|
||||||
app.use(express.static(path.join(__dirname, '../public')))
|
app.use(express.static(path.join(__dirname, '../public')))
|
||||||
|
|
||||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||||
@@ -43,6 +43,7 @@ const onError: ErrorRequestHandler = (err, req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default setProcessVariables().then(async () => {
|
export default setProcessVariables().then(async () => {
|
||||||
|
await setupFolders()
|
||||||
await copySASjsCore()
|
await copySASjsCore()
|
||||||
|
|
||||||
// loading these modules after setting up variables due to
|
// loading these modules after setting up variables due to
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ import {
|
|||||||
fileExists,
|
fileExists,
|
||||||
moveFile,
|
moveFile,
|
||||||
createFolder,
|
createFolder,
|
||||||
deleteFile as deleteFileOnSystem
|
deleteFile as deleteFileOnSystem,
|
||||||
|
folderExists,
|
||||||
|
listFilesAndSubFoldersInFolder,
|
||||||
|
listFilesInFolder,
|
||||||
|
listSubFoldersInFolder,
|
||||||
|
isFolder
|
||||||
} from '@sasjs/utils'
|
} from '@sasjs/utils'
|
||||||
import { createFileTree, ExecutionController, getTreeExample } from './internal'
|
import { createFileTree, ExecutionController, getTreeExample } from './internal'
|
||||||
|
|
||||||
@@ -89,9 +94,6 @@ export class DriveController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It's optional to either provide `_filePath` in url as query parameter
|
|
||||||
* Or provide `filePath` in body as form field.
|
|
||||||
* But it's required to provide else API will respond with Bad Request.
|
|
||||||
*
|
*
|
||||||
* @summary Get file from SASjs Drive
|
* @summary Get file from SASjs Drive
|
||||||
* @query _filePath Location of SAS program
|
* @query _filePath Location of SAS program
|
||||||
@@ -100,28 +102,31 @@ export class DriveController {
|
|||||||
@Get('/file')
|
@Get('/file')
|
||||||
public async getFile(
|
public async getFile(
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
|
@Query() _filePath: string
|
||||||
@Query() _filePath?: string,
|
|
||||||
@FormField() filePath?: string
|
|
||||||
) {
|
) {
|
||||||
return getFile(request, (_filePath ?? filePath)!)
|
return getFile(request, _filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get folder contents from SASjs Drive
|
||||||
|
* @query _folderPath Location of SAS program
|
||||||
|
* @example _folderPath "/Public/somefolder"
|
||||||
|
*/
|
||||||
|
@Get('/folder')
|
||||||
|
public async getFolder(@Query() _folderPath?: string) {
|
||||||
|
return getFolder(_folderPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It's optional to either provide `_filePath` in url as query parameter
|
|
||||||
* Or provide `filePath` in body as form field.
|
|
||||||
* But it's required to provide else API will respond with Bad Request.
|
|
||||||
*
|
*
|
||||||
* @summary Delete file from SASjs Drive
|
* @summary Delete file from SASjs Drive
|
||||||
* @query _filePath Location of SAS program
|
* @query _filePath Location of SAS program
|
||||||
* @example _filePath "/Public/somefolder/some.file"
|
* @example _filePath "/Public/somefolder/some.file"
|
||||||
*/
|
*/
|
||||||
@Delete('/file')
|
@Delete('/file')
|
||||||
public async deleteFile(
|
public async deleteFile(@Query() _filePath: string) {
|
||||||
@Query() _filePath?: string,
|
return deleteFile(_filePath)
|
||||||
@FormField() filePath?: string
|
|
||||||
) {
|
|
||||||
return deleteFile((_filePath ?? filePath)!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,7 +232,7 @@ const getFile = async (req: express.Request, filePath: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(await fileExists(filePathFull))) {
|
if (!(await fileExists(filePathFull))) {
|
||||||
throw new Error('File does not exist.')
|
throw new Error("File doesn't exist.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const extension = path.extname(filePathFull).toLowerCase()
|
const extension = path.extname(filePathFull).toLowerCase()
|
||||||
@@ -238,6 +243,36 @@ const getFile = async (req: express.Request, filePath: string) => {
|
|||||||
req.res?.sendFile(path.resolve(filePathFull))
|
req.res?.sendFile(path.resolve(filePathFull))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getFolder = async (folderPath?: string) => {
|
||||||
|
const driveFilesPath = getTmpFilesFolderPath()
|
||||||
|
|
||||||
|
if (folderPath) {
|
||||||
|
const folderPathFull = path
|
||||||
|
.join(getTmpFilesFolderPath(), folderPath)
|
||||||
|
.replace(new RegExp('/', 'g'), path.sep)
|
||||||
|
|
||||||
|
if (!folderPathFull.includes(driveFilesPath)) {
|
||||||
|
throw new Error('Cannot get folder outside drive.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await folderExists(folderPathFull))) {
|
||||||
|
throw new Error("Folder doesn't exist.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await isFolder(folderPathFull))) {
|
||||||
|
throw new Error('Not a Folder.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const files: string[] = await listFilesInFolder(folderPathFull)
|
||||||
|
const folders: string[] = await listSubFoldersInFolder(folderPathFull)
|
||||||
|
return { files, folders }
|
||||||
|
}
|
||||||
|
|
||||||
|
const files: string[] = await listFilesInFolder(driveFilesPath)
|
||||||
|
const folders: string[] = await listSubFoldersInFolder(driveFilesPath)
|
||||||
|
return { files, folders }
|
||||||
|
}
|
||||||
|
|
||||||
const deleteFile = async (filePath: string) => {
|
const deleteFile = async (filePath: string) => {
|
||||||
const driveFilesPath = getTmpFilesFolderPath()
|
const driveFilesPath = getTmpFilesFolderPath()
|
||||||
|
|
||||||
@@ -305,9 +340,3 @@ const updateFile = async (
|
|||||||
|
|
||||||
return { status: 'success' }
|
return { status: 'success' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateFilePath = async (filePath: string) => {
|
|
||||||
if (!(await fileExists(filePath))) {
|
|
||||||
throw 'DriveController: File does not exists.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
moveFile,
|
moveFile,
|
||||||
readFileBinary
|
readFileBinary
|
||||||
} from '@sasjs/utils'
|
} from '@sasjs/utils'
|
||||||
import { PreProgramVars, TreeNode } from '../../types'
|
import { PreProgramVars, Session, TreeNode } from '../../types'
|
||||||
import {
|
import {
|
||||||
extractHeaders,
|
extractHeaders,
|
||||||
generateFileUploadSasCode,
|
generateFileUploadSasCode,
|
||||||
@@ -39,7 +39,8 @@ export class ExecutionController {
|
|||||||
preProgramVariables: PreProgramVars,
|
preProgramVariables: PreProgramVars,
|
||||||
vars: ExecutionVars,
|
vars: ExecutionVars,
|
||||||
otherArgs?: any,
|
otherArgs?: any,
|
||||||
returnJson?: boolean
|
returnJson?: boolean,
|
||||||
|
session?: Session
|
||||||
) {
|
) {
|
||||||
if (!(await fileExists(programPath)))
|
if (!(await fileExists(programPath)))
|
||||||
throw 'ExecutionController: SAS file does not exist.'
|
throw 'ExecutionController: SAS file does not exist.'
|
||||||
@@ -51,7 +52,8 @@ export class ExecutionController {
|
|||||||
preProgramVariables,
|
preProgramVariables,
|
||||||
vars,
|
vars,
|
||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson
|
returnJson,
|
||||||
|
session
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +62,13 @@ export class ExecutionController {
|
|||||||
preProgramVariables: PreProgramVars,
|
preProgramVariables: PreProgramVars,
|
||||||
vars: ExecutionVars,
|
vars: ExecutionVars,
|
||||||
otherArgs?: any,
|
otherArgs?: any,
|
||||||
returnJson?: boolean
|
returnJson?: boolean,
|
||||||
|
sessionByFileUpload?: Session
|
||||||
): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
|
): Promise<ExecuteReturnRaw | ExecuteReturnJson> {
|
||||||
const sessionController = getSessionController()
|
const sessionController = getSessionController()
|
||||||
|
|
||||||
const session = await sessionController.getSession()
|
const session =
|
||||||
|
sessionByFileUpload ?? (await sessionController.getSession())
|
||||||
session.inUse = true
|
session.inUse = true
|
||||||
session.consumed = true
|
session.consumed = true
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ export class FileUploadController {
|
|||||||
|
|
||||||
const sessionController = getSessionController()
|
const sessionController = getSessionController()
|
||||||
session = await sessionController.getSession()
|
session = await sessionController.getSession()
|
||||||
session.inUse = true
|
// marking consumed true, so that it's not available
|
||||||
|
// as readySession for any other request
|
||||||
|
session.consumed = true
|
||||||
|
|
||||||
req.sasSession = session
|
req.sasSession = session
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class SessionController {
|
|||||||
? readySessions[0]
|
? readySessions[0]
|
||||||
: await this.createSession()
|
: await this.createSession()
|
||||||
|
|
||||||
if (readySessions.length < 2) this.createSession()
|
if (readySessions.length < 3) this.createSession()
|
||||||
|
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
@@ -87,6 +87,8 @@ ${autoExecContent}`
|
|||||||
codePath,
|
codePath,
|
||||||
'-LOG',
|
'-LOG',
|
||||||
path.join(session.path, 'log.log'),
|
path.join(session.path, 'log.log'),
|
||||||
|
'-PRINT',
|
||||||
|
path.join(session.path, 'output.lst'),
|
||||||
'-WORK',
|
'-WORK',
|
||||||
session.path,
|
session.path,
|
||||||
'-AUTOEXEC',
|
'-AUTOEXEC',
|
||||||
|
|||||||
@@ -143,9 +143,8 @@ const executeReturnRaw = async (
|
|||||||
query
|
query
|
||||||
)) as ExecuteReturnRaw
|
)) as ExecuteReturnRaw
|
||||||
|
|
||||||
// Should over-ride response header for
|
// Should over-ride response header for debug
|
||||||
// debug on GET request to see entire log
|
// on GET request to see entire log rendering on browser.
|
||||||
// rendering on browser.
|
|
||||||
if (isDebugOn(query)) {
|
if (isDebugOn(query)) {
|
||||||
httpHeaders['content-type'] = 'text/plain'
|
httpHeaders['content-type'] = 'text/plain'
|
||||||
}
|
}
|
||||||
@@ -185,7 +184,8 @@ const executeReturnJson = async (
|
|||||||
getPreProgramVariables(req),
|
getPreProgramVariables(req),
|
||||||
{ ...req.query, ...req.body },
|
{ ...req.query, ...req.body },
|
||||||
{ filesNamesMap: filesNamesMap },
|
{ filesNamesMap: filesNamesMap },
|
||||||
true
|
true,
|
||||||
|
req.sasSession
|
||||||
)) as ExecuteReturnJson
|
)) as ExecuteReturnJson
|
||||||
|
|
||||||
let weboutRes: string | IRecordOfAny = webout
|
let weboutRes: string | IRecordOfAny = webout
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import { DriveController } from '../../controllers/'
|
|||||||
import {
|
import {
|
||||||
deployValidation,
|
deployValidation,
|
||||||
fileBodyValidation,
|
fileBodyValidation,
|
||||||
fileParamValidation
|
fileParamValidation,
|
||||||
|
folderParamValidation
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
|
||||||
const controller = new DriveController()
|
const controller = new DriveController()
|
||||||
@@ -44,12 +45,24 @@ driveRouter.post('/deploy', async (req, res) => {
|
|||||||
|
|
||||||
driveRouter.get('/file', async (req, res) => {
|
driveRouter.get('/file', async (req, res) => {
|
||||||
const { error: errQ, value: query } = fileParamValidation(req.query)
|
const { error: errQ, value: query } = fileParamValidation(req.query)
|
||||||
const { error: errB, value: body } = fileBodyValidation(req.body)
|
|
||||||
|
|
||||||
if (errQ && errB) return res.status(400).send(errQ.details[0].message)
|
if (errQ) return res.status(400).send(errQ.details[0].message)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await controller.getFile(req, query._filePath, body.filePath)
|
await controller.getFile(req, query._filePath)
|
||||||
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
driveRouter.get('/folder', async (req, res) => {
|
||||||
|
const { error: errQ, value: query } = folderParamValidation(req.query)
|
||||||
|
|
||||||
|
if (errQ) return res.status(400).send(errQ.details[0].message)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await controller.getFolder(query._folderPath)
|
||||||
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
}
|
}
|
||||||
@@ -57,12 +70,11 @@ driveRouter.get('/file', async (req, res) => {
|
|||||||
|
|
||||||
driveRouter.delete('/file', async (req, res) => {
|
driveRouter.delete('/file', async (req, res) => {
|
||||||
const { error: errQ, value: query } = fileParamValidation(req.query)
|
const { error: errQ, value: query } = fileParamValidation(req.query)
|
||||||
const { error: errB, value: body } = fileBodyValidation(req.body)
|
|
||||||
|
|
||||||
if (errQ && errB) return res.status(400).send(errQ.details[0].message)
|
if (errQ) return res.status(400).send(errQ.details[0].message)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await controller.deleteFile(query._filePath, body.filePath)
|
const response = await controller.deleteFile(query._filePath)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
res.status(403).send(err.toString())
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import {
|
|||||||
readFile,
|
readFile,
|
||||||
deleteFolder,
|
deleteFolder,
|
||||||
generateTimestamp,
|
generateTimestamp,
|
||||||
copy
|
copy,
|
||||||
|
createFolder,
|
||||||
|
createFile
|
||||||
} from '@sasjs/utils'
|
} from '@sasjs/utils'
|
||||||
import * as fileUtilModules from '../../../utils/file'
|
import * as fileUtilModules from '../../../utils/file'
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ const user = {
|
|||||||
isActive: true
|
isActive: true
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('files', () => {
|
describe('drive', () => {
|
||||||
let con: Mongoose
|
let con: Mongoose
|
||||||
let mongoServer: MongoMemoryServer
|
let mongoServer: MongoMemoryServer
|
||||||
const controller = new UserController()
|
const controller = new UserController()
|
||||||
@@ -69,6 +71,7 @@ describe('files', () => {
|
|||||||
await mongoServer.stop()
|
await mongoServer.stop()
|
||||||
await deleteFolder(tmpFolder)
|
await deleteFolder(tmpFolder)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('deploy', () => {
|
describe('deploy', () => {
|
||||||
const shouldFailAssertion = async (payload: any) => {
|
const shouldFailAssertion = async (payload: any) => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
@@ -172,17 +175,126 @@ describe('files', () => {
|
|||||||
|
|
||||||
await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code)
|
await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code)
|
||||||
|
|
||||||
await deleteFolder(getTmpFilesFolderPath())
|
await deleteFolder(path.join(getTmpFilesFolderPath(), 'public'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('folder', () => {
|
||||||
|
describe('get', () => {
|
||||||
|
const getFolderApi = '/SASjsApi/drive/folder'
|
||||||
|
|
||||||
|
it('should get root SAS folder on drive', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get(getFolderApi)
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
expect(res.body).toEqual({ files: [], folders: [] })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should get a SAS folder on drive having _folderPath as query param', async () => {
|
||||||
|
const pathToDrive = fileUtilModules.getTmpFilesFolderPath()
|
||||||
|
|
||||||
|
const dirLevel1 = 'level1'
|
||||||
|
const dirLevel2 = 'level2'
|
||||||
|
const fileLevel1 = 'file1'
|
||||||
|
const fileLevel2 = 'file2'
|
||||||
|
|
||||||
|
await createFolder(path.join(pathToDrive, dirLevel1, dirLevel2))
|
||||||
|
await createFile(
|
||||||
|
path.join(pathToDrive, dirLevel1, fileLevel1),
|
||||||
|
'some file content'
|
||||||
|
)
|
||||||
|
await createFile(
|
||||||
|
path.join(pathToDrive, dirLevel1, dirLevel2, fileLevel2),
|
||||||
|
'some file content'
|
||||||
|
)
|
||||||
|
|
||||||
|
const res1 = await request(app)
|
||||||
|
.get(getFolderApi)
|
||||||
|
.query({ _folderPath: '/' })
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
|
||||||
|
expect(res1.statusCode).toEqual(200)
|
||||||
|
expect(res1.body).toEqual({ files: [], folders: [dirLevel1] })
|
||||||
|
|
||||||
|
const res2 = await request(app)
|
||||||
|
.get(getFolderApi)
|
||||||
|
.query({ _folderPath: dirLevel1 })
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
|
||||||
|
expect(res2.statusCode).toEqual(200)
|
||||||
|
expect(res2.body).toEqual({ files: [fileLevel1], folders: [dirLevel2] })
|
||||||
|
|
||||||
|
const res3 = await request(app)
|
||||||
|
.get(getFolderApi)
|
||||||
|
.query({ _folderPath: `${dirLevel1}/${dirLevel2}` })
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
|
||||||
|
expect(res3.statusCode).toEqual(200)
|
||||||
|
expect(res3.body).toEqual({ files: [fileLevel2], folders: [] })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
|
const res = await request(app).get(getFolderApi).expect(401)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Unauthorized')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if folder is not present', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get(getFolderApi)
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.query({ _folderPath: `/my/path/code-${generateTimestamp()}` })
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(`Error: Folder doesn't exist.`)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if folderPath outside Drive', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get(getFolderApi)
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.query({ _folderPath: '/../path/code.sas' })
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Error: Cannot get folder outside drive.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if folderPath is of a file', async () => {
|
||||||
|
const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas')
|
||||||
|
const filePath = '/my/path/code.sas'
|
||||||
|
|
||||||
|
const pathToCopy = path.join(
|
||||||
|
fileUtilModules.getTmpFilesFolderPath(),
|
||||||
|
filePath
|
||||||
|
)
|
||||||
|
await copy(fileToCopyPath, pathToCopy)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.get(getFolderApi)
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.query({ _folderPath: filePath })
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Error: Not a Folder.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('file', () => {
|
describe('file', () => {
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('should create a SAS file on drive having filePath as form field', async () => {
|
it('should create a SAS file on drive having filePath as form field', async () => {
|
||||||
|
const pathToUpload = `/my/path/code-1.sas`
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/drive/file')
|
.post('/SASjsApi/drive/file')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.field('filePath', '/my/path/code.sas')
|
.field('filePath', pathToUpload)
|
||||||
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
|
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
|
||||||
|
|
||||||
expect(res.statusCode).toEqual(200)
|
expect(res.statusCode).toEqual(200)
|
||||||
@@ -192,10 +304,12 @@ describe('files', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should create a SAS file on drive having _filePath as query param', async () => {
|
it('should create a SAS file on drive having _filePath as query param', async () => {
|
||||||
|
const pathToUpload = `/my/path/code-2.sas`
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/drive/file')
|
.post('/SASjsApi/drive/file')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.query({ _filePath: '/my/path/code1.sas' })
|
.query({ _filePath: pathToUpload })
|
||||||
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
|
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
|
||||||
|
|
||||||
expect(res.statusCode).toEqual(200)
|
expect(res.statusCode).toEqual(200)
|
||||||
@@ -217,7 +331,7 @@ describe('files', () => {
|
|||||||
|
|
||||||
it('should respond with Forbidden if file is already present', async () => {
|
it('should respond with Forbidden if file is already present', async () => {
|
||||||
const fileToAttachPath = path.join(__dirname, 'files', 'sample.sas')
|
const fileToAttachPath = path.join(__dirname, 'files', 'sample.sas')
|
||||||
const pathToUpload = '/my/path/code.sas'
|
const pathToUpload = `/my/path/code-${generateTimestamp()}.sas`
|
||||||
|
|
||||||
const pathToCopy = path.join(
|
const pathToCopy = path.join(
|
||||||
fileUtilModules.getTmpFilesFolderPath(),
|
fileUtilModules.getTmpFilesFolderPath(),
|
||||||
@@ -386,7 +500,7 @@ describe('files', () => {
|
|||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch('/SASjsApi/drive/file')
|
.patch('/SASjsApi/drive/file')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
.field('filePath', `/my/path/code-${generateTimestamp()}.sas`)
|
.field('filePath', `/my/path/code-3.sas`)
|
||||||
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
|
.attach('file', path.join(__dirname, 'files', 'sample.sas'))
|
||||||
.expect(403)
|
.expect(403)
|
||||||
|
|
||||||
@@ -427,9 +541,9 @@ describe('files', () => {
|
|||||||
const pathToUpload = '/my/path/code.exe'
|
const pathToUpload = '/my/path/code.exe'
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.patch(`/SASjsApi/drive/file?_filePath=${pathToUpload}`)
|
.patch('/SASjsApi/drive/file')
|
||||||
.auth(accessToken, { type: 'bearer' })
|
.auth(accessToken, { type: 'bearer' })
|
||||||
// .field('filePath', pathToUpload)
|
.query({ _filePath: pathToUpload })
|
||||||
.attach('file', fileToAttachPath)
|
.attach('file', fileToAttachPath)
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
@@ -483,6 +597,79 @@ describe('files', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('get', () => {
|
||||||
|
it('should get a SAS file on drive having _filePath as query param', async () => {
|
||||||
|
const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas')
|
||||||
|
const fileToCopyContent = await readFile(fileToCopyPath)
|
||||||
|
const filePath = '/my/path/code.sas'
|
||||||
|
|
||||||
|
const pathToCopy = path.join(
|
||||||
|
fileUtilModules.getTmpFilesFolderPath(),
|
||||||
|
filePath
|
||||||
|
)
|
||||||
|
await copy(fileToCopyPath, pathToCopy)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.get('/SASjsApi/drive/file')
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.query({ _filePath: filePath })
|
||||||
|
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
expect(res.text).toEqual(fileToCopyContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
|
const res = await request(app).get('/SASjsApi/drive/file').expect(401)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Unauthorized')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if file is not present', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get('/SASjsApi/drive/file')
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.query({ _filePath: `/my/path/code-4.sas` })
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(`Error: File doesn't exist.`)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Forbidden if filePath outside Drive', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get('/SASjsApi/drive/file')
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.query({ _filePath: '/../path/code.sas' })
|
||||||
|
.expect(403)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Error: Cannot get file outside drive.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should respond with Bad Request if filePath doesn't has correct extension", async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.patch('/SASjsApi/drive/file')
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.query({ _filePath: '/my/path/code.exe' })
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Invalid file extension')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request if filePath is missing', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/drive/file')
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(`"_filePath" is required`)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ const style = `<style>
|
|||||||
width: 150px;
|
width: 150px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 10px 10px 0 0;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.app-container .app img{
|
.app-container .app img{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
</style>`
|
</style>`
|
||||||
|
|
||||||
@@ -31,7 +31,10 @@ const singleAppStreamHtml = (
|
|||||||
logo?: string
|
logo?: string
|
||||||
) =>
|
) =>
|
||||||
` <a class="app" href="${streamServiceName}" title="${appLoc}">
|
` <a class="app" href="${streamServiceName}" title="${appLoc}">
|
||||||
<img src="${logo ? streamServiceName + '/' + logo : defaultAppLogo}" />
|
<img
|
||||||
|
src="${logo ? streamServiceName + '/' + logo : defaultAppLogo}"
|
||||||
|
onerror="this.src = '${defaultAppLogo}';"
|
||||||
|
/>
|
||||||
${streamServiceName}
|
${streamServiceName}
|
||||||
</a>`
|
</a>`
|
||||||
|
|
||||||
|
|||||||
@@ -40,17 +40,6 @@ export const publishAppStream = async (
|
|||||||
|
|
||||||
if (!streamServiceName) {
|
if (!streamServiceName) {
|
||||||
streamServiceName = `AppStreamName${appCount + 1}`
|
streamServiceName = `AppStreamName${appCount + 1}`
|
||||||
} else {
|
|
||||||
const alreadyDeployed = process.appStreamConfig[streamServiceName]
|
|
||||||
if (alreadyDeployed) {
|
|
||||||
if (alreadyDeployed.appLoc === appLoc) {
|
|
||||||
// redeploying to same streamServiceName
|
|
||||||
} else {
|
|
||||||
// trying to deploy to another existing streamServiceName
|
|
||||||
// assign new streamServiceName
|
|
||||||
streamServiceName = `${streamServiceName}-${appCount + 1}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
router.use(`/${streamServiceName}`, express.static(pathToDeployment))
|
router.use(`/${streamServiceName}`, express.static(pathToDeployment))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { AppStreamConfig } from '../types'
|
|||||||
import { getTmpAppStreamConfigPath } from './file'
|
import { getTmpAppStreamConfigPath } from './file'
|
||||||
|
|
||||||
export const loadAppStreamConfig = async () => {
|
export const loadAppStreamConfig = async () => {
|
||||||
|
if (process.env.NODE_ENV === 'test') return
|
||||||
|
|
||||||
const appStreamConfigPath = getTmpAppStreamConfigPath()
|
const appStreamConfigPath = getTmpAppStreamConfigPath()
|
||||||
|
|
||||||
const content = (await fileExists(appStreamConfigPath))
|
const content = (await fileExists(appStreamConfigPath))
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
import { getTmpMacrosPath, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.'
|
import { getTmpMacrosPath, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.'
|
||||||
|
|
||||||
export const copySASjsCore = async () => {
|
export const copySASjsCore = async () => {
|
||||||
|
if (process.env.NODE_ENV === 'test') return
|
||||||
|
|
||||||
console.log('Copying Macros from container to drive(tmp).')
|
console.log('Copying Macros from container to drive(tmp).')
|
||||||
|
|
||||||
const macrosDrivePath = getTmpMacrosPath()
|
const macrosDrivePath = getTmpMacrosPath()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export * from './parseLogToArray'
|
|||||||
export * from './removeTokensInDB'
|
export * from './removeTokensInDB'
|
||||||
export * from './saveTokensInDB'
|
export * from './saveTokensInDB'
|
||||||
export * from './setProcessVariables'
|
export * from './setProcessVariables'
|
||||||
|
export * from './setupFolders'
|
||||||
export * from './sleep'
|
export * from './sleep'
|
||||||
export * from './upload'
|
export * from './upload'
|
||||||
export * from './validation'
|
export * from './validation'
|
||||||
|
|||||||
7
api/src/utils/setupFolders.ts
Normal file
7
api/src/utils/setupFolders.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { createFolder } from '@sasjs/utils'
|
||||||
|
import { getTmpFilesFolderPath } from './file'
|
||||||
|
|
||||||
|
export const setupFolders = async () => {
|
||||||
|
const drivePath = getTmpFilesFolderPath()
|
||||||
|
await createFolder(drivePath)
|
||||||
|
}
|
||||||
@@ -98,6 +98,11 @@ export const fileParamValidation = (data: any): Joi.ValidationResult =>
|
|||||||
_filePath: filePathSchema
|
_filePath: filePathSchema
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
|
export const folderParamValidation = (data: any): Joi.ValidationResult =>
|
||||||
|
Joi.object({
|
||||||
|
_folderPath: Joi.string()
|
||||||
|
}).validate(data)
|
||||||
|
|
||||||
export const runSASValidation = (data: any): Joi.ValidationResult =>
|
export const runSASValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
code: Joi.string().required()
|
code: Joi.string().required()
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.42",
|
"version": "0.0.47",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.42",
|
"version": "0.0.47",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"standard-version": "^9.3.2"
|
"standard-version": "^9.3.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.42",
|
"version": "0.0.47",
|
||||||
"description": "NodeJS wrapper for calling the SAS binary executable",
|
"description": "NodeJS wrapper for calling the SAS binary executable",
|
||||||
"repository": "https://github.com/sasjs/server",
|
"repository": "https://github.com/sasjs/server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
### Get contents of folder
|
||||||
|
GET http://localhost:5000/SASjsApi/drive/folder?_path=/Public/app/react-seed-app/services/web
|
||||||
|
|
||||||
###
|
###
|
||||||
POST http://localhost:5000/SASjsApi/drive/deploy
|
POST http://localhost:5000/SASjsApi/drive/deploy
|
||||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJjbGllbnRJRDEiLCJ1c2VybmFtZSI6InVzZXJuYW1lMSIsImlzYWRtaW4iOmZhbHNlLCJpc2FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNjM1ODA0MDc2LCJleHAiOjE2MzU4OTA0NzZ9.Cx1F54ILgAUtnkit0Wg1K1YVO2RdNjOnTKdPhUtDm5I
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiJjbGllbnRJRDEiLCJ1c2VybmFtZSI6InVzZXJuYW1lMSIsImlzYWRtaW4iOmZhbHNlLCJpc2FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNjM1ODA0MDc2LCJleHAiOjE2MzU4OTA0NzZ9.Cx1F54ILgAUtnkit0Wg1K1YVO2RdNjOnTKdPhUtDm5I
|
||||||
|
|||||||
24
web/package-lock.json
generated
24
web/package-lock.json
generated
@@ -8472,9 +8472,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimist": {
|
"node_modules/minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||||
},
|
},
|
||||||
"node_modules/mkdirp": {
|
"node_modules/mkdirp": {
|
||||||
"version": "0.5.5",
|
"version": "0.5.5",
|
||||||
@@ -8581,9 +8581,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-forge": {
|
"node_modules/node-forge": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
||||||
"integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==",
|
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6.13.0"
|
"node": ">= 6.13.0"
|
||||||
@@ -17622,9 +17622,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
|
||||||
},
|
},
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.5",
|
"version": "0.5.5",
|
||||||
@@ -17715,9 +17715,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz",
|
||||||
"integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==",
|
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node-releases": {
|
"node-releases": {
|
||||||
|
|||||||
@@ -50,25 +50,9 @@ const Studio = () => {
|
|||||||
.map((logLine: any) => logLine.line)
|
.map((logLine: any) => logLine.line)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
|
|
||||||
setLog(`<div><h2>SAS Log</h2><pre>${parsedLog}</pre></div>`)
|
setLog(parsedLog)
|
||||||
|
|
||||||
let weboutString: string
|
setWebout(`${res.data?._webout}`)
|
||||||
try {
|
|
||||||
weboutString = res.data._webout
|
|
||||||
.split('>>weboutBEGIN<<')[1]
|
|
||||||
.split('>>weboutEND<<')[0]
|
|
||||||
} catch (_) {
|
|
||||||
weboutString = res?.data?._webout ?? ''
|
|
||||||
}
|
|
||||||
|
|
||||||
let webout: string
|
|
||||||
try {
|
|
||||||
webout = JSON.stringify(JSON.parse(weboutString), null, 4)
|
|
||||||
} catch (_) {
|
|
||||||
webout = weboutString
|
|
||||||
}
|
|
||||||
|
|
||||||
setWebout(`<pre><code>${webout}</code></pre>`)
|
|
||||||
setTab('2')
|
setTab('2')
|
||||||
|
|
||||||
// Scroll to bottom of log
|
// Scroll to bottom of log
|
||||||
@@ -100,6 +84,7 @@ const Studio = () => {
|
|||||||
}, [location.search])
|
}, [location.search])
|
||||||
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
@@ -152,17 +137,15 @@ const Studio = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value="2">
|
<TabPanel value="2">
|
||||||
<div
|
<div style={{ marginTop: '50px' }}>
|
||||||
id="sas_log"
|
<h2>SAS Log</h2>
|
||||||
style={{ marginTop: '50px' }}
|
<pre>{log}</pre>
|
||||||
dangerouslySetInnerHTML={{ __html: log }}
|
</div>
|
||||||
/>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value="3">
|
<TabPanel value="3">
|
||||||
<div
|
<div style={{ marginTop: '50px' }}>
|
||||||
style={{ marginTop: '50px' }}
|
<pre>{webout}</pre>
|
||||||
dangerouslySetInnerHTML={{ __html: webout }}
|
</div>
|
||||||
/>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabContext>
|
</TabContext>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user