1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Compare commits

..

10 Commits

Author SHA1 Message Date
semantic-release-bot
1fed5ea6ac chore(release): 0.1.0 [skip ci]
# [0.1.0](https://github.com/sasjs/server/compare/v0.0.77...v0.1.0) (2022-05-23)

### Bug Fixes

* issue174 + issue175 + issue146 ([80b33c7](80b33c7a18))
* **web:** click to copy + notification ([f37f8e9](f37f8e95d1))

### Features

* **env:** added new env variable LOG_FORMAT_MORGAN ([53bf68a](53bf68a6af))
2022-05-23 21:22:02 +00:00
Muhammad Saad
97f689f292 Merge pull request #177 from sasjs/issue174
fix: issue174 + issue175 + issue146
2022-05-23 14:17:25 -07:00
Saad Jutt
53bf68a6af feat(env): added new env variable LOG_FORMAT_MORGAN 2022-05-23 21:14:37 +05:00
Saad Jutt
f37f8e95d1 fix(web): click to copy + notification 2022-05-23 20:29:29 +05:00
Saad Jutt
80b33c7a18 fix: issue174 + issue175 + issue146 2022-05-23 19:24:56 +05:00
Muhammad Saad
b1803fe385 Merge pull request #170 from sasjs/dummy-release-command
chore: added dummy release command
2022-05-16 09:42:23 -07:00
Saad Jutt
7dd08c3b5b chore: added dummy release command 2022-05-16 21:36:00 +05:00
semantic-release-bot
b780b59b66 chore(release): 0.0.77 [skip ci]
## [0.0.77](https://github.com/sasjs/server/compare/v0.0.76...v0.0.77) (2022-05-16)

### Bug Fixes

* **release:** Github workflow without npm token ([c017d13](c017d13061))
2022-05-16 16:26:07 +00:00
Muhammad Saad
7b457eaec5 Merge pull request #169 from saadjutt01/main
Release on main update
2022-05-16 09:21:54 -07:00
Saad Jutt
c017d13061 fix(release): Github workflow without npm token 2022-05-16 21:17:53 +05:00
32 changed files with 9956 additions and 1268 deletions

View File

@@ -2,8 +2,8 @@ name: SASjs Server Executable Release
on: on:
push: push:
tags: branches:
- 'v*.*.*' - main
jobs: jobs:
release: release:
@@ -49,10 +49,11 @@ jobs:
zip macos.zip api-macos zip macos.zip api-macos
zip windows.zip api-win.exe zip windows.zip api-win.exe
- name: Install Semantic Release and plugins
run: |
npm i
npm i -g semantic-release
- name: Release - name: Release
uses: softprops/action-gh-release@v1 run: |
with: GITHUB_TOKEN=${{ secrets.GH_TOKEN }} semantic-release
files: |
./executables/linux.zip
./executables/macos.zip
./executables/windows.zip

43
.releaserc Normal file
View File

@@ -0,0 +1,43 @@
{
"branches": [
"main"
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
[
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md"
]
}
],
[
"@semantic-release/github",
{
"assets": [
{
"path": "./executables/linux.zip",
"label": "Linux Executable Binary"
},
{
"path": "./executables/macos.zip",
"label": "Macos Executable Binary"
},
{
"path": "./executables/windows.zip",
"label": "Windows Executable Binary"
}
]
}
],
[
"@semantic-release/exec",
{
"publishCmd": "echo 'publish command'"
}
]
]
}

View File

