mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 03:34:35 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e486fda69 | ||
|
|
79cac53fdb | ||
|
|
450d99f06e | ||
|
|
51ee8c0825 | ||
|
|
a1151606f2 | ||
|
|
38193c83dd | ||
|
|
59ecc36f2b | ||
|
|
8bc459c9a7 | ||
|
|
f1f1e47f76 | ||
|
|
679e9de245 | ||
|
|
f0ac996b3c | ||
|
|
2d77222ae8 | ||
|
|
e6e5a5fd64 | ||
|
|
e1eb04494a | ||
|
|
b7fa8e5f80 | ||
|
|
ef4fae4496 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
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.15](https://github.com/sasjs/server/compare/v0.0.14...v0.0.15) (2022-01-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **studio:** web component updated ([2d77222](https://github.com/sasjs/server/commit/2d77222ae8a139acd9d96466d0e68291c4ebd70e))
|
||||||
|
* updated route for sas code ([e1eb044](https://github.com/sasjs/server/commit/e1eb04494a5650726c95990f74fc719eced4ccb5))
|
||||||
|
* **web:** autosave and autofocus ([51ee8c0](https://github.com/sasjs/server/commit/51ee8c0825f021d1d67b2d765d5b434cbf248a1f))
|
||||||
|
* **web:** parsing of webout ([a115160](https://github.com/sasjs/server/commit/a1151606f21e0007e2b1ca1245d592d96866f62a))
|
||||||
|
* **web:** sticky tabs on Studio + extra run code button removed ([450d99f](https://github.com/sasjs/server/commit/450d99f06e5929eb1679e6203284e4faa44e19b0))
|
||||||
|
|
||||||
### [0.0.14](https://github.com/sasjs/server/compare/v0.0.13...v0.0.14) (2021-12-19)
|
### [0.0.14](https://github.com/sasjs/server/compare/v0.0.13...v0.0.14) (2021-12-19)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,16 @@ components:
|
|||||||
- clientSecret
|
- clientSecret
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
ExecuteSASCodePayload:
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 'Code of SAS program'
|
||||||
|
example: '* SAS Code HERE;'
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
MemberType.folder:
|
MemberType.folder:
|
||||||
enum:
|
enum:
|
||||||
- folder
|
- folder
|
||||||
@@ -358,16 +368,6 @@ components:
|
|||||||
- description
|
- description
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
RunSASPayload:
|
|
||||||
properties:
|
|
||||||
code:
|
|
||||||
type: string
|
|
||||||
description: 'Code of SAS program'
|
|
||||||
example: '* SAS Code HERE;'
|
|
||||||
required:
|
|
||||||
- code
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
ExecuteReturnJsonResponse:
|
ExecuteReturnJsonResponse:
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
@@ -511,6 +511,30 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ClientPayload'
|
$ref: '#/components/schemas/ClientPayload'
|
||||||
|
/SASjsApi/code/execute:
|
||||||
|
post:
|
||||||
|
operationId: ExecuteSASCode
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: 'Execute SAS code.'
|
||||||
|
summary: 'Run SAS Code and returns log'
|
||||||
|
tags:
|
||||||
|
- CODE
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ExecuteSASCodePayload'
|
||||||
/SASjsApi/drive/deploy:
|
/SASjsApi/drive/deploy:
|
||||||
post:
|
post:
|
||||||
operationId: Deploy
|
operationId: Deploy
|
||||||
@@ -982,6 +1006,26 @@ paths:
|
|||||||
format: double
|
format: double
|
||||||
type: number
|
type: number
|
||||||
example: '6789'
|
example: '6789'
|
||||||
|
/SASjsApi/session:
|
||||||
|
get:
|
||||||
|
operationId: Session
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value: {id: 123, username: johnusername, displayName: John}
|
||||||
|
summary: 'Get session info (username).'
|
||||||
|
tags:
|
||||||
|
- Session
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
parameters: []
|
||||||
/SASjsApi/stp/execute:
|
/SASjsApi/stp/execute:
|
||||||
get:
|
get:
|
||||||
operationId: ExecuteReturnRaw
|
operationId: ExecuteReturnRaw
|
||||||
@@ -1037,50 +1081,6 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
||||||
/SASjsApi/stp/run:
|
|
||||||
post:
|
|
||||||
operationId: RunSAS
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
description: 'Trigger a SAS program.'
|
|
||||||
summary: 'Run SAS Program, return raw content'
|
|
||||||
tags:
|
|
||||||
- STP
|
|
||||||
security:
|
|
||||||
-
|
|
||||||
bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/RunSASPayload'
|
|
||||||
/SASjsApi/session:
|
|
||||||
get:
|
|
||||||
operationId: Session
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UserResponse'
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value: {id: 123, username: johnusername, displayName: John}
|
|
||||||
summary: 'Get session info (username).'
|
|
||||||
tags:
|
|
||||||
- Session
|
|
||||||
security:
|
|
||||||
-
|
|
||||||
bearerAuth: []
|
|
||||||
parameters: []
|
|
||||||
servers:
|
servers:
|
||||||
-
|
-
|
||||||
url: /
|
url: /
|
||||||
@@ -1106,3 +1106,6 @@ tags:
|
|||||||
-
|
-
|
||||||
name: STP
|
name: STP
|
||||||
description: 'Operations about STP'
|
description: 'Operations about STP'
|
||||||
|
-
|
||||||
|
name: CODE
|
||||||
|
description: 'Operations on SAS code'
|
||||||
|
|||||||
63
api/src/controllers/code.ts
Normal file
63
api/src/controllers/code.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import { Request, Security, Route, Tags, Post, Body } from 'tsoa'
|
||||||
|
import { ExecutionController } from './internal'
|
||||||
|
import { PreProgramVars } from '../types'
|
||||||
|
|
||||||
|
interface ExecuteSASCodePayload {
|
||||||
|
/**
|
||||||
|
* Code of SAS program
|
||||||
|
* @example "* SAS Code HERE;"
|
||||||
|
*/
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Security('bearerAuth')
|
||||||
|
@Route('SASjsApi/code')
|
||||||
|
@Tags('CODE')
|
||||||
|
export class CodeController {
|
||||||
|
/**
|
||||||
|
* Execute SAS code.
|
||||||
|
* @summary Run SAS Code and returns log
|
||||||
|
*/
|
||||||
|
@Post('/execute')
|
||||||
|
public async executeSASCode(
|
||||||
|
@Request() request: express.Request,
|
||||||
|
@Body() body: ExecuteSASCodePayload
|
||||||
|
): Promise<string> {
|
||||||
|
return executeSASCode(request, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const executeSASCode = async (req: any, { code }: ExecuteSASCodePayload) => {
|
||||||
|
try {
|
||||||
|
const result = await new ExecutionController().executeProgram(
|
||||||
|
code,
|
||||||
|
getPreProgramVariables(req),
|
||||||
|
{ ...req.query, _debug: 131 },
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
return result as string
|
||||||
|
} catch (err: any) {
|
||||||
|
throw {
|
||||||
|
code: 400,
|
||||||
|
status: 'failure',
|
||||||
|
message: 'Job execution failed.',
|
||||||
|
error: typeof err === 'object' ? err.toString() : err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPreProgramVariables = (req: any): PreProgramVars => {
|
||||||
|
const host = req.get('host')
|
||||||
|
const protocol = req.protocol + '://'
|
||||||
|
const { user, accessToken } = req
|
||||||
|
return {
|
||||||
|
username: user.username,
|
||||||
|
userId: user.userId,
|
||||||
|
displayName: user.displayName,
|
||||||
|
serverUrl: protocol + host,
|
||||||
|
accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
export * from './auth'
|
export * from './auth'
|
||||||
export * from './client'
|
export * from './client'
|
||||||
|
export * from './code'
|
||||||
export * from './drive'
|
export * from './drive'
|
||||||
export * from './group'
|
export * from './group'
|
||||||
|
export * from './session'
|
||||||
export * from './stp'
|
export * from './stp'
|
||||||
export * from './user'
|
export * from './user'
|
||||||
export * from './session'
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export class SessionController {
|
|||||||
|
|
||||||
// TODO: don't wait forever
|
// TODO: don't wait forever
|
||||||
while ((await fileExists(codeFilePath)) && !session.crashed) {}
|
while ((await fileExists(codeFilePath)) && !session.crashed) {}
|
||||||
console.log('session crashed?', !!session.crashed, session.crashed)
|
console.log('session crashed?', !!session.crashed, session.crashed || '')
|
||||||
|
|
||||||
session.ready = true
|
session.ready = true
|
||||||
return Promise.resolve(session)
|
return Promise.resolve(session)
|
||||||
|
|||||||
@@ -5,13 +5,6 @@ import { ExecutionController } from './internal'
|
|||||||
import { PreProgramVars } from '../types'
|
import { PreProgramVars } from '../types'
|
||||||
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
|
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
|
||||||
|
|
||||||
interface RunSASPayload {
|
|
||||||
/**
|
|
||||||
* Code of SAS program
|
|
||||||
* @example "* SAS Code HERE;"
|
|
||||||
*/
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
interface ExecuteReturnJsonPayload {
|
interface ExecuteReturnJsonPayload {
|
||||||
/**
|
/**
|
||||||
* Location of SAS program
|
* Location of SAS program
|
||||||
@@ -48,18 +41,6 @@ export class STPController {
|
|||||||
return executeReturnRaw(request, _program)
|
return executeReturnRaw(request, _program)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger a SAS program.
|
|
||||||
* @summary Run SAS Program, return raw content
|
|
||||||
*/
|
|
||||||
@Post('/run')
|
|
||||||
public async runSAS(
|
|
||||||
@Request() request: express.Request,
|
|
||||||
@Body() body: RunSASPayload
|
|
||||||
): Promise<string> {
|
|
||||||
return runSAS(request, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger a SAS program using it's location in the _program parameter.
|
* Trigger a SAS program using it's location in the _program parameter.
|
||||||
* Enable debugging using the _debug parameter.
|
* Enable debugging using the _debug parameter.
|
||||||
@@ -109,25 +90,6 @@ const executeReturnRaw = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const runSAS = async (req: any, { code }: RunSASPayload) => {
|
|
||||||
try {
|
|
||||||
const result = await new ExecutionController().executeProgram(
|
|
||||||
code,
|
|
||||||
getPreProgramVariables(req),
|
|
||||||
req.query
|
|
||||||
)
|
|
||||||
|
|
||||||
return result as string
|
|
||||||
} catch (err: any) {
|
|
||||||
throw {
|
|
||||||
code: 400,
|
|
||||||
status: 'failure',
|
|
||||||
message: 'Job execution failed.',
|
|
||||||
error: typeof err === 'object' ? err.toString() : err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const executeReturnJson = async (
|
const executeReturnJson = async (
|
||||||
req: any,
|
req: any,
|
||||||
_program: string
|
_program: string
|
||||||
|
|||||||
25
api/src/routes/api/code.ts
Normal file
25
api/src/routes/api/code.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import { runSASValidation } from '../../utils'
|
||||||
|
import { CodeController } from '../../controllers/'
|
||||||
|
|
||||||
|
const runRouter = express.Router()
|
||||||
|
|
||||||
|
const controller = new CodeController()
|
||||||
|
|
||||||
|
runRouter.post('/execute', async (req, res) => {
|
||||||
|
const { error, value: body } = runSASValidation(req.body)
|
||||||
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await controller.executeSASCode(req, body)
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default runRouter
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
|
|
||||||
import driveRouter from './drive'
|
import driveRouter from './drive'
|
||||||
import stpRouter from './stp'
|
import stpRouter from './stp'
|
||||||
|
import codeRouter from './code'
|
||||||
import userRouter from './user'
|
import userRouter from './user'
|
||||||
import groupRouter from './group'
|
import groupRouter from './group'
|
||||||
import clientRouter from './client'
|
import clientRouter from './client'
|
||||||
@@ -31,6 +32,7 @@ router.use(
|
|||||||
router.use('/drive', authenticateAccessToken, driveRouter)
|
router.use('/drive', authenticateAccessToken, driveRouter)
|
||||||
router.use('/group', desktopRestrict, groupRouter)
|
router.use('/group', desktopRestrict, groupRouter)
|
||||||
router.use('/stp', authenticateAccessToken, stpRouter)
|
router.use('/stp', authenticateAccessToken, stpRouter)
|
||||||
|
router.use('/code', authenticateAccessToken, codeRouter)
|
||||||
router.use('/user', desktopRestrict, userRouter)
|
router.use('/user', desktopRestrict, userRouter)
|
||||||
router.use(
|
router.use(
|
||||||
'/',
|
'/',
|
||||||
|
|||||||
@@ -24,22 +24,6 @@ stpRouter.get('/execute', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
stpRouter.post('/run', async (req, res) => {
|
|
||||||
const { error, value: body } = runSASValidation(req.body)
|
|
||||||
if (error) return res.status(400).send(error.details[0].message)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await controller.runSAS(req, body)
|
|
||||||
res.send(response)
|
|
||||||
} catch (err: any) {
|
|
||||||
const statusCode = err.code
|
|
||||||
|
|
||||||
delete err.code
|
|
||||||
|
|
||||||
res.status(statusCode).send(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
stpRouter.post(
|
stpRouter.post(
|
||||||
'/execute',
|
'/execute',
|
||||||
fileUploadController.preuploadMiddleware,
|
fileUploadController.preuploadMiddleware,
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import { getRealPath } from '@sasjs/utils'
|
|||||||
export const connectDB = async () => {
|
export const connectDB = async () => {
|
||||||
// NOTE: when exporting app.js as agent for supertest
|
// NOTE: when exporting app.js as agent for supertest
|
||||||
// we should exlcude connecting to the real database
|
// we should exlcude connecting to the real database
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
process.driveLoc = path.join(process.cwd(), 'tmp')
|
||||||
|
return
|
||||||
|
} else {
|
||||||
const { MODE } = process.env
|
const { MODE } = process.env
|
||||||
|
|
||||||
if (MODE?.trim() !== 'server') {
|
if (MODE?.trim() !== 'server') {
|
||||||
|
|||||||
@@ -38,6 +38,10 @@
|
|||||||
{
|
{
|
||||||
"name": "STP",
|
"name": "STP",
|
||||||
"description": "Operations about STP"
|
"description": "Operations about STP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CODE",
|
||||||
|
"description": "Operations on SAS code"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"yaml": true,
|
"yaml": true,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ services:
|
|||||||
- ./web:/usr/server/web
|
- ./web:/usr/server/web
|
||||||
|
|
||||||
mongodb:
|
mongodb:
|
||||||
image: mongo:latest
|
image: mongo:5.0.4
|
||||||
ports:
|
ports:
|
||||||
- 27017:27017
|
- 27017:27017
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.0.14",
|
"version": "0.0.15",
|
||||||
"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.14",
|
"version": "0.0.15",
|
||||||
"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": {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const Home = () => {
|
|||||||
and contributions are welcomed.
|
and contributions are welcomed.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
SASjs Server is maintained by the SASjs Apps team -{' '}
|
SASjs Server is maintained by the SAS Apps team -{' '}
|
||||||
<a
|
<a
|
||||||
href="https://sasapps.io/contact-us"
|
href="https://sasapps.io/contact-us"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -2,17 +2,37 @@ import React, { useEffect, useRef, useState } from 'react'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import { Button, Paper, Stack, Toolbar } from '@mui/material'
|
import { Button, Paper, Stack, Tab } from '@mui/material'
|
||||||
import Editor from '@monaco-editor/react'
|
import { makeStyles } from '@mui/styles'
|
||||||
|
import Editor, { OnMount } from '@monaco-editor/react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { TabContext, TabList, TabPanel } from '@mui/lab'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(() => ({
|
||||||
|
root: {
|
||||||
|
fontSize: '1rem',
|
||||||
|
color: 'gray',
|
||||||
|
'&.Mui-selected': {
|
||||||
|
color: 'black'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
const Studio = () => {
|
const Studio = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const [fileContent, setFileContent] = useState('')
|
const [fileContent, setFileContent] = useState('')
|
||||||
const [log, setLog] = useState('')
|
const [log, setLog] = useState('')
|
||||||
|
const [webout, setWebout] = useState('')
|
||||||
|
const [tab, setTab] = React.useState('1')
|
||||||
|
const handleTabChange = (_e: any, newValue: string) => {
|
||||||
|
setTab(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
const editorRef = useRef(null)
|
const editorRef = useRef(null as any)
|
||||||
const handleEditorDidMount = (editor: any) => (editorRef.current = editor)
|
const handleEditorDidMount: OnMount = (editor) => {
|
||||||
|
editor.focus()
|
||||||
|
editorRef.current = editor
|
||||||
|
}
|
||||||
|
|
||||||
const getSelection = () => {
|
const getSelection = () => {
|
||||||
const editor = editorRef.current as any
|
const editor = editorRef.current as any
|
||||||
@@ -20,25 +40,47 @@ const Studio = () => {
|
|||||||
return selection ?? ''
|
return selection ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRunSelectionBtnClick = () => runCode(getSelection())
|
const handleRunBtnClick = () => runCode(getSelection() || fileContent)
|
||||||
|
|
||||||
const handleRunBtnClick = () => runCode(fileContent)
|
|
||||||
|
|
||||||
const runCode = (code: string) => {
|
const runCode = (code: string) => {
|
||||||
axios
|
axios
|
||||||
.post(`/SASjsApi/stp/run`, { code })
|
.post(`/SASjsApi/code/execute`, { code })
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
const data =
|
setLog(`<div><h2>SAS Log</h2><pre>${res?.data?.log}</pre></div>`)
|
||||||
typeof res.data === 'string'
|
|
||||||
? res.data
|
|
||||||
: `<pre><code>${JSON.stringify(res.data, null, 4)}</code></pre>`
|
|
||||||
|
|
||||||
setLog(data)
|
let weboutString: string
|
||||||
document?.getElementById('sas_log')?.scrollIntoView()
|
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')
|
||||||
})
|
})
|
||||||
.catch((err) => console.log(err))
|
.catch((err) => console.log(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const content = localStorage.getItem('fileContent') ?? ''
|
||||||
|
setFileContent(content)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fileContent.length) {
|
||||||
|
localStorage.setItem('fileContent', fileContent)
|
||||||
|
}
|
||||||
|
}, [fileContent])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(location.search)
|
const params = new URLSearchParams(location.search)
|
||||||
const programPath = params.get('_program')
|
const programPath = params.get('_program')
|
||||||
@@ -50,48 +92,74 @@ const Studio = () => {
|
|||||||
.catch((err) => console.log(err))
|
.catch((err) => console.log(err))
|
||||||
}, [location.search])
|
}, [location.search])
|
||||||
|
|
||||||
|
const classes = useStyles()
|
||||||
return (
|
return (
|
||||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
<>
|
||||||
<Toolbar />
|
<br />
|
||||||
<Paper
|
<br />
|
||||||
sx={{
|
<br />
|
||||||
height: '75vh',
|
<Box sx={{ width: '100%', typography: 'body1' }}>
|
||||||
padding: '10px',
|
<TabContext value={tab}>
|
||||||
overflow: 'auto',
|
<Box
|
||||||
position: 'relative'
|
sx={{
|
||||||
}}
|
borderBottom: 1,
|
||||||
elevation={3}
|
borderColor: 'divider'
|
||||||
>
|
}}
|
||||||
<Editor
|
style={{ position: 'fixed', background: 'white', width: '100%' }}
|
||||||
height="95%"
|
>
|
||||||
value={fileContent}
|
<TabList onChange={handleTabChange} centered>
|
||||||
onMount={handleEditorDidMount}
|
<Tab className={classes.root} label="Code" value="1" />
|
||||||
onChange={(val) => {
|
<Tab className={classes.root} label="Log" value="2" />
|
||||||
if (val) setFileContent(val)
|
<Tab className={classes.root} label="Webout" value="3" />
|
||||||
}}
|
</TabList>
|
||||||
/>
|
</Box>
|
||||||
</Paper>
|
<TabPanel value="1">
|
||||||
<Stack
|
{/* <Toolbar /> */}
|
||||||
spacing={3}
|
<Paper
|
||||||
direction="row"
|
sx={{
|
||||||
sx={{ justifyContent: 'center', marginTop: '20px' }}
|
height: '70vh',
|
||||||
>
|
marginTop: '50px',
|
||||||
<Button variant="contained" onClick={handleRunBtnClick}>
|
padding: '10px',
|
||||||
Run SAS Code
|
overflow: 'auto',
|
||||||
</Button>
|
position: 'relative'
|
||||||
<Button variant="contained" onClick={handleRunSelectionBtnClick}>
|
}}
|
||||||
Run Selected SAS Code
|
elevation={3}
|
||||||
</Button>
|
>
|
||||||
</Stack>
|
<Editor
|
||||||
{log && (
|
height="95%"
|
||||||
<>
|
value={fileContent}
|
||||||
<br />
|
onMount={handleEditorDidMount}
|
||||||
<h2 id="sas_log">Output</h2>
|
onChange={(val) => {
|
||||||
<br />
|
if (val) setFileContent(val)
|
||||||
<div dangerouslySetInnerHTML={{ __html: log }} />
|
}}
|
||||||
</>
|
/>
|
||||||
)}
|
</Paper>
|
||||||
</Box>
|
<Stack
|
||||||
|
spacing={3}
|
||||||
|
direction="row"
|
||||||
|
sx={{ justifyContent: 'center', marginTop: '20px' }}
|
||||||
|
>
|
||||||
|
<Button variant="contained" onClick={handleRunBtnClick}>
|
||||||
|
Run SAS Code
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="2">
|
||||||
|
<div
|
||||||
|
id="sas_log"
|
||||||
|
style={{ marginTop: '50px' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: log }}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="3">
|
||||||
|
<div
|
||||||
|
style={{ marginTop: '50px' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: webout }}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
</TabContext>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user