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

Compare commits

..

31 Commits

Author SHA1 Message Date
semantic-release-bot
70655e74d3 chore(release): 0.19.0 [skip ci]
# [0.19.0](https://github.com/sasjs/server/compare/v0.18.0...v0.19.0) (2022-09-05)

### Features

* added mocking endpoints ([0a0ba2c](0a0ba2cca5))
2022-09-05 12:21:34 +00:00
Allan Bowe
cb82fea0d8 Merge pull request #264 from sasjs/mocker
Mocker
2022-09-05 13:16:10 +01:00
b9a596616d chore: cleanup 2022-09-05 12:20:56 +02:00
semantic-release-bot
72a5393be3 chore(release): 0.18.0 [skip ci]
# [0.18.0](https://github.com/sasjs/server/compare/v0.17.5...v0.18.0) (2022-09-02)

### Features

* add option for program launch in context menu ([ee2db27](ee2db276bb))
2022-09-02 19:26:49 +00:00
Allan Bowe
769a840e9f Merge pull request #273 from sasjs/issue-270
feat: add option for program launch in context menu
2022-09-02 20:23:02 +01:00
730c7c52ac chore: remove commented code 2022-09-03 00:09:48 +05:00
ee2db276bb feat: add option for program launch in context menu 2022-09-02 23:40:02 +05:00
semantic-release-bot
d0a24aacb6 chore(release): 0.17.5 [skip ci]
## [0.17.5](https://github.com/sasjs/server/compare/v0.17.4...v0.17.5) (2022-09-02)

### Bug Fixes