@@ -1,6 +1,22 @@
# Changelog # [0.1.0](https://github.com/sasjs/server/compare/v0.0.77...v0.1.0) (2022-05-23)
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.
### Bug Fixes
* issue174 + issue175 + issue146 ([80b33c7](https://github.com/sasjs/server/commit/80b33c7a18c1b7727316ffeca71658346733e935))
* **web:** click to copy + notification ([f37f8e9](https://github.com/sasjs/server/commit/f37f8e95d1a85e00ceca2413dbb5e1f3f3f72255))
### Features
* **env:** added new env variable LOG_FORMAT_MORGAN ([53bf68a](https://github.com/sasjs/server/commit/53bf68a6aff44bb7b2f40d40d6554809253a01a8))
## [0.0.77](https://github.com/sasjs/server/compare/v0.0.76...v0.0.77) (2022-05-16)
### Bug Fixes
* **release:** Github workflow without npm token ([c017d13](https://github.com/sasjs/server/commit/c017d13061d21aeacd0690367992d12ca57a115b))
### [0.0.76](https://github.com/sasjs/server/compare/v0.0.75...v0.0.76) (2022-05-16) ### [0.0.76](https://github.com/sasjs/server/compare/v0.0.75...v0.0.76) (2022-05-16)

View File

@@ -63,7 +63,7 @@ SAS_PATH=/path/to/sas/executable.exe
# Path to working directory # Path to working directory
# This location is for SAS WORK, staged files, DRIVE, configuration etc # This location is for SAS WORK, staged files, DRIVE, configuration etc
DRIVE_PATH=/tmp SASJS_ROOT=./sasjs_root
# options: [http|https] default: http # options: [http|https] default: http
PROTOCOL= PROTOCOL=
@@ -125,6 +125,10 @@ HELMET_COEP=
# } # }
HELMET_CSP_CONFIG_PATH=./csp.config.json HELMET_CSP_CONFIG_PATH=./csp.config.json
# LOG_FORMAT_MORGAN options: [combined|common|dev|short|tiny] default: `common`
# Docs: https://www.npmjs.com/package/morgan#predefined-formats
LOG_FORMAT_MORGAN=
``` ```
## Persisting the Session ## Persisting the Session
@@ -147,7 +151,7 @@ Install the npm package [pm2](https://www.npmjs.com/package/pm2) (`npm install p
```bash ```bash
export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas
export PORT=5001 export PORT=5001
export DRIVE_PATH=./tmp export SASJS_ROOT=./sasjs_root
pm2 start api-linux pm2 start api-linux
``` ```

View File

@@ -18,4 +18,6 @@ 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
SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas SAS_PATH=/opt/sas/sas9/SASHome/SASFoundation/9.4/sas
DRIVE_PATH=./tmp SASJS_ROOT=./sasjs_root
LOG_FORMAT_MORGAN=common

View File

@@ -94,9 +94,6 @@
"tsoa": "3.14.1", "tsoa": "3.14.1",
"typescript": "^4.3.2" "typescript": "^4.3.2"
}, },
"configuration": {
"sasPath": "/opt/sas/sas9/SASHome/SASFoundation/9.4/sas"
},
"nodemonConfig": { "nodemonConfig": {
"ignore": [ "ignore": [
"tmp/**/*" "tmp/**/*"

View File

@@ -12,25 +12,46 @@ import helmet from 'helmet'
import { import {
connectDB, connectDB,
copySASjsCore, copySASjsCore,
getWebBuildFolderPath, CorsType,
getWebBuildFolder,
HelmetCoepType,
instantiateLogger,
loadAppStreamConfig, loadAppStreamConfig,
ModeType,
ProtocolType,
ReturnCode,
setProcessVariables, setProcessVariables,
setupFolders setupFolders,
verifyEnvVariables
} from './utils' } from './utils'
import { getEnvCSPDirectives } from './utils/parseHelmetConfig' import { getEnvCSPDirectives } from './utils/parseHelmetConfig'
dotenv.config() dotenv.config()
instantiateLogger()
if (verifyEnvVariables()) {
process.exit(ReturnCode.InvalidEnv)
}
const app = express() const app = express()
app.use(cookieParser()) app.use(cookieParser())
app.use(morgan('tiny'))
const { MODE, CORS, WHITELIST, PROTOCOL, HELMET_CSP_CONFIG_PATH, HELMET_COEP } = const {
process.env MODE,
CORS,
WHITELIST,
PROTOCOL,
HELMET_CSP_CONFIG_PATH,
HELMET_COEP,
LOG_FORMAT_MORGAN
} = process.env
app.use(morgan(LOG_FORMAT_MORGAN as string))
export const cookieOptions = { export const cookieOptions = {
secure: PROTOCOL === 'https', secure: PROTOCOL === ProtocolType.HTTPS,
httpOnly: true, httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours maxAge: 24 * 60 * 60 * 1000 // 24 hours
} }
@@ -38,9 +59,8 @@ export const cookieOptions = {
const cspConfigJson: { [key: string]: string[] | null } = getEnvCSPDirectives( const cspConfigJson: { [key: string]: string[] | null } = getEnvCSPDirectives(
HELMET_CSP_CONFIG_PATH HELMET_CSP_CONFIG_PATH
) )
const coepFlag = if (PROTOCOL === ProtocolType.HTTP)
HELMET_COEP === 'true' || HELMET_COEP === undefined ? true : false cspConfigJson['upgrade-insecure-requests'] = null
if (PROTOCOL === 'http') cspConfigJson['upgrade-insecure-requests'] = null
/*********************************** /***********************************
* CSRF Protection * * CSRF Protection *
@@ -58,14 +78,14 @@ app.use(
...cspConfigJson ...cspConfigJson
} }
}, },
crossOriginEmbedderPolicy: coepFlag crossOriginEmbedderPolicy: HELMET_COEP === HelmetCoepType.TRUE
}) })
) )
/*********************************** /***********************************
* Enabling CORS * * Enabling CORS *
***********************************/ ***********************************/
if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') { if (MODE === ModeType.Server || CORS === CorsType.ENABLED) {
const whiteList: string[] = [] const whiteList: string[] = []
WHITELIST?.split(' ') WHITELIST?.split(' ')
?.filter((url) => !!url) ?.filter((url) => !!url)
@@ -84,7 +104,7 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') {
* Express Sessions * * Express Sessions *
* With Mongo Store * * With Mongo Store *
***********************************/ ***********************************/
if (MODE?.trim() === 'server') { if (MODE === ModeType.Server) {
let store: MongoStore | undefined let store: MongoStore | undefined
// NOTE: when exporting app.js as agent for supertest // NOTE: when exporting app.js as agent for supertest
@@ -129,7 +149,7 @@ export default setProcessVariables().then(async () => {
// should be served after setting up web route // should be served after setting up web route
// index.html needs to be injected with some js script. // index.html needs to be injected with some js script.
app.use(express.static(getWebBuildFolderPath())) app.use(express.static(getWebBuildFolder()))
app.use(onError) app.use(onError)

View File

@@ -32,7 +32,7 @@ import {
import { createFileTree, ExecutionController, getTreeExample } from './internal' import { createFileTree, ExecutionController, getTreeExample } from './internal'
import { TreeNode } from '../types' import { TreeNode } from '../types'
import { getTmpFilesFolderPath } from '../utils' import { getFilesFolder } from '../utils'
interface DeployPayload { interface DeployPayload {
appLoc: string appLoc: string
@@ -214,12 +214,12 @@ const getFileTree = () => {
} }
const deploy = async (data: DeployPayload) => { const deploy = async (data: DeployPayload) => {
const driveFilesPath = getTmpFilesFolderPath() const driveFilesPath = getFilesFolder()
const appLocParts = data.appLoc.replace(/^\//, '').split('/') const appLocParts = data.appLoc.replace(/^\//, '').split('/')
const appLocPath = path const appLocPath = path
.join(getTmpFilesFolderPath(), ...appLocParts) .join(getFilesFolder(), ...appLocParts)
.replace(new RegExp('/', 'g'), path.sep) .replace(new RegExp('/', 'g'), path.sep)
if (!appLocPath.includes(driveFilesPath)) { if (!appLocPath.includes(driveFilesPath)) {
@@ -238,10 +238,10 @@ const deploy = async (data: DeployPayload) => {
} }
const getFile = async (req: express.Request, filePath: string) => { const getFile = async (req: express.Request, filePath: string) => {
const driveFilesPath = getTmpFilesFolderPath() const driveFilesPath = getFilesFolder()
const filePathFull = path const filePathFull = path
.join(getTmpFilesFolderPath(), filePath) .join(getFilesFolder(), filePath)
.replace(new RegExp('/', 'g'), path.sep) .replace(new RegExp('/', 'g'), path.sep)
if (!filePathFull.includes(driveFilesPath)) { if (!filePathFull.includes(driveFilesPath)) {
@@ -261,11 +261,11 @@ const getFile = async (req: express.Request, filePath: string) => {
} }
const getFolder = async (folderPath?: string) => { const getFolder = async (folderPath?: string) => {
const driveFilesPath = getTmpFilesFolderPath() const driveFilesPath = getFilesFolder()
if (folderPath) { if (folderPath) {
const folderPathFull = path const folderPathFull = path
.join(getTmpFilesFolderPath(), folderPath) .join(getFilesFolder(), folderPath)
.replace(new RegExp('/', 'g'), path.sep) .replace(new RegExp('/', 'g'), path.sep)
if (!folderPathFull.includes(driveFilesPath)) { if (!folderPathFull.includes(driveFilesPath)) {
@@ -291,10 +291,10 @@ const getFolder = async (folderPath?: string) => {
} }
const deleteFile = async (filePath: string) => { const deleteFile = async (filePath: string) => {
const driveFilesPath = getTmpFilesFolderPath() const driveFilesPath = getFilesFolder()
const filePathFull = path const filePathFull = path
.join(getTmpFilesFolderPath(), filePath) .join(getFilesFolder(), filePath)
.replace(new RegExp('/', 'g'), path.sep) .replace(new RegExp('/', 'g'), path.sep)
if (!filePathFull.includes(driveFilesPath)) { if (!filePathFull.includes(driveFilesPath)) {
@@ -314,7 +314,7 @@ const saveFile = async (
filePath: string, filePath: string,
multerFile: Express.Multer.File multerFile: Express.Multer.File
): Promise<GetFileResponse> => { ): Promise<GetFileResponse> => {
const driveFilesPath = getTmpFilesFolderPath() const driveFilesPath = getFilesFolder()
const filePathFull = path const filePathFull = path
.join(driveFilesPath, filePath) .join(driveFilesPath, filePath)
@@ -339,7 +339,7 @@ const updateFile = async (
filePath: string, filePath: string,
multerFile: Express.Multer.File multerFile: Express.Multer.File
): Promise<GetFileResponse> => { ): Promise<GetFileResponse> => {
const driveFilesPath = getTmpFilesFolderPath() const driveFilesPath = getFilesFolder()
const filePathFull = path const filePathFull = path
.join(driveFilesPath, filePath) .join(driveFilesPath, filePath)

View File

@@ -12,8 +12,8 @@ import { PreProgramVars, Session, TreeNode } from '../../types'
import { import {
extractHeaders, extractHeaders,
generateFileUploadSasCode, generateFileUploadSasCode,
getTmpFilesFolderPath, getFilesFolder,
getTmpMacrosPath, getMacrosFolder,
HTTPHeaders, HTTPHeaders,
isDebugOn isDebugOn
} from '../../utils' } from '../../utils'
@@ -110,7 +110,7 @@ export class ExecutionController {
` `
program = ` program = `
options insert=(SASAUTOS="${getTmpMacrosPath()}"); options insert=(SASAUTOS="${getMacrosFolder()}");
/* runtime vars */ /* runtime vars */
${varStatments} ${varStatments}
@@ -191,7 +191,7 @@ ${program}`
const root: TreeNode = { const root: TreeNode = {
name: 'files', name: 'files',
relativePath: '', relativePath: '',
absolutePath: getTmpFilesFolderPath(), absolutePath: getFilesFolder(),
children: [] children: []
} }

View File

@@ -3,7 +3,7 @@ import { Session } from '../../types'
import { promisify } from 'util' import { promisify } from 'util'
import { execFile } from 'child_process' import { execFile } from 'child_process'
import { import {
getTmpSessionsFolderPath, getSessionsFolder,
generateUniqueFileName, generateUniqueFileName,
sysInitCompiledPath sysInitCompiledPath
} from '../../utils' } from '../../utils'
@@ -37,7 +37,7 @@ export class SessionController {
private async createSession(): Promise<Session> { private async createSession(): Promise<Session> {
const sessionId = generateUniqueFileName(generateTimestamp()) const sessionId = generateUniqueFileName(generateTimestamp())
const sessionFolder = path.join(getTmpSessionsFolderPath(), sessionId) const sessionFolder = path.join(getSessionsFolder(), sessionId)
const creationTimeStamp = sessionId.split('-').pop() as string const creationTimeStamp = sessionId.split('-').pop() as string
// death time of session is 15 mins from creation // death time of session is 15 mins from creation

View File

@@ -1,5 +1,5 @@
import path from 'path' import path from 'path'
import { getTmpFilesFolderPath } from '../../utils/file' import { getFilesFolder } from '../../utils/file'
import { import {
createFolder, createFolder,
createFile, createFile,
@@ -17,7 +17,7 @@ export const createFileTree = async (
parentFolders: string[] = [] parentFolders: string[] = []
) => { ) => {
const destinationPath = path.join( const destinationPath = path.join(
getTmpFilesFolderPath(), getFilesFolder(),
path.join(...parentFolders) path.join(...parentFolders)
) )

View File

@@ -19,7 +19,7 @@ import {
} from './internal' } from './internal'
import { import {
getPreProgramVariables, getPreProgramVariables,
getTmpFilesFolderPath, getFilesFolder,
HTTPHeaders, HTTPHeaders,
isDebugOn, isDebugOn,
LogLine, LogLine,
@@ -132,7 +132,7 @@ const executeReturnRaw = async (
const query = req.query as ExecutionVars const query = req.query as ExecutionVars
const sasCodePath = const sasCodePath =
path path
.join(getTmpFilesFolderPath(), _program) .join(getFilesFolder(), _program)
.replace(new RegExp('/', 'g'), path.sep) + '.sas' .replace(new RegExp('/', 'g'), path.sep) + '.sas'
try { try {
@@ -172,7 +172,7 @@ const executeReturnJson = async (
): Promise<ExecuteReturnJsonResponse> => { ): Promise<ExecuteReturnJsonResponse> => {
const sasCodePath = const sasCodePath =
path path
.join(getTmpFilesFolderPath(), _program) .join(getFilesFolder(), _program)
.replace(new RegExp('/', 'g'), path.sep) + '.sas' .replace(new RegExp('/', 'g'), path.sep) + '.sas'
const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null const filesNamesMap = req.files?.length ? makeFilesNamesMap(req.files) : null

View File

@@ -5,7 +5,7 @@ import { readFile } from '@sasjs/utils'
import User from '../model/User' import User from '../model/User'
import Client from '../model/Client' import Client from '../model/Client'
import { getWebBuildFolderPath, generateAuthCode } from '../utils' import { getWebBuildFolder, generateAuthCode } from '../utils'
import { InfoJWT } from '../types' import { InfoJWT } from '../types'
import { AuthController } from './auth' import { AuthController } from './auth'
@@ -63,7 +63,7 @@ export class WebController {
} }
const home = async () => { const home = async () => {
const indexHtmlPath = path.join(getWebBuildFolderPath(), 'index.html') const indexHtmlPath = path.join(getWebBuildFolder(), 'index.html')
// Attention! Cannot use fileExists here, // Attention! Cannot use fileExists here,
// due to limitation after building executable // due to limitation after building executable

View File

@@ -1,13 +1,13 @@
import path from 'path' import path from 'path'
import { Request } from 'express' import { Request } from 'express'
import multer, { FileFilterCallback, Options } from 'multer' import multer, { FileFilterCallback, Options } from 'multer'
import { blockFileRegex, getTmpUploadsPath } from '../utils' import { blockFileRegex, getUploadsFolder } from '../utils'
const fieldNameSize = 300 const fieldNameSize = 300
const fileSize = 104857600 // 100 MB const fileSize = 104857600 // 100 MB
const storage = multer.diskStorage({ const storage = multer.diskStorage({
destination: getTmpUploadsPath(), destination: getUploadsFolder(),
filename: function ( filename: function (
_req: Request, _req: Request,
file: Express.Multer.File, file: Express.Multer.File,

View File

@@ -21,17 +21,17 @@ import * as fileUtilModules from '../../../utils/file'
const timestamp = generateTimestamp() const timestamp = generateTimestamp()
const tmpFolder = path.join(process.cwd(), `tmp-${timestamp}`) const tmpFolder = path.join(process.cwd(), `tmp-${timestamp}`)
jest jest
.spyOn(fileUtilModules, 'getTmpFolderPath') .spyOn(fileUtilModules, 'getSasjsRootFolder')
.mockImplementation(() => tmpFolder) .mockImplementation(() => tmpFolder)
jest jest
.spyOn(fileUtilModules, 'getTmpUploadsPath') .spyOn(fileUtilModules, 'getUploadsFolder')
.mockImplementation(() => path.join(tmpFolder, 'uploads')) .mockImplementation(() => path.join(tmpFolder, 'uploads'))
import appPromise from '../../../app' import appPromise from '../../../app'
import { UserController } from '../../../controllers/' import { UserController } from '../../../controllers/'
import { getTreeExample } from '../../../controllers/internal' import { getTreeExample } from '../../../controllers/internal'
import { generateAccessToken, saveTokensInDB } from '../../../utils/' import { generateAccessToken, saveTokensInDB } from '../../../utils/'
const { getTmpFilesFolderPath } = fileUtilModules const { getFilesFolder } = fileUtilModules
const clientId = 'someclientID' const clientId = 'someclientID'
const user = { const user = {
@@ -157,10 +157,10 @@ describe('drive', () => {
expect(res.text).toEqual( expect(res.text).toEqual(
'{"status":"success","message":"Files deployed successfully to @sasjs/server."}' '{"status":"success","message":"Files deployed successfully to @sasjs/server."}'
) )
await expect(folderExists(getTmpFilesFolderPath())).resolves.toEqual(true) await expect(folderExists(getFilesFolder())).resolves.toEqual(true)
const testJobFolder = path.join( const testJobFolder = path.join(
getTmpFilesFolderPath(), getFilesFolder(),
'public', 'public',
'jobs', 'jobs',
'extract' 'extract'
@@ -174,7 +174,7 @@ describe('drive', () => {
await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code) await expect(readFile(testJobFile)).resolves.toEqual(exampleService.code)
await deleteFolder(path.join(getTmpFilesFolderPath(), 'public')) await deleteFolder(path.join(getFilesFolder(), 'public'))
}) })
}) })
@@ -192,7 +192,7 @@ describe('drive', () => {
}) })
it('should get a SAS folder on drive having _folderPath as query param', async () => { it('should get a SAS folder on drive having _folderPath as query param', async () => {
const pathToDrive = fileUtilModules.getTmpFilesFolderPath() const pathToDrive = fileUtilModules.getFilesFolder()
const dirLevel1 = 'level1' const dirLevel1 = 'level1'
const dirLevel2 = 'level2' const dirLevel2 = 'level2'
@@ -267,10 +267,7 @@ describe('drive', () => {
const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas') const fileToCopyPath = path.join(__dirname, 'files', 'sample.sas')
const filePath = '/my/path/code.sas' const filePath = '/my/path/code.sas'
const pathToCopy = path.join( const pathToCopy = path.join(fileUtilModules.getFilesFolder(), filePath)
fileUtilModules.getTmpFilesFolderPath(),
filePath
)
await copy(fileToCopyPath, pathToCopy) await copy(fileToCopyPath, pathToCopy)
const res = await request(app) const res = await request(app)
@@ -333,7 +330,7 @@ describe('drive', () => {
const pathToUpload = `/my/path/code-${generateTimestamp()}.sas` const pathToUpload = `/my/path/code-${generateTimestamp()}.sas`
const pathToCopy = path.join( const pathToCopy = path.join(
fileUtilModules.getTmpFilesFolderPath(), fileUtilModules.getFilesFolder(),
pathToUpload pathToUpload
) )
await copy(fileToAttachPath, pathToCopy) await copy(fileToAttachPath, pathToCopy)
@@ -445,7 +442,7 @@ describe('drive', () => {
const pathToUpload = '/my/path/code.sas' const pathToUpload = '/my/path/code.sas'
const pathToCopy = path.join( const pathToCopy = path.join(
fileUtilModules.getTmpFilesFolderPath(), fileUtilModules.getFilesFolder(),
pathToUpload pathToUpload
) )
await copy(fileToAttachPath, pathToCopy) await copy(fileToAttachPath, pathToCopy)
@@ -467,7 +464,7 @@ describe('drive', () => {
const pathToUpload = '/my/path/code.sas' const pathToUpload = '/my/path/code.sas'
const pathToCopy = path.join( const pathToCopy = path.join(
fileUtilModules.getTmpFilesFolderPath(), fileUtilModules.getFilesFolder(),
pathToUpload pathToUpload
) )
await copy(fileToAttachPath, pathToCopy) await copy(fileToAttachPath, pathToCopy)
@@ -603,10 +600,7 @@ describe('drive', () => {
const fileToCopyContent = await readFile(fileToCopyPath) const fileToCopyContent = await readFile(fileToCopyPath)
const filePath = '/my/path/code.sas' const filePath = '/my/path/code.sas'
const pathToCopy = path.join( const pathToCopy = path.join(fileUtilModules.getFilesFolder(), filePath)
fileUtilModules.getTmpFilesFolderPath(),
filePath
)
await copy(fileToCopyPath, pathToCopy) await copy(fileToCopyPath, pathToCopy)
const res = await request(app) const res = await request(app)

View File

@@ -2,7 +2,7 @@ import path from 'path'
import express from 'express' import express from 'express'
import { folderExists } from '@sasjs/utils' import { folderExists } from '@sasjs/utils'
import { addEntryToAppStreamConfig, getTmpFilesFolderPath } from '../../utils' import { addEntryToAppStreamConfig, getFilesFolder } from '../../utils'
import { appStreamHtml } from './appStreamHtml' import { appStreamHtml } from './appStreamHtml'
const router = express.Router() const router = express.Router()
@@ -22,7 +22,7 @@ export const publishAppStream = async (
streamLogo?: string, streamLogo?: string,
addEntryToFile: boolean = true addEntryToFile: boolean = true
) => { ) => {
const driveFilesPath = getTmpFilesFolderPath() const driveFilesPath = getFilesFolder()
const appLocParts = appLoc.replace(/^\//, '')?.split('/') const appLocParts = appLoc.replace(/^\//, '')?.split('/')
const appLocPath = path.join(driveFilesPath, ...appLocParts) const appLocPath = path.join(driveFilesPath, ...appLocParts)

View File

@@ -4,5 +4,6 @@ declare namespace NodeJS {
driveLoc: string driveLoc: string
sessionController?: import('../../controllers/internal').SessionController sessionController?: import('../../controllers/internal').SessionController
appStreamConfig: import('../').AppStreamConfig appStreamConfig: import('../').AppStreamConfig
logger: import('@sasjs/utils/logger').Logger
} }
} }

View File

@@ -2,12 +2,12 @@ import { createFile, fileExists, readFile } from '@sasjs/utils'
import { publishAppStream } from '../routes/appStream' import { publishAppStream } from '../routes/appStream'
import { AppStreamConfig } from '../types' import { AppStreamConfig } from '../types'
import { getTmpAppStreamConfigPath } from './file' import { getAppStreamConfigPath } from './file'
export const loadAppStreamConfig = async () => { export const loadAppStreamConfig = async () => {
if (process.env.NODE_ENV === 'test') return if (process.env.NODE_ENV === 'test') return
const appStreamConfigPath = getTmpAppStreamConfigPath() const appStreamConfigPath = getAppStreamConfigPath()
const content = (await fileExists(appStreamConfigPath)) const content = (await fileExists(appStreamConfigPath))
? await readFile(appStreamConfigPath) ? await readFile(appStreamConfigPath)
@@ -63,7 +63,7 @@ export const removeEntryFromAppStreamConfig = (streamServiceName: string) => {
} }
const saveAppStreamConfig = async () => { const saveAppStreamConfig = async () => {
const appStreamConfigPath = getTmpAppStreamConfigPath() const appStreamConfigPath = getAppStreamConfigPath()
try { try {
await createFile( await createFile(

View File

@@ -7,14 +7,14 @@ import {
readFile readFile
} from '@sasjs/utils' } from '@sasjs/utils'
import { getTmpMacrosPath, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.' import { getMacrosFolder, sasJSCoreMacros, sasJSCoreMacrosInfo } from '.'
export const copySASjsCore = async () => { export const copySASjsCore = async () => {
if (process.env.NODE_ENV === 'test') return 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 = getMacrosFolder()
await deleteFolder(macrosDrivePath) await deleteFolder(macrosDrivePath)
await createFolder(macrosDrivePath) await createFolder(macrosDrivePath)

View File

@@ -11,28 +11,26 @@ export const sysInitCompiledPath = path.join(
export const sasJSCoreMacros = path.join(apiRoot, 'sasjscore') export const sasJSCoreMacros = path.join(apiRoot, 'sasjscore')
export const sasJSCoreMacrosInfo = path.join(sasJSCoreMacros, '.macrolist') export const sasJSCoreMacrosInfo = path.join(sasJSCoreMacros, '.macrolist')
export const getWebBuildFolderPath = () => export const getWebBuildFolder = () => path.join(codebaseRoot, 'web', 'build')
path.join(codebaseRoot, 'web', 'build')
export const getTmpFolderPath = () => process.driveLoc export const getSasjsRootFolder = () => process.driveLoc
export const getTmpAppStreamConfigPath = () => export const getAppStreamConfigPath = () =>
path.join(getTmpFolderPath(), 'appStreamConfig.json') path.join(getSasjsRootFolder(), 'appStreamConfig.json')
export const getTmpMacrosPath = () => path.join(getTmpFolderPath(), 'sasjscore') export const getMacrosFolder = () =>
path.join(getSasjsRootFolder(), 'sasjscore')
export const getTmpUploadsPath = () => path.join(getTmpFolderPath(), 'uploads') export const getUploadsFolder = () => path.join(getSasjsRootFolder(), 'uploads')
export const getTmpFilesFolderPath = () => export const getFilesFolder = () => path.join(getSasjsRootFolder(), 'files')
path.join(getTmpFolderPath(), 'files')
export const getTmpLogFolderPath = () => path.join(getTmpFolderPath(), 'logs') export const getLogFolder = () => path.join(getSasjsRootFolder(), 'logs')
export const getTmpWeboutFolderPath = () => export const getWeboutFolder = () => path.join(getSasjsRootFolder(), 'webouts')
path.join(getTmpFolderPath(), 'webouts')
export const getTmpSessionsFolderPath = () => export const getSessionsFolder = () =>
path.join(getTmpFolderPath(), 'sessions') path.join(getSasjsRootFolder(), 'sessions')
export const generateUniqueFileName = (fileName: string, extension = '') => export const generateUniqueFileName = (fileName: string, extension = '') =>
[ [

View File

@@ -5,12 +5,12 @@ import { createFolder, fileExists, folderExists } from '@sasjs/utils'
const isWindows = () => process.platform === 'win32' const isWindows = () => process.platform === 'win32'
export const getDesktopFields = async () => { export const getDesktopFields = async () => {
const { SAS_PATH, DRIVE_PATH } = process.env const { SAS_PATH } = process.env
const sasLoc = SAS_PATH ?? (await getSASLocation()) const sasLoc = SAS_PATH ?? (await getSASLocation())
const driveLoc = DRIVE_PATH ?? (await getDriveLocation()) // const driveLoc = DRIVE_PATH ?? (await getDriveLocation())
return { sasLoc, driveLoc } return { sasLoc }
} }
const getDriveLocation = async (): Promise<string> => { const getDriveLocation = async (): Promise<string> => {

View File

@@ -9,6 +9,7 @@ export * from './generateRefreshToken'
export * from './getCertificates' export * from './getCertificates'
export * from './getDesktopFields' export * from './getDesktopFields'
export * from './getPreProgramVariables' export * from './getPreProgramVariables'
export * from './instantiateLogger'
export * from './isDebugOn' export * from './isDebugOn'
export * from './parseLogToArray' export * from './parseLogToArray'
export * from './removeTokensInDB' export * from './removeTokensInDB'
@@ -18,4 +19,5 @@ export * from './setProcessVariables'
export * from './setupFolders' export * from './setupFolders'
export * from './upload' export * from './upload'
export * from './validation' export * from './validation'
export * from './verifyEnvVariables'
export * from './verifyTokenInDB' export * from './verifyTokenInDB'

View File

@@ -0,0 +1,7 @@
import { LogLevel, Logger } from '@sasjs/utils/logger'
export const instantiateLogger = () => {
const logLevel = (process.env.LOG_LEVEL || LogLevel.Info) as LogLevel
const logger = new Logger(logLevel)
process.logger = logger
}

View File

@@ -1,30 +1,29 @@
import path from 'path' import path from 'path'
import { getAbsolutePath, getRealPath } from '@sasjs/utils' import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils'
import { configuration } from '../../package.json' import { getDesktopFields, ModeType } from '.'
import { getDesktopFields } from '.'
export const setProcessVariables = async () => { export const setProcessVariables = async () => {
if (process.env.NODE_ENV === 'test') { if (process.env.NODE_ENV === 'test') {
process.driveLoc = path.join(process.cwd(), 'tmp') process.driveLoc = path.join(process.cwd(), 'sasjs_root')
return return
} }
const { MODE } = process.env const { MODE } = process.env
if (MODE?.trim() === 'server') { if (MODE === ModeType.Server) {
const { SAS_PATH, DRIVE_PATH } = process.env process.sasLoc = process.env.SAS_PATH as string
process.sasLoc = SAS_PATH ?? configuration.sasPath
const absPath = getAbsolutePath(DRIVE_PATH ?? 'tmp', process.cwd())
process.driveLoc = getRealPath(absPath)
} else { } else {
const { sasLoc, driveLoc } = await getDesktopFields() const { sasLoc } = await getDesktopFields()
process.sasLoc = sasLoc process.sasLoc = sasLoc
process.driveLoc = driveLoc
} }
const { SASJS_ROOT } = process.env
const absPath = getAbsolutePath(SASJS_ROOT ?? 'sasjs_root', process.cwd())
await createFolder(absPath)
process.driveLoc = getRealPath(absPath)
console.log('sasLoc: ', process.sasLoc) console.log('sasLoc: ', process.sasLoc)
console.log('sasDrive: ', process.driveLoc) console.log('sasDrive: ', process.driveLoc)
} }

View File

@@ -1,7 +1,7 @@
import { createFolder } from '@sasjs/utils' import { createFolder } from '@sasjs/utils'
import { getTmpFilesFolderPath } from './file' import { getFilesFolder } from './file'
export const setupFolders = async () => { export const setupFolders = async () => {
const drivePath = getTmpFilesFolderPath() const drivePath = getFilesFolder()
await createFolder(drivePath) await createFolder(drivePath)
} }

View File

@@ -0,0 +1,211 @@
export enum ModeType {
Server = 'server',
Desktop = 'desktop'
}
export enum ProtocolType {
HTTP = 'http',
HTTPS = 'https'
}
export enum CorsType {
ENABLED = 'enable',
DISABLED = 'disable'
}
export enum HelmetCoepType {
TRUE = 'true',
FALSE = 'false'
}
export enum LOG_FORMAT_MORGANType {
Combined = 'combined',
Common = 'common',
Dev = 'dev',
Short = 'short',
tiny = 'tiny'
}
export enum ReturnCode {
Success,
InvalidEnv
}
export const verifyEnvVariables = (): ReturnCode => {
const errors: string[] = []
errors.push(...verifyMODE())
errors.push(...verifyPROTOCOL())
errors.push(...verifyPORT())
errors.push(...verifyCORS())
errors.push(...verifyHELMET_COEP())
errors.push(...verifyLOG_FORMAT_MORGAN())
if (errors.length) {
process.logger?.error(
`Invalid environment variable(s) provided: \n${errors.join('\n')}`
)
return ReturnCode.InvalidEnv
}
return ReturnCode.Success
}
const verifyMODE = (): string[] => {
const errors: string[] = []
const { MODE } = process.env
if (MODE) {
const modeTypes = Object.values(ModeType)
if (!modeTypes.includes(MODE as ModeType))
errors.push(`- MODE '${MODE}'\n - valid options ${modeTypes}`)
} else {
process.env.MODE = DEFAULTS.MODE
}
if (process.env.MODE === ModeType.Server) {
const {
ACCESS_TOKEN_SECRET,
REFRESH_TOKEN_SECRET,
AUTH_CODE_SECRET,
SESSION_SECRET,
DB_CONNECT
} = process.env
if (!ACCESS_TOKEN_SECRET)
errors.push(
`- ACCESS_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'`
)
if (!REFRESH_TOKEN_SECRET)
errors.push(
`- REFRESH_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'`
)
if (!AUTH_CODE_SECRET)
errors.push(
`- AUTH_CODE_SECRET is required for PROTOCOL '${ModeType.Server}'`
)
if (!SESSION_SECRET)
errors.push(
`- SESSION_SECRET is required for PROTOCOL '${ModeType.Server}'`
)
if (process.env.NODE_ENV !== 'test')
if (!DB_CONNECT)
errors.push(
`- DB_CONNECT is required for PROTOCOL '${ModeType.Server}'`
)
}
return errors
}
const verifyPROTOCOL = (): string[] => {
const errors: string[] = []
const { PROTOCOL } = process.env
if (PROTOCOL) {
const protocolTypes = Object.values(ProtocolType)
if (!protocolTypes.includes(PROTOCOL as ProtocolType))
errors.push(`- PROTOCOL '${PROTOCOL}'\n - valid options ${protocolTypes}`)
} else {
process.env.PROTOCOL = DEFAULTS.PROTOCOL
}
if (process.env.PROTOCOL === ProtocolType.HTTPS) {
const { PRIVATE_KEY, FULL_CHAIN } = process.env
if (!PRIVATE_KEY)
errors.push(
`- PRIVATE_KEY is required for PROTOCOL '${ProtocolType.HTTPS}'`
)
if (!FULL_CHAIN)
errors.push(
`- FULL_CHAIN is required for PROTOCOL '${ProtocolType.HTTPS}'`
)
}
return errors
}
const verifyCORS = (): string[] => {
const errors: string[] = []
const { CORS } = process.env
if (CORS) {
const corsTypes = Object.values(CorsType)
if (!corsTypes.includes(CORS as CorsType))
errors.push(`- CORS '${CORS}'\n - valid options ${corsTypes}`)
} else {
const { MODE } = process.env
process.env.CORS =
MODE === ModeType.Server ? CorsType.DISABLED : CorsType.ENABLED
}
return errors
}
const verifyPORT = (): string[] => {
const errors: string[] = []
const { PORT } = process.env
if (PORT) {
if (Number.isNaN(parseInt(PORT)))
errors.push(`- PORT '${PORT}'\n - should be a valid number`)
} else {
process.env.PORT = DEFAULTS.PORT
}
return errors
}
const verifyHELMET_COEP = (): string[] => {
const errors: string[] = []
const { HELMET_COEP } = process.env
if (HELMET_COEP) {
const helmetCoepTypes = Object.values(HelmetCoepType)
if (!helmetCoepTypes.includes(HELMET_COEP as HelmetCoepType))
errors.push(
`- HELMET_COEP '${HELMET_COEP}'\n - valid options ${helmetCoepTypes}`
)
HELMET_COEP
} else {
process.env.HELMET_COEP = DEFAULTS.HELMET_COEP
}
return errors
}
const verifyLOG_FORMAT_MORGAN = (): string[] => {
const errors: string[] = []
const { LOG_FORMAT_MORGAN } = process.env
if (LOG_FORMAT_MORGAN) {
const logFormatMorganTypes = Object.values(LOG_FORMAT_MORGANType)
if (
!logFormatMorganTypes.includes(LOG_FORMAT_MORGAN as LOG_FORMAT_MORGANType)
)
errors.push(
`- LOG_FORMAT_MORGAN '${LOG_FORMAT_MORGAN}'\n - valid options ${logFormatMorganTypes}`
)
LOG_FORMAT_MORGAN
} else {
process.env.LOG_FORMAT_MORGAN = DEFAULTS.LOG_FORMAT_MORGAN
}
return errors
}
const DEFAULTS = {
MODE: ModeType.Desktop,
PROTOCOL: ProtocolType.HTTP,
PORT: '5000',
HELMET_COEP: HelmetCoepType.TRUE,
LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common
}

10580
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@
"server": "npm run server:prepare && npm run server:start", "server": "npm run server:prepare && npm run server:start",
"server:prepare": "cd web && npm ci && npm run build && cd ../api && npm ci && npm run build && cd ..", "server:prepare": "cd web && npm ci && npm run build && cd ../api && npm ci && npm run build && cd ..",
"server:start": "cd api && npm run start:prod", "server:start": "cd api && npm run start:prod",
"release": "standard-version",
"lint-api:fix": "npx prettier --write \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "lint-api:fix": "npx prettier --write \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"lint-api": "npx prettier --check \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "lint-api": "npx prettier --check \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"lint-web:fix": "npx prettier --write \"web/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "lint-web:fix": "npx prettier --write \"web/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
@@ -16,7 +15,9 @@
"lint:fix": "npm run lint-api:fix && npm run lint-web:fix" "lint:fix": "npm run lint-api:fix && npm run lint-web:fix"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^2.3.1", "@semantic-release/changelog": "^6.0.1",
"standard-version": "^9.3.2" "@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^8.0.4"
} }
} }

90
web/package-lock.json generated
View File

@@ -24,9 +24,11 @@
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-webpack-plugin": "^7.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-monaco-editor": "^0.48.0", "react-monaco-editor": "^0.48.0",
"react-router-dom": "^5.3.0" "react-router-dom": "^5.3.0",
"react-toastify": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.0", "@babel/core": "^7.16.0",
@@ -38,6 +40,7 @@
"@types/dotenv-webpack": "^7.0.3", "@types/dotenv-webpack": "^7.0.3",
"@types/prismjs": "^1.16.6", "@types/prismjs": "^1.16.6",
"@types/react": "^17.0.37", "@types/react": "^17.0.37",
"@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.1", "@types/react-router-dom": "^5.3.1",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
@@ -3296,6 +3299,15 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"node_modules/@types/react-copy-to-clipboard": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz",
"integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-dom": { "node_modules/@types/react-dom": {
"version": "17.0.11", "version": "17.0.11",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz",
@@ -4863,6 +4875,14 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true "dev": true
}, },
"node_modules/copy-to-clipboard": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
"integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
"dependencies": {
"toggle-selection": "^1.0.6"
}
},
"node_modules/copy-webpack-plugin": { "node_modules/copy-webpack-plugin": {
"version": "10.2.4", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz",
@@ -9299,6 +9319,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"dependencies": {
"copy-to-clipboard": "^3.3.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": "^15.3.0 || 16 || 17 || 18"
}
},
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -9372,6 +9404,18 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/react-toastify": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.1.tgz",
"integrity": "sha512-c2zeZHkCX+WXuItS/JRqQ/8CH8Qm/je+M0rt09xe9fnu5YPJigtNOdD8zX4fwLA093V2am3abkGfOowwpkrwOQ==",
"dependencies": {
"clsx": "^1.1.1"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-transition-group": { "node_modules/react-transition-group": {
"version": "4.4.2", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
@@ -10335,6 +10379,11 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
},
"node_modules/toidentifier": { "node_modules/toidentifier": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
@@ -13641,6 +13690,15 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"@types/react-copy-to-clipboard": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz",
"integrity": "sha512-O29AThfxrkUFRsZXjfSWR2yaWo0ppB1yLEnHA+Oh24oNetjBAwTDu1PmolIqdJKzsZiO4J1jn6R6TmO96uBvGg==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-dom": { "@types/react-dom": {
"version": "17.0.11", "version": "17.0.11",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz",
@@ -14848,6 +14906,14 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true "dev": true
}, },
"copy-to-clipboard": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
"integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
"requires": {
"toggle-selection": "^1.0.6"
}
},
"copy-webpack-plugin": { "copy-webpack-plugin": {
"version": "10.2.4", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz",
@@ -18162,6 +18228,15 @@
"object-assign": "^4.1.1" "object-assign": "^4.1.1"
} }
}, },
"react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"requires": {
"copy-to-clipboard": "^3.3.1",
"prop-types": "^15.8.1"
}
},
"react-dom": { "react-dom": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -18223,6 +18298,14 @@
"tiny-warning": "^1.0.0" "tiny-warning": "^1.0.0"
} }
}, },
"react-toastify": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.0.1.tgz",
"integrity": "sha512-c2zeZHkCX+WXuItS/JRqQ/8CH8Qm/je+M0rt09xe9fnu5YPJigtNOdD8zX4fwLA093V2am3abkGfOowwpkrwOQ==",
"requires": {
"clsx": "^1.1.1"
}
},
"react-transition-group": { "react-transition-group": {
"version": "4.4.2", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
@@ -18967,6 +19050,11 @@
"is-number": "^7.0.0" "is-number": "^7.0.0"
} }
}, },
"toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
},
"toidentifier": { "toidentifier": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",

View File

@@ -23,9 +23,11 @@
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-webpack-plugin": "^7.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-monaco-editor": "^0.48.0", "react-monaco-editor": "^0.48.0",
"react-router-dom": "^5.3.0" "react-router-dom": "^5.3.0",
"react-toastify": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.0", "@babel/core": "^7.16.0",
@@ -37,6 +39,7 @@
"@types/dotenv-webpack": "^7.0.3", "@types/dotenv-webpack": "^7.0.3",
"@types/prismjs": "^1.16.6", "@types/prismjs": "^1.16.6",
"@types/react": "^17.0.37", "@types/react": "^17.0.37",
"@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.1", "@types/react-router-dom": "^5.3.1",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",

View File

@@ -24,7 +24,9 @@ const Header = (props: any) => {
const history = useHistory() const history = useHistory()
const { pathname } = useLocation() const { pathname } = useLocation()
const appContext = useContext(AppContext) const appContext = useContext(AppContext)
const [tabValue, setTabValue] = useState(pathname) const [tabValue, setTabValue] = useState(
pathname === '/SASjsLogon' ? '/' : pathname
)
const [anchorEl, setAnchorEl] = useState< const [anchorEl, setAnchorEl] = useState<
(EventTarget & HTMLButtonElement) | null (EventTarget & HTMLButtonElement) | null
>(null) >(null)

View File

@@ -1,15 +1,18 @@
import axios from 'axios' import axios from 'axios'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { ToastContainer, toast } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { CssBaseline, Box, Typography } from '@mui/material' import { CssBaseline, Box, Typography, Button } from '@mui/material'
const getAuthCode = async (credentials: any) => const getAuthCode = async (credentials: any) =>
axios.post('/SASLogon/authorize', credentials).then((res) => res.data) axios.post('/SASLogon/authorize', credentials).then((res) => res.data)
const AuthCode = () => { const AuthCode = () => {
const location = useLocation() const location = useLocation()
const [displayCode, setDisplayCode] = useState(null) const [displayCode, setDisplayCode] = useState('')
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
useEffect(() => { useEffect(() => {
@@ -56,6 +59,20 @@ const AuthCode = () => {
{errorMessage && <Typography>{errorMessage}</Typography>} {errorMessage && <Typography>{errorMessage}</Typography>}
<br /> <br />
<CopyToClipboard
text={displayCode}
onCopy={() =>
toast.info('Code copied to ClipBoard', {
theme: 'dark',
position: toast.POSITION.BOTTOM_RIGHT
})
}
>
<Button variant="contained">Copy to Clipboard</Button>
</CopyToClipboard>
<ToastContainer />
</Box> </Box>
) )
} }