├── sasjs
├── macros
│ └── .gitkeep
├── utils
│ ├── copysas9.sh
│ └── copyviya.sh
├── services
│ └── common
│ │ ├── appinit.sas
│ │ ├── append.sas
│ │ └── upload.sas
└── sasjsconfig.json
├── .gitignore
├── src
├── logo.png
├── table.css
├── switch.css
├── style.css
├── index.html
└── scripts.js
├── .gitpod.dockerfile
├── .gitpod.yml
├── stream.sas
├── package.json
└── README.md
/sasjs/macros/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | sasjsbuild/
3 | sasjsresults/
4 | .env*
5 |
--------------------------------------------------------------------------------
/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasjs/fileuploader/master/src/logo.png
--------------------------------------------------------------------------------
/sasjs/utils/copysas9.sh:
--------------------------------------------------------------------------------
1 | echo "sasjs: copying deploy script to repo root"
2 | cp sasjsbuild/sas9.sas streamsas9.sas
--------------------------------------------------------------------------------
/sasjs/utils/copyviya.sh:
--------------------------------------------------------------------------------
1 | echo "sasjs: copying deploy script to repo root"
2 | cp sasjsbuild/viya.sas streamviya.sas
--------------------------------------------------------------------------------
/.gitpod.dockerfile:
--------------------------------------------------------------------------------
1 | FROM gitpod/workspace-full
2 |
3 | RUN sudo apt-get update \
4 | && sudo apt-get install -y \
5 | doxygen \
6 | && sudo rm -rf /var/lib/apt/lists/*
--------------------------------------------------------------------------------
/sasjs/services/common/appinit.sas:
--------------------------------------------------------------------------------
1 | /**
2 | @file appinit.sas
3 | @brief Initialisation service - runs on app startup
4 | @details This is always the first service called when the app is opened.
5 |
6 | **/
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | # List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/config-start-tasks/
2 | tasks:
3 | - init: npm install -g npm && npm i -g @sasjs/cli
4 |
5 | vscode:
6 | extensions:
7 | - sasjs.sasjs-for-vscode
8 |
9 |
--------------------------------------------------------------------------------
/src/table.css:
--------------------------------------------------------------------------------
1 | table {
2 | font-family: arial, sans-serif;
3 | border-collapse: collapse;
4 | width: 100%;
5 | }
6 |
7 | td,
8 | th {
9 | border: 1px solid #dddddd;
10 | text-align: left;
11 | padding: 8px;
12 | }
13 |
14 | tr:nth-child(even) {
15 | background-color: #dddddd;
16 | }
17 |
--------------------------------------------------------------------------------
/stream.sas:
--------------------------------------------------------------------------------
1 | /**
2 | @file
3 | @brief Stream the SAS 9 or Viya version
4 | @details Depending on server type, will execute either the SAS 9 or the Viya
5 | version of the fileuploader app
6 | **/
7 |
8 | %global sysprocessmode;
9 | data _null_;
10 | mode=symget('sysprocessmode');
11 | if mode in ("SAS Object Server","SAS Compute Server")
12 | then call symputx('streamer','streamviya.sas');
13 | else call symputx('streamer','streamsas9.sas');
14 | run;
15 |
16 | filename stream url
17 | "https://raw.githubusercontent.com/sasjs/fileuploader/master/&streamer";
18 | %inc stream;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@sasjs/fileuploader",
3 | "repository": "https://github.com/sasjs/fileuploader",
4 | "version": "1.0.0",
5 | "description": "A plain javascript app to demonstrate the process of uploading a file (with a parameter) and dealing with the response from SAS",
6 | "main": "index.js",
7 | "scripts": {
8 | "deploy": "rsync -avhe ssh ./src/* --delete $SSH_ACCOUNT:$DEPLOY_PATH",
9 | "deploywin": "scp -i ~/.ssh/private_key -r ./src/* $SSH_ACCOUNT:$DEPLOY_PATH",
10 | "prepare": "cpy-t node_modules/@sasjs/adapter/index.js src/sasjs.js"
11 | },
12 | "keywords": [
13 | "SAS",
14 | "SASViya",
15 | "SASjs"
16 | ],
17 | "author": "",
18 | "license": "ISC",
19 | "dependencies": {
20 | "@sasjs/adapter": "4.1.6",
21 | "@sasjs/core": "4.45.11"
22 | },
23 | "devDependencies": {
24 | "@types/tough-cookie": "^4.0.1",
25 | "cpy-t": "^1.1.5"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/switch.css:
--------------------------------------------------------------------------------
1 | .switch {
2 | position: relative;
3 | display: inline-block;
4 | width: 60px;
5 | height: 34px;
6 | }
7 |
8 | .switch input {
9 | opacity: 0;
10 | width: 0;
11 | height: 0;
12 | }
13 |
14 | .slider {
15 | position: absolute;
16 | cursor: pointer;
17 | top: 0;
18 | left: 0;
19 | right: 0;
20 | bottom: 0;
21 | background-color: #ccc;
22 | -webkit-transition: 0.4s;
23 | transition: 0.4s;
24 | }
25 |
26 | .slider:before {
27 | position: absolute;
28 | content: "";
29 | height: 26px;
30 | width: 26px;
31 | left: 4px;
32 | bottom: 4px;
33 | background-color: white;
34 | -webkit-transition: 0.4s;
35 | transition: 0.4s;
36 | }
37 |
38 | input:checked + .slider {
39 | background-color: #2196f3;
40 | }
41 |
42 | input:focus + .slider {
43 | box-shadow: 0 0 1px #2196f3;
44 | }
45 |
46 | input:checked + .slider:before {
47 | -webkit-transform: translateX(26px);
48 | -ms-transform: translateX(26px);
49 | transform: translateX(26px);
50 | }
51 |
52 | /* Rounded sliders */
53 | .slider.round {
54 | border-radius: 34px;
55 | }
56 |
57 | .slider.round:before {
58 | border-radius: 50%;
59 | }
60 |
--------------------------------------------------------------------------------
/sasjs/services/common/append.sas:
--------------------------------------------------------------------------------
1 | /**
2 | @file
3 | @brief appends a file from frontend to a user provided location
4 | @details Returns the file size or -1 in case of error.
5 |
6 |
SAS Macros
7 | @li mf_getfilesize.sas
8 | @li mf_isdir.sas
9 | @li mp_abort.sas
10 | @li mp_binarycopy.sas
11 | @li mp_webin.sas
12 |
13 | **/
14 |
15 | %mp_abort(iftrue= (%mf_isdir(&path) = 0)
16 | ,mac=&_program..sas
17 | ,msg=%str(File path (&path) is not a valid directory)
18 | )
19 |
20 | /*
21 | Straighten up the _webin_xxx variables
22 | */
23 | %mp_webin()
24 |
25 | /* setup the output destination */
26 | %let outloc=&path/&_webin_filename1;
27 | filename fileout "&outloc";
28 |
29 | /* send the data in APPEND mode */
30 | %mp_binarycopy(inref=&_webin_fileref1, outref=fileout, mode=APPEND)
31 |
32 | %mp_abort(iftrue= (&syscc ge 4)
33 | ,mac=&_program..sas
34 | ,msg=%str(Error occurred reading &_webin_fileref1 and writing to &outloc)
35 | )
36 |
37 | /* success - lets create a directory listing */
38 | data fromsas;
39 | filesize="%mf_getfilesize(fpath=&path/&_webin_filename1,format=yes)";
40 | run;
41 |
42 | /* now send it back to the frontend */
43 | %webout(OPEN)
44 | %webout(OBJ,fromsas)
45 | %webout(CLOSE)
46 |
--------------------------------------------------------------------------------
/sasjs/services/common/upload.sas:
--------------------------------------------------------------------------------
1 | /**
2 | @file
3 | @brief Loads a file from frontend to a user provided location
4 | @details Returns a directory listing if successful.
5 |
6 | The macros shown below are compiled from the SASjs CORE library (or the
7 | sasjs/macros project directory) when running the `sasjs cb` command. This is
8 | why you see them in the service, but not in the file in the GIT repository.
9 |
10 | SAS Macros
11 | @li mp_abort.sas
12 | @li mf_isdir.sas
13 | @li mp_dirlist.sas
14 | @li mp_binarycopy.sas
15 | @li mp_webin.sas
16 |
17 | **/
18 |
19 | %mp_abort(iftrue= (%mf_isdir(&path) = 0)
20 | ,mac=&_program..sas
21 | ,msg=%str(File path (&path) is not a valid directory)
22 | )
23 |
24 | /*
25 | Straighten up the _webin_xxx variables
26 | */
27 | %mp_webin()
28 |
29 | /* setup the output destination */
30 | %let outloc=&path/&_webin_filename1;
31 | filename fileout "&outloc";
32 |
33 | /* send the data */
34 | %mp_binarycopy(inref=&_webin_fileref1, outref=fileout)
35 |
36 | %mp_abort(iftrue= (&syscc ge 4)
37 | ,mac=&_program..sas
38 | ,msg=%str(Error occurred reading &_webin_fileref1 and writing to &outloc)
39 | )
40 |
41 | /* success - lets create a directory listing */
42 | %mp_dirlist(path=&path,outds=dirlist)
43 | proc sort data=dirlist;
44 | by filepath;
45 | run;
46 |
47 | /* now send it back to the frontend */
48 | %webout(OPEN)
49 | %webout(OBJ,dirlist)
50 | %webout(CLOSE)
51 |
--------------------------------------------------------------------------------
/sasjs/sasjsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
3 | "macroFolders": [
4 | "sasjs/macros"
5 | ],
6 | "serviceConfig": {
7 | "serviceFolders": [
8 | "sasjs/services/common"
9 | ]
10 | },
11 | "streamConfig": {
12 | "streamLogo": "logo.png",
13 | "streamServiceName": "FileUploader",
14 | "streamWeb": true,
15 | "streamWebFolder": "web",
16 | "webSourcePath": "src"
17 | },
18 | "defaultTarget": "viya",
19 | "targets": [
20 | {
21 | "name": "viya",
22 | "serverUrl": "https://sasviya.com",
23 | "serverType": "SASVIYA",
24 | "httpsAgentOptions": {
25 | "allowInsecureRequests": false
26 | },
27 | "appLoc": "/Public/app/fileuploader",
28 | "deployConfig": {
29 | "deployServicePack": true,
30 | "deployScripts": [
31 | "sasjs/utils/copyviya.sh"
32 | ]
33 | },
34 | "contextName": "SAS Job Execution compute context"
35 | },
36 | {
37 | "name": "sas9",
38 | "serverType": "SAS9",
39 | "serverUrl": "https://sas.analytium.co.uk:8343",
40 | "appLoc": "/30.SASApps/3030.Projects/fileuploader",
41 | "deployConfig": {
42 | "deployServicePack": true,
43 | "deployScripts": [
44 | "sasjs/utils/copysas9.sh"
45 | ]
46 | }
47 | },
48 | {
49 | "name": "server",
50 | "serverType": "SASJS",
51 | "serverUrl": "",
52 | "appLoc": "/Public/app/fileuploader"
53 | }
54 | ]
55 | }
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: 'Roboto', sans-serif;
3 | }
4 |
5 | body {
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | min-height: 100vh;
11 | margin: 0;
12 | padding-bottom: 30px;
13 | }
14 |
15 | hr {
16 | clear: both;
17 | display: block;
18 | width: 100%;
19 | background-color: black;
20 | height: 1px;
21 | }
22 |
23 | .code {
24 | font-family: Monaco, Courier, monospace;
25 | border: 1px solid #d9d9d9;
26 | padding: 5px;
27 | border-radius: 3px;
28 | background-color: #000000;
29 | color: #f6e30f;
30 | }
31 |
32 | button {
33 | background-color: #3f51b5;
34 | color: #ffffff;
35 | border: none;
36 | padding: 10px 30px;
37 | border-radius: 5px;
38 | font-size: 16px;
39 | cursor: pointer;
40 | margin: 10px 0;
41 | }
42 |
43 | #upload[disabled] {
44 | background-color: gray;
45 | }
46 |
47 | #cancel {
48 | background-color: red;
49 | }
50 |
51 | #cancel[disabled] {
52 | background-color: gray;
53 | }
54 |
55 | button:focus,
56 | input:focus {
57 | outline: none;
58 | }
59 |
60 | button:hover {
61 | box-shadow: inset 0 0 100px 100px rgba(255, 255, 255, 0.1);
62 | }
63 | button[disabled]:hover {
64 | box-shadow: none;
65 | }
66 |
67 | .form {
68 | display: flex;
69 | flex-direction: column;
70 | }
71 |
72 | .form input {
73 | padding: 10px;
74 | border-radius: 5px;
75 | font-size: 20px;
76 | border: 1px solid #d9d9d9;
77 | margin-top: 5px;
78 | }
79 |
80 | select {
81 | font-size: 16px;
82 | padding: 20px;
83 | width: 30%;
84 | }
85 |
86 | .debug {
87 | position: absolute;
88 | top: 0;
89 | right: 0;
90 | padding: 20px;
91 | }
92 | .debug span {
93 | font-size: 20px;
94 | vertical-align: -webkit-baseline-middle;
95 | }
96 |
97 | .display-none {
98 | display: none;
99 | }
100 |
101 | #progressBar {
102 | position: relative;
103 | width: 100%;
104 | background-color: #ddd;
105 | }
106 |
107 | #barStatus {
108 | position: absolute;
109 | height: 100%;
110 | background-color: #25f;
111 | }
112 |
113 | #fileUploadStatus {
114 | display: flex;
115 | justify-content: center;
116 | }
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
22 |
23 |
24 | Debug
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | This is a template SASjs app
33 |
34 | You can use it to upload a local file to a directory on your SAS server
35 |
36 |
37 |
38 |
39 |
40 | Log in
41 |
42 |
51 |
52 |
57 | Upload
58 |
59 |
64 | Cancel
65 |
66 |
67 |
68 |
69 |
70 | FILEPATH
71 |
72 |
73 | Alfreds Futterkiste
74 |
75 |
76 |
77 |
78 | Logs
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://gitpod.io/#https://github.com/sasjs/fileuploader)
2 |
3 | # Demo SASjs File Uploader app
4 |
5 | This is a vanilla JS demo app to show how to build a SAS-Powered file upload process. The app has a logon screen, then an input box (so the user can provide a target directory path), and a file picker.
6 |
7 | The file is sent to SAS where the path is verified, the file is written, and a directory listing is returned.
8 |
9 | If the file is greater than 5mb it is sent to SAS in chunks, with a progress bar displayed. A video demonstrating this process is available here: https://www.youtube.com/watch?v=rf9myXovrsk
10 |
11 | 
12 |
13 | If the user logs out of SAS whilst an upload is in progress, then the app will prompt for credentials on the next request. The "redirect" flow is implemented, which is compatible with 2FA and other more complex authentication mechanisms.
14 |
15 | ## Fast Deploy
16 | This app can be deployed as a streaming SAS app by running the code below. Be
17 | sure to set the value of `appLoc` to your preferred parent folder location in
18 | metadata (SAS 9) or SAS Drive (Viya).
19 |
20 | ```sas
21 | %let apploc=/Public/app/fileuploader;
22 | filename mc url "https://raw.githubusercontent.com/sasjs/fileuploader/master/stream.sas";
23 | %inc mc;
24 | ```
25 |
26 | The link to open the app will be shown in the log.
27 |
28 |
29 | ## Alternatives
30 | It is also possible to upload files using SAS Studio, into your home directory (you can set symlinks to other locations).
31 |
32 | If your use case is simply about loading data into SAS, you might want to consider https://datacontroller.io (lets you load excel and CSV files into any SAS table or database with full audit trail - it's also free for up to 5 users).
33 |
34 | ## Building from Source
35 |
36 | To deploy this app, first install the SASjs CLI - full instructions [here](https://cli.sasjs.io/installation/).
37 |
38 | Next, run `sasjs add` to prepare your target ([instructions](https://cli.sasjs.io/add/)).
39 |
40 | Then run the below to deploy the backend (SAS) services:
41 |
42 | ```
43 | npm install
44 | sasjs cbd -t YOURTARGET
45 | ```
46 |
47 | If you don't have the ability to `sasjs add` due to not having access to a client / secret, you can instead run `sasjs cb` and execute the resulting `sasjsbuild/build.sas` script in SAS Studio V to create the backend services.
48 |
49 | ## Frontend
50 |
51 | ### Configuration
52 |
53 | Open `index.html` and make sure the value for `appLoc` is the same as that used when deploying the backend (`sasjsconfig.json`).
54 |
55 | Also, update the `