* SASINITIALFOLDER split over 2 params, closes [#271](https://github.com/sasjs/server/issues/271) ([393b5ea](393b5eaf99))
2022-09-02 18:08:49 +00:00
Allan Bowe
57dfdf89a4 Merge pull request #272 from sasjs/allanbowe/session-crashed-since-271
fix: SASINITIALFOLDER split over 2 params, closes #271
2022-09-02 19:03:37 +01:00
Allan Bowe
393b5eaf99 fix: SASINITIALFOLDER split over 2 params, closes #271 2022-09-02 17:59:10 +00:00
Saad Jutt
7477326b22 chore: lower cased env values 2022-09-01 23:38:04 +05:00
Saad Jutt
76bf84316e chore: MOCK_SERVERTYPE instead of string literals 2022-09-01 23:34:57 +05:00
semantic-release-bot
e355276e44 chore(release): 0.17.4 [skip ci]
## [0.17.4](https://github.com/sasjs/server/compare/v0.17.3...v0.17.4) (2022-09-01)

### Bug Fixes

* invalid JS logic ([9f06080](9f06080348))
2022-09-01 12:50:14 +00:00
Allan Bowe
a3a9e3bd9f Merge pull request #269 from sasjs/allanbowe/error-unrecognized-sas-267
fix: invalid JS logic
2022-09-01 13:41:52 +01:00
Allan Bowe
9f06080348 fix: invalid JS logic 2022-09-01 12:35:58 +00:00
semantic-release-bot
4bbf9cfdb3 chore(release): 0.17.3 [skip ci]
## [0.17.3](https://github.com/sasjs/server/compare/v0.17.2...v0.17.3) (2022-09-01)

### Bug Fixes

* making SASINITIALFOLDER option windows only.  Closes [#267](https://github.com/sasjs/server/issues/267) ([e63271a](e63271a67a))
2022-09-01 12:25:33 +00:00
Allan Bowe
e8e71fcde9 Merge pull request #268 from sasjs/allanbowe/error-unrecognized-sas-267
fix: making SASINITIALFOLDER option windows only.  Closes #267
2022-09-01 13:21:25 +01:00
Allan Bowe
e63271a67a fix: making SASINITIALFOLDER option windows only. Closes #267 2022-09-01 12:18:53 +00:00
7633608318 chore: mocker architecture fix, env validation 2022-08-31 13:31:28 +02:00
semantic-release-bot
e67d27d264 chore(release): 0.17.2 [skip ci]
## [0.17.2](https://github.com/sasjs/server/compare/v0.17.1...v0.17.2) (2022-08-31)

### Bug Fixes

* addition of SASINITIALFOLDER startup option.  Closes [#260](https://github.com/sasjs/server/issues/260) ([a5ee2f2](a5ee2f2923))
2022-08-31 09:35:51 +00:00
Allan Bowe
53033ccc96 Merge pull request #262 from sasjs/allanbowe/sas-default-folder-should-260
fix: addition of SASINITIALFOLDER startup option.  Closes #260
2022-08-31 10:32:14 +01:00
semantic-release-bot
6131ed1cbe chore(release): 0.17.1 [skip ci]
## [0.17.1](https://github.com/sasjs/server/compare/v0.17.0...v0.17.1) (2022-08-30)

### Bug Fixes

* typo mistake ([ee17d37](ee17d37aa1))
2022-08-30 18:15:33 +00:00
Allan Bowe
5d624e3399 Merge pull request #266 from sasjs/issue-265
fix: typo mistake
2022-08-30 19:10:47 +01:00
ee17d37aa1 fix: typo mistake 2022-08-30 22:42:11 +05:00
572fe22d50 chore: mocksas9 controller 2022-08-30 17:27:37 +02:00
091268bf58 chore: mocking only mandatory bits from sas9 responses 2022-08-29 12:40:29 +02:00
71a4a48443 chore: generic sas9 mock responses 2022-08-29 10:30:01 +02:00
3b188cd724 style: lint 2022-08-26 18:03:28 +02:00
eeba2328c0 chore: added login, logout endpoints 2022-08-26 17:59:07 +02:00
0a0ba2cca5 feat: added mocking endpoints 2022-08-25 15:58:08 +02:00
Allan Bowe
a5ee2f2923 fix: addition of SASINITIALFOLDER startup option. Closes #260 2022-08-19 15:20:36 +00:00
17 changed files with 2281 additions and 1983 deletions

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@ node_modules/
.env* .env*
sas/ sas/
sasjs_root/ sasjs_root/
api/mocks/custom/*
!api/mocks/custom/.keep
tmp/ tmp/
build/ build/
sasjsbuild/ sasjsbuild/

View File

@@ -1,3 +1,52 @@
# [0.19.0](https://github.com/sasjs/server/compare/v0.18.0...v0.19.0) (2022-09-05)
### Features
* added mocking endpoints ([0a0ba2c](https://github.com/sasjs/server/commit/0a0ba2cca5db867de46fb2486d856a84ec68d3b4))
# [0.18.0](https://github.com/sasjs/server/compare/v0.17.5...v0.18.0) (2022-09-02)
### Features
* add option for program launch in context menu ([ee2db27](https://github.com/sasjs/server/commit/ee2db276bb0bbd522f758e0b66f7e7b2f4afd9d5))
## [0.17.5](https://github.com/sasjs/server/compare/v0.17.4...v0.17.5) (2022-09-02)
### Bug Fixes
* SASINITIALFOLDER split over 2 params, closes [#271](https://github.com/sasjs/server/issues/271) ([393b5ea](https://github.com/sasjs/server/commit/393b5eaf990049c39eecf2b9e8dd21a001b6e298))
## [0.17.4](https://github.com/sasjs/server/compare/v0.17.3...v0.17.4) (2022-09-01)
### Bug Fixes
* invalid JS logic ([9f06080](https://github.com/sasjs/server/commit/9f06080348aed076f8188a26fb4890d38a5a3510))
## [0.17.3](https://github.com/sasjs/server/compare/v0.17.2...v0.17.3) (2022-09-01)
### Bug Fixes
* making SASINITIALFOLDER option windows only. Closes [#267](https://github.com/sasjs/server/issues/267) ([e63271a](https://github.com/sasjs/server/commit/e63271a67a0deb3059a5f2bec1854efee5a6e5a5))
## [0.17.2](https://github.com/sasjs/server/compare/v0.17.1...v0.17.2) (2022-08-31)
### Bug Fixes
* addition of SASINITIALFOLDER startup option. Closes [#260](https://github.com/sasjs/server/issues/260) ([a5ee2f2](https://github.com/sasjs/server/commit/a5ee2f292384f90e9d95d003d652311c0d91a7a7))
## [0.17.1](https://github.com/sasjs/server/compare/v0.17.0...v0.17.1) (2022-08-30)
### Bug Fixes
* typo mistake ([ee17d37](https://github.com/sasjs/server/commit/ee17d37aa188b0ca43cea0e89d6cd1a566b765cb))
# [0.17.0](https://github.com/sasjs/server/compare/v0.16.1...v0.17.0) (2022-08-25) # [0.17.0](https://github.com/sasjs/server/compare/v0.16.1...v0.17.0) (2022-08-25)

View File

@@ -96,6 +96,9 @@ PROTOCOL=
# default: 5000 # default: 5000
PORT= PORT=
# options: [sas9|sasviya]
# If not present, mocking function is disabled
MOCK_SERVERTYPE=
# #
## Additional SAS Options ## Additional SAS Options

0
api/mocks/custom/.keep Normal file
View File

View File

@@ -0,0 +1 @@
You have signed in.

View File

@@ -0,0 +1 @@
You have signed out.

View File

@@ -0,0 +1,30 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" dir="ltr" class="bg">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1" />
</head>
<div class="content">
<form id="credentials" class="minimal" action="/SASLogon/login?service=http%3A%2F%2Flocalhost:5004%2FSASStoredProcess%2Fj_spring_cas_security_check" method="post">
<!--form container-->
<input type="hidden" name="lt" value="LT-8-WGkt9EXwICBihaVbxGc92opjufTK1D" aria-hidden="true" />
<input type="hidden" name="execution" value="e2s1" aria-hidden="true" />
<input type="hidden" name="_eventId" value="submit" aria-hidden="true" />
<span class="userid">
<input id="username" name="username" tabindex="3" aria-labelledby="username1 message1 message2 message3" name="username" placeholder="User ID" type="text" autofocus="true" value="" maxlength="500" autocomplete="off" />
</span>
<span class="password">
<input id="password" name="password" tabindex="4" name="password" placeholder="Password" type="password" value="" maxlength="500" autocomplete="off" />
</span>
<button type="submit" class="btn-submit" title="Sign In" tabindex="5" onClick="this.disabled=true;setSubmitUrl(this.form);this.form.submit();return false;">Sign In</button>
</form>
</div>
</html>

View File

@@ -0,0 +1 @@
"title": "Log Off SAS Demo User"

View File

@@ -85,8 +85,10 @@ components:
type: string type: string
_webout: _webout:
anyOf: anyOf:
- type: string -
- $ref: '#/components/schemas/IRecordOfAny' type: string
-
$ref: '#/components/schemas/IRecordOfAny'
log: log:
items: items:
$ref: '#/components/schemas/LogLine' $ref: '#/components/schemas/LogLine'
@@ -106,6 +108,7 @@ components:
enum: enum:
- sas - sas
- js - js
- py
type: string type: string
ExecuteCodePayload: ExecuteCodePayload:
properties: properties:
@@ -135,9 +138,12 @@ components:
members: members:
items: items:
anyOf: anyOf:
- $ref: '#/components/schemas/FolderMember' -
- $ref: '#/components/schemas/ServiceMember' $ref: '#/components/schemas/FolderMember'
- $ref: '#/components/schemas/FileMember' -
$ref: '#/components/schemas/ServiceMember'
-
$ref: '#/components/schemas/FileMember'
type: array type: array
required: required:
- name - name
@@ -186,9 +192,12 @@ components:
members: members:
items: items:
anyOf: anyOf:
- $ref: '#/components/schemas/FolderMember' -
- $ref: '#/components/schemas/ServiceMember' $ref: '#/components/schemas/FolderMember'
- $ref: '#/components/schemas/FileMember' -
$ref: '#/components/schemas/ServiceMember'
-
$ref: '#/components/schemas/FileMember'
type: array type: array
required: required:
- members - members
@@ -374,7 +383,7 @@ components:
autoExec: autoExec:
type: string type: string
description: 'User-specific auto-exec code' description: 'User-specific auto-exec code'
example: '' example: ""
required: required:
- displayName - displayName
- username - username
@@ -423,27 +432,13 @@ components:
- description - description
type: object type: object
additionalProperties: false additionalProperties: false
_LeanDocument__LeanDocument_T__: FlattenMaps_T_:
properties: {} properties: {}
type: object type: object
Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__:
properties:
_id:
$ref: '#/components/schemas/_LeanDocument__LeanDocument_T__'
description: 'This documents _id.'
__v:
description: 'This documents __v.'
id:
description: 'The string version of this documents _id.'
type: object
description: 'From T, pick a set of properties whose keys are in the union K'
Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_:
$ref: '#/components/schemas/Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__'
description: 'Construct a type with the properties of T except for those in type K.'
LeanDocument_this_:
$ref: '#/components/schemas/Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_'
IGroup: IGroup:
$ref: '#/components/schemas/LeanDocument_this_' $ref: '#/components/schemas/FlattenMaps_T_'
ObjectId:
type: string
InfoResponse: InfoResponse:
properties: properties:
mode: mode:
@@ -623,11 +618,7 @@ paths:
$ref: '#/components/schemas/TokenResponse' $ref: '#/components/schemas/TokenResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString}
{
accessToken: someRandomCryptoString,
refreshToken: someRandomCryptoString
}
summary: 'Accepts client/auth code and returns access/refresh tokens' summary: 'Accepts client/auth code and returns access/refresh tokens'
tags: tags:
- Auth - Auth
@@ -651,16 +642,13 @@ paths:
$ref: '#/components/schemas/TokenResponse' $ref: '#/components/schemas/TokenResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {accessToken: someRandomCryptoString, refreshToken: someRandomCryptoString}
{
accessToken: someRandomCryptoString,
refreshToken: someRandomCryptoString
}
summary: 'Returns new access/refresh tokens' summary: 'Returns new access/refresh tokens'
tags: tags:
- Auth - Auth
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/auth/logout: /SASjsApi/auth/logout:
post: post:
@@ -672,7 +660,8 @@ paths:
tags: tags:
- Auth - Auth
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/client: /SASjsApi/client:
post: post:
@@ -686,16 +675,13 @@ paths:
$ref: '#/components/schemas/ClientPayload' $ref: '#/components/schemas/ClientPayload'
examples: examples:
'Example 1': 'Example 1':
value: value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString}
{
clientId: someFormattedClientID1234,
clientSecret: someRandomCryptoString
}
summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.' summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.'
tags: tags:
- Client - Client
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -718,7 +704,8 @@ paths:
tags: tags:
- CODE - CODE
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -738,11 +725,7 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {status: success, message: 'Files deployed successfully to @sasjs/server.'}
{
status: success,
message: 'Files deployed successfully to @sasjs/server.'
}
'400': '400':
description: 'Invalid Format' description: 'Invalid Format'
content: content:
@@ -751,11 +734,7 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {status: failure, message: 'Provided not supported data format.'}
{
status: failure,
message: 'Provided not supported data format.'
}
'500': '500':
description: 'Execution Error' description: 'Execution Error'
content: content:
@@ -769,7 +748,8 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -789,11 +769,7 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {status: success, message: 'Files deployed successfully to @sasjs/server.'}
{
status: success,
message: 'Files deployed successfully to @sasjs/server.'
}
'400': '400':
description: 'Invalid Format' description: 'Invalid Format'
content: content:
@@ -802,11 +778,7 @@ paths:
$ref: '#/components/schemas/DeployResponse' $ref: '#/components/schemas/DeployResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {status: failure, message: 'Provided not supported data format.'}
{
status: failure,
message: 'Provided not supported data format.'
}
'500': '500':
description: 'Execution Error' description: 'Execution Error'
content: content:
@@ -821,7 +793,8 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -845,9 +818,11 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- in: query -
in: query
name: _filePath name: _filePath
required: true required: true
schema: schema:
@@ -870,9 +845,11 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- in: query -
in: query
name: _filePath name: _filePath
required: true required: true
schema: schema:
@@ -904,9 +881,11 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: 'Location of file' -
description: 'Location of file'
in: query in: query
name: _filePath name: _filePath
required: false required: false
@@ -940,7 +919,7 @@ paths:
'Example 1': 'Example 1':
value: {status: success} value: {status: success}
'403': '403':
description: '' description: ""
content: content:
application/json: application/json:
schema: schema:
@@ -953,9 +932,11 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: 'Location of SAS program' -
description: 'Location of SAS program'
in: query in: query
name: _filePath name: _filePath
required: false required: false
@@ -996,9 +977,11 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- in: query -
in: query
name: _folderPath name: _folderPath
required: false required: false
schema: schema:
@@ -1021,9 +1004,11 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- in: query -
in: query
name: _folderPath name: _folderPath
required: true required: true
schema: schema:
@@ -1049,13 +1034,13 @@ paths:
$ref: '#/components/schemas/FileFolderResponse' $ref: '#/components/schemas/FileFolderResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {status: failure, message: 'Add folder request failed.'}
{ status: failure, message: 'Add folder request failed.' }
summary: 'Create an empty folder in SASjs Drive' summary: 'Create an empty folder in SASjs Drive'
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1089,7 +1074,8 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1111,7 +1097,8 @@ paths:
tags: tags:
- Drive - Drive
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/user: /SASjsApi/user:
get: get:
@@ -1127,26 +1114,13 @@ paths:
type: array type: array
examples: examples:
'Example 1': 'Example 1':
value: value: [{id: 123, username: johnusername, displayName: John, isAdmin: false}, {id: 456, username: starkusername, displayName: Stark, isAdmin: true}]
[
{
id: 123,
username: johnusername,
displayName: John,
isAdmin: false
},
{
id: 456,
username: starkusername,
displayName: Stark,
isAdmin: true
}
]
summary: 'Get list of all users (username, displayname). All users can request this.' summary: 'Get list of all users (username, displayname). All users can request this.'
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
post: post:
operationId: CreateUser operationId: CreateUser
@@ -1159,19 +1133,13 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse' $ref: '#/components/schemas/UserDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
{
id: 1234,
displayName: 'John Snow',
username: johnSnow01,
isAdmin: false,
isActive: true
}
summary: 'Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.' summary: 'Create user with the following attributes: UserId, UserName, Password, isAdmin, isActive. Admin only task.'
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1194,9 +1162,11 @@ paths:
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The User's username" -
description: 'The User''s username'
in: path in: path
name: username name: username
required: true required: true
@@ -1214,21 +1184,16 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse' $ref: '#/components/schemas/UserDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
{
id: 1234,
displayName: 'John Snow',
username: johnSnow01,
isAdmin: false,
isActive: true
}
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.' summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The User's username" -
description: 'The User''s username'
in: path in: path
name: username name: username
required: true required: true
@@ -1250,9 +1215,11 @@ paths:
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The User's username" -
description: 'The User''s username'
in: path in: path
name: username name: username
required: true required: true
@@ -1283,9 +1250,11 @@ paths:
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The user's identifier" -
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1304,21 +1273,16 @@ paths:
$ref: '#/components/schemas/UserDetailsResponse' $ref: '#/components/schemas/UserDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {id: 1234, displayName: 'John Snow', username: johnSnow01, isAdmin: false, isActive: true}
{
id: 1234,
displayName: 'John Snow',
username: johnSnow01,
isAdmin: false,
isActive: true
}
summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.' summary: 'Update user properties - such as displayName. Can be performed either by admins, or the user in question.'
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The user's identifier" -
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1341,9 +1305,11 @@ paths:
tags: tags:
- User - User
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The user's identifier" -
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1374,19 +1340,13 @@ paths:
type: array type: array
examples: examples:
'Example 1': 'Example 1':
value: value: [{groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users'}]
[
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users'
}
]
summary: 'Get list of all groups (groupName and groupDescription). All users can request this.' summary: 'Get list of all groups (groupName and groupDescription). All users can request this.'
tags: tags:
- Group - Group
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
post: post:
operationId: CreateGroup operationId: CreateGroup
@@ -1399,19 +1359,13 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse' $ref: '#/components/schemas/GroupDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
summary: 'Create a new group. Admin only.' summary: 'Create a new group. Admin only.'
tags: tags:
- Group - Group
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1433,9 +1387,11 @@ paths:
tags: tags:
- Group - Group
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The group's name" -
description: 'The group''s name'
in: path in: path
name: name name: name
required: true required: true
@@ -1455,9 +1411,11 @@ paths:
tags: tags:
- Group - Group
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The group's identifier" -
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1475,14 +1433,16 @@ paths:
schema: schema:
allOf: allOf:
- {$ref: '#/components/schemas/IGroup'} - {$ref: '#/components/schemas/IGroup'}
- { properties: { _id: {} }, required: [_id], type: object } - {properties: {_id: {$ref: '#/components/schemas/ObjectId'}}, required: [_id], type: object}
summary: 'Delete a group. Admin task only.' summary: 'Delete a group. Admin task only.'
tags: tags:
- Group - Group
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The group's identifier" -
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1502,21 +1462,16 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse' $ref: '#/components/schemas/GroupDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
summary: 'Add a user to a group. Admin task only.' summary: 'Add a user to a group. Admin task only.'
tags: tags:
- Group - Group
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The group's identifier" -
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1524,7 +1479,8 @@ paths:
format: double format: double
type: number type: number
example: '1234' example: '1234'
- description: "The user's identifier" -
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1543,21 +1499,16 @@ paths:
$ref: '#/components/schemas/GroupDetailsResponse' $ref: '#/components/schemas/GroupDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {groupId: 123, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}
{
groupId: 123,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
summary: 'Remove a user to a group. Admin task only.' summary: 'Remove a user to a group. Admin task only.'
tags: tags:
- Group - Group
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The group's identifier" -
description: 'The group''s identifier'
in: path in: path
name: groupId name: groupId
required: true required: true
@@ -1565,7 +1516,8 @@ paths:
format: double format: double
type: number type: number
example: '1234' example: '1234'
- description: "The user's identifier" -
description: 'The user''s identifier'
in: path in: path
name: userId name: userId
required: true required: true
@@ -1585,14 +1537,7 @@ paths:
$ref: '#/components/schemas/InfoResponse' $ref: '#/components/schemas/InfoResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {mode: desktop, cors: enable, whiteList: ['http://example.com', 'http://example2.com'], protocol: http, runTimes: [sas, js]}
{
mode: desktop,
cors: enable,
whiteList: ['http://example.com', 'http://example2.com'],
protocol: http,
runTimes: [sas, js]
}
summary: 'Get server info (mode, cors, whiteList, protocol).' summary: 'Get server info (mode, cors, whiteList, protocol).'
tags: tags:
- Info - Info
@@ -1630,42 +1575,14 @@ paths:
type: array type: array
examples: examples:
'Example 1': 'Example 1':
value: value: [{permissionId: 123, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}, {permissionId: 124, path: /SASjsApi/code/execute, type: Route, setting: Grant, group: {groupId: 1, name: DCGroup, description: 'This group represents Data Controller Users', isActive: true, users: []}}]
[
{
permissionId: 123,
path: /SASjsApi/code/execute,
type: Route,
setting: Grant,
user:
{
id: 1,
username: johnSnow01,
displayName: 'John Snow',
isAdmin: false
}
},
{
permissionId: 124,
path: /SASjsApi/code/execute,
type: Route,
setting: Grant,
group:
{
groupId: 1,
name: DCGroup,
description: 'This group represents Data Controller Users',
isActive: true,
users: []
}
}
]
description: "Get the list of permission rules applicable the authenticated user.\nIf the user is an admin, all rules are returned." description: "Get the list of permission rules applicable the authenticated user.\nIf the user is an admin, all rules are returned."
summary: 'Get the list of permission rules. If the user is admin, all rules are returned.' summary: 'Get the list of permission rules. If the user is admin, all rules are returned.'
tags: tags:
- Permission - Permission
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
post: post:
operationId: CreatePermission operationId: CreatePermission
@@ -1678,25 +1595,13 @@ paths:
$ref: '#/components/schemas/PermissionDetailsResponse' $ref: '#/components/schemas/PermissionDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {permissionId: 123, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}
{
permissionId: 123,
path: /SASjsApi/code/execute,
type: Route,
setting: Grant,
user:
{
id: 1,
username: johnSnow01,
displayName: 'John Snow',
isAdmin: false
}
}
summary: 'Create a new permission. Admin only.' summary: 'Create a new permission. Admin only.'
tags: tags:
- Permission - Permission
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
requestBody: requestBody:
required: true required: true
@@ -1716,27 +1621,16 @@ paths:
$ref: '#/components/schemas/PermissionDetailsResponse' $ref: '#/components/schemas/PermissionDetailsResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {permissionId: 123, path: /SASjsApi/code/execute, type: Route, setting: Grant, user: {id: 1, username: johnSnow01, displayName: 'John Snow', isAdmin: false}}
{
permissionId: 123,
path: /SASjsApi/code/execute,
type: Route,
setting: Grant,
user:
{
id: 1,
username: johnSnow01,
displayName: 'John Snow',
isAdmin: false
}
}
summary: 'Update permission setting. Admin only' summary: 'Update permission setting. Admin only'
tags: tags:
- Permission - Permission
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The permission's identifier" -
description: 'The permission''s identifier'
in: path in: path
name: permissionId name: permissionId
required: true required: true
@@ -1759,9 +1653,11 @@ paths:
tags: tags:
- Permission - Permission
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: "The user's identifier" -
description: 'The user''s identifier'
in: path in: path
name: permissionId name: permissionId
required: true required: true
@@ -1781,18 +1677,13 @@ paths:
$ref: '#/components/schemas/UserResponse' $ref: '#/components/schemas/UserResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {id: 123, username: johnusername, displayName: John, isAdmin: false}
{
id: 123,
username: johnusername,
displayName: John,
isAdmin: false
}
summary: 'Get session info (username).' summary: 'Get session info (username).'
tags: tags:
- Session - Session
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: [] parameters: []
/SASjsApi/stp/execute: /SASjsApi/stp/execute:
get: get:
@@ -1811,9 +1702,11 @@ paths:
tags: tags:
- STP - STP
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: 'Location of SAS or JS code' -
description: 'Location of SAS or JS code'
in: query in: query
name: _program name: _program
required: true required: true
@@ -1831,25 +1724,17 @@ paths:
$ref: '#/components/schemas/ExecuteReturnJsonResponse' $ref: '#/components/schemas/ExecuteReturnJsonResponse'
examples: examples:
'Example 1': 'Example 1':
value: value: {status: success, _webout: 'webout content', log: [], httpHeaders: {Content-type: application/zip, Cache-Control: 'public, max-age=1000'}}
{
status: success,
_webout: 'webout content',
log: [],
httpHeaders:
{
Content-type: application/zip,
Cache-Control: 'public, max-age=1000'
}
}
description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms\n\nThe response will be a JSON object with the following root attributes:\nlog, webout, headers.\n\nThe webout attribute will be nested JSON ONLY if the response-header\ncontains a content-type of application/json AND it is valid JSON.\nOtherwise it will be a stringified version of the webout content." description: "Trigger a SAS or JS program using the _program URL parameter.\n\nAccepts URL parameters and file uploads. For more details, see docs:\n\nhttps://server.sasjs.io/storedprograms\n\nThe response will be a JSON object with the following root attributes:\nlog, webout, headers.\n\nThe webout attribute will be nested JSON ONLY if the response-header\ncontains a content-type of application/json AND it is valid JSON.\nOtherwise it will be a stringified version of the webout content."
summary: 'Execute a Stored Program, return a JSON object' summary: 'Execute a Stored Program, return a JSON object'
tags: tags:
- STP - STP
security: security:
- bearerAuth: [] -
bearerAuth: []
parameters: parameters:
- description: 'Location of SAS or JS code' -
description: 'Location of SAS or JS code'
in: query in: query
name: _program name: _program
required: false required: false
@@ -1887,18 +1772,7 @@ paths:
application/json: application/json:
schema: schema:
properties: properties:
user: user: {properties: {isAdmin: {type: boolean}, displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [isAdmin, displayName, username, id], type: object}
{
properties:
{
isAdmin: { type: boolean },
displayName: { type: string },
username: { type: string },
id: { type: number, format: double }
},
required: [isAdmin, displayName, username, id],
type: object
}
loggedIn: {type: boolean} loggedIn: {type: boolean}
required: required:
- user - user
@@ -1954,27 +1828,39 @@ paths:
security: [] security: []
parameters: [] parameters: []
servers: servers:
- url: / -
url: /
tags: tags:
- name: Auth -
name: Auth
description: 'Operations about auth' description: 'Operations about auth'
- name: Client -
name: Client
description: 'Operations about clients' description: 'Operations about clients'
- name: CODE -
name: CODE
description: 'Execution of code (various runtimes are supported)' description: 'Execution of code (various runtimes are supported)'
- name: Drive -
name: Drive
description: 'Operations on SASjs Drive' description: 'Operations on SASjs Drive'
- name: Group -
name: Group
description: 'Operations on groups and group memberships' description: 'Operations on groups and group memberships'
- name: Info -
name: Info
description: 'Get Server Information' description: 'Get Server Information'
- name: Permission -
name: Permission
description: 'Operations about permissions' description: 'Operations about permissions'
- name: Session -
name: Session
description: 'Get Session information' description: 'Get Session information'
- name: STP -
name: STP
description: 'Execution of Stored Programs' description: 'Execution of Stored Programs'
- name: User -
name: User
description: 'Operations with users' description: 'Operations with users'
- name: Web -
name: Web
description: 'Operations on Web' description: 'Operations on Web'

View File

@@ -101,12 +101,14 @@ ${autoExecContent}`
session.path, session.path,
'-AUTOEXEC', '-AUTOEXEC',
autoExecPath, autoExecPath,
isWindows() ? '-nologo' : '',
process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '', process.sasLoc!.endsWith('sas.exe') ? '-nosplash' : '',
process.sasLoc!.endsWith('sas.exe') ? '-icon' : '', process.sasLoc!.endsWith('sas.exe') ? '-icon' : '',
process.sasLoc!.endsWith('sas.exe') ? '-nodms' : '', process.sasLoc!.endsWith('sas.exe') ? '-nodms' : '',
process.sasLoc!.endsWith('sas.exe') ? '-noterminal' : '', process.sasLoc!.endsWith('sas.exe') ? '-noterminal' : '',
process.sasLoc!.endsWith('sas.exe') ? '-nostatuswin' : '', process.sasLoc!.endsWith('sas.exe') ? '-nostatuswin' : '',
isWindows() ? '-nologo' : '' process.sasLoc!.endsWith('sas.exe') ? '-SASINITIALFOLDER' : '',
process.sasLoc!.endsWith('sas.exe') ? session.path : ''
]) ])
.then(() => { .then(() => {
session.completed = true session.completed = true

View File

@@ -0,0 +1,143 @@
import { readFile } from '@sasjs/utils'
import express from 'express'
import path from 'path'
import { Request, Post, Get } from 'tsoa'
export interface Sas9Response {
content: string
redirect?: string
error?: boolean
}
export interface MockFileRead {
content: string
error?: boolean
}
export class MockSas9Controller {
private loggedIn: boolean = false
@Get('/SASStoredProcess')
public async sasStoredProcess(): Promise<Sas9Response> {
if (!this.loggedIn) {
return {
content: '',
redirect: '/SASLogon/login'
}
}
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'sas-stored-process'
])
}
@Post('/SASStoredProcess/do/')
public async sasStoredProcessDo(
@Request() req: express.Request
): Promise<Sas9Response> {
if (!this.loggedIn) {
return {
content: '',
redirect: '/SASLogon/login'
}
}
let program = req.query._program?.toString() || ''
program = program.replace('/', '')
const content = await getMockResponseFromFile([
process.cwd(),
'mocks',
...program.split('/')
])
if (content.error) {
return content
}
const parsedContent = parseJsonIfValid(content.content)
return {
content: parsedContent
}
}
@Get('/SASLogon/login')
public async loginGet(): Promise<Sas9Response> {
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'login'
])
}
@Post('/SASLogon/login')
public async loginPost(): Promise<Sas9Response> {
this.loggedIn = true
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'logged-in'
])
}
@Get('/SASLogon/logout')
public async logout(): Promise<Sas9Response> {
this.loggedIn = false
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'logged-out'
])
}
}
/**
* If JSON is valid it will be parsed otherwise will return text unaltered
* @param content string to be parsed
* @returns JSON or string
*/
const parseJsonIfValid = (content: string) => {
let fileContent = ''
try {
fileContent = JSON.parse(content)
} catch (err: any) {
fileContent = content
}
return fileContent
}
const getMockResponseFromFile = async (
filePath: string[]
): Promise<MockFileRead> => {
const filePathParsed = path.join(...filePath)
let error: boolean = false
let file = await readFile(filePathParsed).catch((err: any) => {
const errMsg = `Error reading mocked file on path: ${filePathParsed}\nError: ${err}`
console.error(errMsg)
error = true
return errMsg
})
return {
content: file,
error: error
}
}

View File

@@ -1,8 +1,25 @@
import express from 'express' import express from 'express'
import sas9WebRouter from './sas9-web'
import sasViyaWebRouter from './sasviya-web'
import webRouter from './web' import webRouter from './web'
import { MOCK_SERVERTYPEType } from '../../utils'
const router = express.Router() const router = express.Router()
const { MOCK_SERVERTYPE } = process.env
switch (MOCK_SERVERTYPE) {
case MOCK_SERVERTYPEType.SAS9: {
router.use('/', sas9WebRouter)
break
}
case MOCK_SERVERTYPEType.SASVIYA: {
router.use('/', sasViyaWebRouter)
break
}
default: {
router.use('/', webRouter) router.use('/', webRouter)
}
}
export default router export default router

View File

@@ -0,0 +1,88 @@
import express from 'express'
import { WebController } from '../../controllers'
import { MockSas9Controller } from '../../controllers/mock-sas9'
const sas9WebRouter = express.Router()
const webController = new WebController()
// Mock controller must be singleton because it keeps the states
// for example `isLoggedIn` and potentially more in future mocks
const controller = new MockSas9Controller()
sas9WebRouter.get('/', async (req, res) => {
let response
try {
response = await webController.home()
} catch (_) {
response = '<html><head></head><body>Web Build is not present</body></html>'
} finally {
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
const injectedContent = response?.replace(
'</head>',
`${codeToInject}</head>`
)
return res.send(injectedContent)
}
})
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
const response = await controller.sasStoredProcess()
if (response.redirect) {
res.redirect(response.redirect)
return
}
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => {
const response = await controller.sasStoredProcessDo(req)
if (response.redirect) {
res.redirect(response.redirect)
return
}
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
sas9WebRouter.get('/SASLogon/login', async (req, res) => {
const response = await controller.loginGet()
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
sas9WebRouter.post('/SASLogon/login', async (req, res) => {
const response = await controller.loginPost()
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
sas9WebRouter.get('/SASLogon/logout', async (req, res) => {
const response = await controller.logout()
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
export default sas9WebRouter

View File

@@ -0,0 +1,32 @@
import express from 'express'
import { WebController } from '../../controllers/web'
const sasViyaWebRouter = express.Router()
const controller = new WebController()
sasViyaWebRouter.get('/', async (req, res) => {
let response
try {
response = await controller.home()
} catch (_) {
response = '<html><head></head><body>Web Build is not present</body></html>'
} finally {
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
const injectedContent = response?.replace(
'</head>',
`${codeToInject}</head>`
)
return res.send(injectedContent)
}
})
sasViyaWebRouter.post('/SASJobExecution/', async (req, res) => {
try {
res.send({ test: 'test' })
} catch (err: any) {
res.status(403).send(err.toString())
}
})
export default sasViyaWebRouter

View File

@@ -16,7 +16,7 @@ export const getDesktopFields = async () => {
nodeLoc = NODE_PATH ?? (await getNodeLocation()) nodeLoc = NODE_PATH ?? (await getNodeLocation())
} }
if (process.runTimes.includes(RunTimeType.JS)) { if (process.runTimes.includes(RunTimeType.PY)) {
pythonLoc = PYTHON_PATH ?? (await getPythonLocation()) pythonLoc = PYTHON_PATH ?? (await getPythonLocation())
} }

View File

@@ -1,3 +1,8 @@
export enum MOCK_SERVERTYPEType {
SAS9 = 'sas9',
SASVIYA = 'sasviya'
}
export enum ModeType { export enum ModeType {
Server = 'server', Server = 'server',
Desktop = 'desktop' Desktop = 'desktop'
@@ -40,6 +45,8 @@ export enum ReturnCode {
export const verifyEnvVariables = (): ReturnCode => { export const verifyEnvVariables = (): ReturnCode => {
const errors: string[] = [] const errors: string[] = []
errors.push(...verifyMOCK_SERVERTYPE())
errors.push(...verifyMODE()) errors.push(...verifyMODE())
errors.push(...verifyPROTOCOL()) errors.push(...verifyPROTOCOL())
@@ -66,6 +73,23 @@ export const verifyEnvVariables = (): ReturnCode => {
return ReturnCode.Success return ReturnCode.Success
} }
const verifyMOCK_SERVERTYPE = (): string[] => {
const errors: string[] = []
const { MOCK_SERVERTYPE } = process.env
if (MOCK_SERVERTYPE) {
const modeTypes = Object.values(MOCK_SERVERTYPEType)
if (!modeTypes.includes(MOCK_SERVERTYPE as MOCK_SERVERTYPEType))
errors.push(
`- MOCK_SERVERTYPE '${MOCK_SERVERTYPE}'\n - valid options ${modeTypes}`
)
} else {
process.env.MOCK_SERVERTYPE = undefined
}
return errors
}
const verifyMODE = (): string[] => { const verifyMODE = (): string[] => {
const errors: string[] = [] const errors: string[] = []
const { MODE } = process.env const { MODE } = process.env

View File

@@ -78,6 +78,18 @@ const TreeViewNode = ({
mouseY: number mouseY: number
} | null>(null) } | null>(null)
const launchProgram = () => {
const baseUrl = window.location.origin
window.open(`${baseUrl}/SASjsApi/stp/execute?_program=${node.relativePath}`)
}
const launchProgramWithDebug = () => {
const baseUrl = window.location.origin
window.open(
`${baseUrl}/SASjsApi/stp/execute?_program=${node.relativePath}&_debug=131`
)
}
const handleContextMenu = (event: React.MouseEvent) => { const handleContextMenu = (event: React.MouseEvent) => {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
@@ -224,8 +236,8 @@ const TreeViewNode = ({
: undefined : undefined
} }
> >
{node.isFolder && ( {node.isFolder ? (
<div> <>
<MenuItem onClick={handleNewFolderItemClick}>Add Folder</MenuItem> <MenuItem onClick={handleNewFolderItemClick}>Add Folder</MenuItem>
<MenuItem <MenuItem
disabled={!node.relativePath} disabled={!node.relativePath}
@@ -233,14 +245,21 @@ const TreeViewNode = ({
> >
Add File Add File
</MenuItem> </MenuItem>
</div> </>
) : (
<>
<MenuItem onClick={launchProgram}>Launch</MenuItem>
<MenuItem onClick={launchProgramWithDebug}>
Launch and Debug
</MenuItem>
</>
)}
{!!node.relativePath && (
<>
<MenuItem onClick={handleRenameItemClick}>Rename</MenuItem>
<MenuItem onClick={handleDeleteItemClick}>Delete</MenuItem>
</>
)} )}
<MenuItem disabled={!node.relativePath} onClick={handleRenameItemClick}>
Rename
</MenuItem>
<MenuItem disabled={!node.relativePath} onClick={handleDeleteItemClick}>
Delete
</MenuItem>
</Menu> </Menu>
</div> </div>
) )