294 Commits

Author SHA1 Message Date
Manuel Romero
9ee489ef5a back to how it was 2024-02-26 16:12:30 +01:00
Manuel Romero
c2f26ae478 cookie secure 2024-02-26 15:59:56 +01:00
Manuel Romero
19e94b031a ahora si 2024-02-26 15:39:30 +01:00
Manuel Romero
7843a26962 dev 2024-02-26 15:22:06 +01:00
Manuel Romero
7040d5d030 session storage 2024-02-26 15:21:40 +01:00
Manuel Romero
66111af16f no ahora si que si 2024-02-26 11:53:33 +01:00
Manuel Romero
1eae012704 Ahora si 2024-02-26 11:47:35 +01:00
Manuel Romero
6f7db33e89 Some UI improvements 2024-02-26 11:45:20 +01:00
Manuel Romero
af323e1f91 Test cookie name 2024-02-26 10:34:37 +01:00
Manuel Romero
b85d5d1a2d Merge 2024-02-23 13:44:33 +01:00
Manuel Romero
6c6e1c27b1 venga 2024-02-23 13:29:07 +01:00
Manuel Romero
08714427b3 Merge branch 'master' into dev 2024-02-23 13:26:51 +01:00
Manuel Romero
1aabf5dd45 error page 2024-02-23 13:24:40 +01:00
Manuel Romero
03ed7b74db testing stuff 2024-02-23 13:15:30 +01:00
Manuel Romero
036ab4077c redirect 2024-02-22 15:31:29 +01:00
Manuel Romero
1888a447d7 Merge branch 'dev' 2024-02-22 14:46:56 +01:00
Manuel Romero
4e99e55373 prod 2024-02-22 14:46:08 +01:00
Manuel Romero
a31d3324ad revision navbar 2024-02-22 14:25:01 +01:00
Manuel Romero
092726c3f9 app link update 2024-02-22 11:58:55 +01:00
mjromper
630a35d528 Update index.html 2024-02-21 19:51:09 +00:00
Manuel Romero
f82c844f6b fox 2024-02-21 17:44:36 +01:00
Manuel Romero
24f9c51559 Buttons for guacamole 2024-02-21 17:22:29 +01:00
Manuel Romero
3ceffc97fb fix app used 2024-02-20 13:55:31 +01:00
Manuel Romero
dcc48ae5d3 stuff 2024-02-20 13:24:17 +01:00
Manuel Romero
b03fc86168 Merge branch 'dev' 2024-02-20 11:01:18 +01:00
Manuel Romero
0784f7bdfb No oid 2024-02-20 10:58:31 +01:00
Manuel Romero
71d26dbb9a adding sub to table user 2024-02-19 16:17:57 +01:00
Manuel Romero
dd4487edcd final 2024-02-19 16:10:43 +01:00
Manuel Romero
f805f78ef3 fix4 2024-02-19 16:04:39 +01:00
Manuel Romero
73206b556d fix3 2024-02-19 15:58:32 +01:00
Manuel Romero
397f3c1251 fix 2024-02-19 15:55:22 +01:00
Manuel Romero
d5d7a128d2 OKTA login 2024-02-19 15:37:32 +01:00
Manuel Romero
1f1e9f5b21 Prod 2024-02-13 16:42:28 +01:00
Manuel Romero
d262735c4e dev 2024-02-13 16:19:01 +01:00
Manuel Romero
ecac686e25 guacamole link 2024-02-13 16:18:28 +01:00
Manuel Romero
f1c9a6e87e cosmetics 2024-01-26 13:29:41 +01:00
Manuel Romero
fcba362cc0 no jwt,, just oauth with qlikcloud 2024-01-22 16:24:43 +01:00
Manuel Romero
c3d23d7415 no jwt, using qlikcliud oauth 2024-01-22 15:55:58 +01:00
Manuel Romero
84303d0632 footer 2024-01-22 13:13:20 +01:00
Manuel Romero
44b6a6d658 footer 2024-01-22 13:12:44 +01:00
Manuel Romero
4e90027630 merge 2024-01-22 12:55:09 +01:00
Manuel Romero
b207b9b3c4 New look 2024-01-22 12:54:10 +01:00
Manuel Romero
213867ed77 Merge branch 'master' into dev 2024-01-22 11:52:59 +01:00
Manuel Romero
4412216b6c white stuff 2024-01-22 11:51:56 +01:00
Manuel Romero
70014e9021 new favicon 2024-01-22 11:15:23 +01:00
Manuel Romero
a3704bd4f0 new favicon 2024-01-22 11:13:16 +01:00
Manuel Romero
ca0b30f37e Merge branch 'master' into dev 2024-01-22 10:56:43 +01:00
Manuel Romero
7e9ec60b8e server 2024-01-22 10:56:30 +01:00
Manuel Romero
adb4f54c54 nnew logo 2024-01-22 10:55:48 +01:00
Manuel Romero
eacba70919 fix 2023-12-14 10:34:24 +01:00
Manuel Romero
a68143af97 fix 2023-12-14 09:16:53 +01:00
Manuel Romero
a95e4e691d ei team 2023-12-13 13:54:20 +01:00
Manuel Romero
d4578bef28 start stop logs 2023-12-11 10:28:05 +01:00
Manuel Romero
ae04d5f62d Merge branch 'master' into dev 2023-12-01 11:38:25 +01:00
Manuel Romero
c3ea7ba384 fix db indentifier 2023-12-01 10:23:35 +01:00
Manuel Romero
bca55b62df Some expceptions controlled 2023-12-01 09:54:27 +01:00
Manuel Romero
36741b06ad refix 2023-11-30 16:12:21 +01:00
Manuel Romero
e7cb9b149d fix 2023-11-30 15:20:53 +01:00
Manuel Romero
8129378fc8 fix 2023-11-30 15:04:39 +01:00
Manuel Romero
f3d34321cd Merge 2023-11-30 14:01:30 +01:00
Manuel Romero
2a1525670c identifier 2023-11-30 13:58:38 +01:00
Manuel Romero
a7e36d0119 running awsrds 2023-11-30 10:54:41 +01:00
Manuel Romero
2ba046310f running awsrds 2023-11-30 10:54:04 +01:00
Manuel Romero
6ed942d954 dev 2023-11-30 10:23:02 +01:00
Manuel Romero
0af1c4d4c9 fix table 2023-11-30 10:22:28 +01:00
Manuel Romero
811220f882 Merge 2023-11-30 10:20:52 +01:00
Manuel Romero
caeef42000 Some fixes for RDS aws start stop 2023-11-30 10:17:53 +01:00
Manuel Romero
90a3cf176f better 2023-11-29 15:54:00 +01:00
Manuel Romero
44cec9cc31 aws-rdss stop sstart 2023-11-29 14:00:40 +01:00
Manuel Romero
e04cec01e6 merge 2023-11-23 16:47:02 +01:00
Manuel Romero
9df38c142e Session qs control 2023-11-23 16:09:31 +01:00
Manuel Romero
51bf4c8064 ahora vamos ya esta 2023-11-23 13:40:32 +01:00
Manuel Romero
cca7714f19 ui fix 2023-11-23 13:27:59 +01:00
Manuel Romero
fc6d9437a9 point to prod 2023-11-23 13:11:23 +01:00
Manuel Romero
537123c6aa ahora si 2023-11-23 12:55:28 +01:00
Manuel Romero
373258f402 vengaa 2023-11-23 12:42:21 +01:00
Manuel Romero
6f2a3b75d4 fix fin 2023-11-23 11:05:55 +01:00
Manuel Romero
ed4cd383df fix it 2023-11-23 10:16:22 +01:00
Manuel Romero
3cd7e1d4ae Added also none active 2023-11-22 16:54:04 +01:00
Manuel Romero
7264710bb5 session and stuff 2023-11-22 14:21:59 +01:00
Manuel Romero
004c425738 master into dev 2023-11-21 15:47:21 +01:00
Manuel Romero
cd50c47b4a request new session 2023-11-21 15:42:03 +01:00
Manuel Romero
93d240e9f2 less logs 2023-11-21 12:50:44 +01:00
Manuel Romero
01640adde7 Merge branch 'dev' 2023-11-21 12:03:33 +01:00
Manuel Romero
c3c1c76acb mongo redirect fixed 2023-11-21 12:03:18 +01:00
Manuel Romero
7e58cbc685 log 2023-11-21 11:51:53 +01:00
Manuel Romero
5e7ad89fdb Merge branch 'dev' 2023-11-21 11:42:12 +01:00
Manuel Romero
f1be3b7730 mongo redirect 2023-11-21 11:36:08 +01:00
Manuel Romero
08286e3172 dale 2023-11-21 11:30:05 +01:00
Manuel Romero
f16683a421 Merge branch 'dev' 2023-11-21 11:12:58 +01:00
Manuel Romero
ce7f97839b qmimongo and stuff 2023-11-21 11:12:45 +01:00
Manuel Romero
7e39db8dbd no cors 2023-11-21 09:41:11 +01:00
Manuel Romero
5c029d8976 prod 2023-11-20 16:48:41 +01:00
Manuel Romero
bc76634bca double server 2023-11-20 16:35:43 +01:00
Manuel Romero
fcb8365fe4 try catch 2023-11-20 16:01:43 +01:00
Manuel Romero
b2f4a41c0d vengga 2023-11-20 15:42:36 +01:00
Manuel Romero
811ca7ca70 fixxxx 2023-11-20 15:31:12 +01:00
Manuel Romero
1fcec290b6 fix 2023-11-20 15:09:25 +01:00
Manuel Romero
d9d2ac29b9 no se 2023-11-20 15:07:25 +01:00
Manuel Romero
28e3c12b23 fixxxx 2023-11-20 14:44:06 +01:00
Manuel Romero
1eff5089f2 dix 2023-11-20 14:38:23 +01:00
Manuel Romero
b0520064e8 extracted to componnent 2023-11-20 14:31:13 +01:00
Manuel Romero
b3262d9acb fix 2023-11-20 13:31:27 +01:00
Manuel Romero
272cb32e74 cost to provisions 2023-11-20 13:17:08 +01:00
Manuel Romero
577246830d cost data 2023-11-17 14:12:42 +01:00
Manuel Romero
7fed166c29 fix 2023-11-17 13:45:19 +01:00
Manuel Romero
294a3a8d2d fix 2023-11-17 13:42:04 +01:00
Manuel Romero
835d162ede qlik-embed 2023-11-17 12:23:53 +01:00
Manuel Romero
8f8c18490a fix 2023-10-10 15:38:42 +02:00
Manuel Romero
dbcdd6974b add guacamole link to outputs and email 2023-10-05 11:02:23 +02:00
Manuel Romero
61071e1d00 fix 2023-09-26 15:58:56 +02:00
Manuel Romero
1aa83a1d5b fix delete 2023-09-26 13:22:43 +02:00
Manuel Romero
f9c27e4b3d conn name 2023-09-26 12:31:20 +02:00
Manuel Romero
cb10e425d7 ssh connection 2023-09-26 12:18:30 +02:00
Manuel Romero
8827940789 fix delete 2023-09-26 12:00:34 +02:00
Manuel Romero
30e099ee2e guacamole 2023-09-26 11:58:47 +02:00
Manuel Romero
834aff2a9d talend email 2023-07-12 10:21:07 +02:00
Manuel Romero
975b84a3b2 no sort users table 2023-07-11 15:21:09 +02:00
Manuel Romero
14d1690100 fix 2023-07-05 07:48:57 +02:00
Manuel Romero
4bbd86e82f fix 2023-07-04 15:50:53 +02:00
Manuel Romero
ba30495cc8 filter emails 2023-07-04 15:40:12 +02:00
Manuel Romero
368d58070c fixes chulos 2023-06-15 13:00:28 +02:00
Manuel Romero
54f05b11c4 getUsers inactive 2023-06-15 12:23:42 +02:00
Manuel Romero
77b599ec83 fjx url 2023-06-12 14:49:55 +02:00
Manuel Romero
2cbcb9dd5d fix 2023-06-12 13:48:13 +02:00
Manuel Romero
0f0d050599 fix nextUrl 2023-06-12 13:31:36 +02:00
Manuel Romero
99d9a1a053 fix 2023-06-08 17:15:41 +02:00
Manuel Romero
1c9d5e5ce2 status session 2023-06-08 15:29:59 +02:00
Manuel Romero
56748c4478 output 2023-06-07 10:53:20 +02:00
Manuel Romero
41cdb46fc2 database link 2023-05-25 12:04:14 +02:00
Manuel Romero
515fd749e6 set pendingNextAction to null 2023-05-25 11:04:29 +02:00
Manuel Romero
2574278b68 Merge branch 'master' into dev 2023-05-19 13:27:52 +02:00
Manuel Romero
395c495130 happy 2023-05-19 13:27:35 +02:00
Manuel Romero
075815a6b5 happy 2023-05-19 13:26:47 +02:00
Manuel Romero
29c6bcad1e fix automl and error catch 2023-05-19 12:51:39 +02:00
Manuel Romero
35408b7b1f auto add automations 2023-05-19 11:53:11 +02:00
Manuel Romero
04d8d41b05 automations generation 2023-05-18 17:23:16 +02:00
Manuel Romero
7700675a76 students 2023-05-18 10:52:18 +02:00
Manuel Romero
2b57b2535d post spaces 2023-05-17 14:00:23 +02:00
Manuel Romero
ddc36dbfcf Only active users 2023-05-16 10:20:10 +02:00
Manuel Romero
2ab78335a2 adding session name 2023-05-16 09:42:43 +02:00
Manuel Romero
fed9ad156c edit user and more 2023-05-12 13:09:03 +02:00
Manuel Romero
b8e06f91ef small fix 2023-05-12 10:20:19 +02:00
Manuel Romero
de56713e96 added studetns count 2023-05-12 10:16:39 +02:00
Manuel Romero
3515158bb0 do not delete, set finshed to true 2023-05-11 12:46:11 +02:00
Manuel Romero
a5ed3edd84 cosmetic changes 2023-05-11 11:12:53 +02:00
Manuel Romero
e2d32d954e submit form 2023-05-11 10:44:38 +02:00
Manuel Romero
baf2b3776c submit form 2023-05-10 17:32:50 +02:00
Manuel Romero
f48e32230b small fix 2023-05-10 17:03:07 +02:00
Manuel Romero
3338f66c0b fix form 2023-05-10 14:56:28 +02:00
Manuel Romero
8d34b3d4af more stuff 2023-05-10 14:52:11 +02:00
Manuel Romero
023a3fa89d more trainning stuff 2023-05-10 13:38:22 +02:00
Manuel Romero
ad6d7a1082 new training stuff 2023-05-09 16:23:45 +02:00
Manuel Romero
8e8a23454a fix force externnal access 2023-05-02 12:38:35 +02:00
Manuel Romero
2cea8c6c8a remove logs 2023-03-30 12:43:22 +02:00
Manuel Romero
ce795f99f0 fix 2023-03-30 12:27:10 +02:00
Manuel Romero
fcda1da75b adding jobtitle to ui 2023-03-30 12:15:19 +02:00
Manuel Romero
199d11d22e Adding mail and Jobtitle 2023-03-30 12:03:13 +02:00
Manuel Romero
dd819b17ce fix logs destroy 2023-03-30 09:43:54 +02:00
Manuel Romero
2b93f3f986 fix logs destroy 2023-03-30 09:41:41 +02:00
Manuel Romero
1cf448c823 fix 2023-03-28 11:17:35 +02:00
Manuel Romero
6618b80964 adding active to users 2023-03-24 16:33:12 +01:00
Manuel Romero
3215f559d4 active users 2023-03-24 16:14:32 +01:00
Manuel Romero
cb634636f5 added active field for users 2023-03-24 14:14:44 +01:00
Manuel Romero
c9b8ed3e58 fix error 2023-03-23 16:27:51 +01:00
Manuel Romero
6e6233232c update error provisions 2023-03-14 11:32:05 +01:00
Manuel Romero
642ac66b9f let users see events and shares for a provisioh 2023-03-02 16:36:49 +01:00
Manuel Romero
319ab11274 fix 2023-02-23 12:18:27 +01:00
Manuel Romero
5e928f2e67 fix ui reload provisions 2023-02-10 10:09:29 +01:00
Manuel Romero
26b65b5752 Some events logs improvements 2023-02-10 10:00:11 +01:00
Manuel Romero
b677044fa6 better apis 2023-02-07 17:10:53 +01:00
Manuel Romero
bafcd504f2 stop-auto event 2023-02-07 13:23:10 +01:00
Manuel Romero
f41a9f76c1 mega fix 2023-02-07 12:44:51 +01:00
Manuel Romero
37c8f97493 Simple event types 2023-02-07 12:13:18 +01:00
Manuel Romero
9aac529d70 fix 2023-02-06 16:59:54 +01:00
Manuel Romero
3d59581432 provision admin query update 2023-02-06 16:43:48 +01:00
Manuel Romero
469e1244cc update shares 2023-02-06 15:50:10 +01:00
Manuel Romero
1735edf494 no edit events after owner changes 2023-02-06 15:36:56 +01:00
Manuel Romero
b02b258d77 fixes 2023-02-06 14:51:05 +01:00
Manuel Romero
222056574c Adding events owner 2023-02-06 13:44:57 +01:00
Manuel Romero
6748b3518f more fixes 2023-02-01 17:04:36 +01:00
Manuel Romero
29ccb721f5 fix ui 2023-02-01 16:35:00 +01:00
Manuel Romero
26706a154b fix 2022-11-30 09:38:01 +01:00
Manuel Romero
131be49e5f fix 2022-11-30 09:35:20 +01:00
Manuel Romero
b7e57ef49c better outputs in email 2022-11-29 15:47:10 +01:00
Manuel Romero
6f57285db0 fix 2022-11-22 11:57:38 +01:00
Manuel Romero
6694637fc1 fix ui 2022-11-22 10:52:48 +01:00
Manuel Romero
e84ffa16d0 fix ui 2022-11-22 10:47:26 +01:00
Manuel Romero
10b492b3d2 better ui finished 2022-11-22 10:37:50 +01:00
Manuel Romero
ab0866d36a better ui 2022-11-21 17:13:56 +01:00
Manuel Romero
deb29c0224 fix 2022-11-21 15:26:55 +01:00
Manuel Romero
c8944543cc provision page 2022-11-21 14:50:04 +01:00
Manuel Romero
6ae1472d6c fix 2022-11-18 15:53:42 +01:00
Manuel Romero
9c2c0393e1 ui fixes 2022-11-18 14:21:28 +01:00
Manuel Romero
062e3c89fd ui fixes 2022-11-18 14:04:59 +01:00
Manuel Romero
c08db405f4 ui fixes 2022-11-18 13:49:47 +01:00
Manuel Romero
e7ebfa7f4c ui fixes 2022-11-18 13:29:49 +01:00
Manuel Romero
46a908fb3c ui fixes 2022-11-18 12:56:13 +01:00
Manuel Romero
36a9fbed56 ui fixes 2022-11-18 12:51:31 +01:00
Manuel Romero
63965ec26e ui fixes 2022-11-18 12:42:27 +01:00
Manuel Romero
8b09158148 ui fixes 2022-11-18 12:13:28 +01:00
Manuel Romero
fc10564ab5 fixes 2022-11-17 13:58:22 +01:00
Manuel Romero
4c5b65aa38 better UI 2022-11-17 13:07:10 +01:00
Manuel Romero
94e392d3ef input text 2022-11-17 12:40:47 +01:00
Manuel Romero
ebc7924049 schedule fix 2022-11-14 10:55:25 +01:00
Manuel Romero
689b789864 scripts ref to project path 2022-11-10 12:01:10 +01:00
Manuel Romero
c3b0470c20 scripts ref to project path 2022-11-10 11:23:19 +01:00
Manuel Romero
3501cf72aa fix 2022-11-09 16:44:50 +01:00
Manuel Romero
3464bfa85a distribute snapshots endpoints 2022-11-09 16:24:33 +01:00
Manuel Romero
671b1b79ed distribute snapshots endpoints 2022-11-09 16:07:07 +01:00
Manuel Romero
b823e82411 stop synapse 2022-10-21 14:51:34 +02:00
Manuel Romero
25c1462848 some fixes in ui 2022-10-21 14:05:02 +02:00
Manuel Romero
fb1308556c fix 2022-10-21 13:06:19 +02:00
Manuel Romero
d85f521b53 fix 2022-10-21 12:59:52 +02:00
Manuel Romero
9105755afe synapse start stop ui 2022-10-21 12:42:38 +02:00
Manuel Romero
b3b157641e fix docker image az cli 2022-10-21 11:58:39 +02:00
Manuel Romero
55678c9e91 fix docker image az cli 2022-10-21 11:53:44 +02:00
Manuel Romero
7e173a2a5f resume synapse 2022-10-21 11:41:42 +02:00
Manuel Romero
41b0001886 pause and stop synapse 2022-10-21 11:29:25 +02:00
Manuel Romero
75efcbc3a1 fix stuff 2022-10-13 13:06:20 +02:00
Manuel Romero
3c3641040c test 2022-10-07 12:00:16 +02:00
Manuel Romero
fce686972c injecting aws credentials 2022-10-07 11:42:17 +02:00
Manuel Romero
d28a7b5f26 terraform image for each scenario 2022-10-04 16:10:31 +02:00
Manuel Romero
9372e47589 fix 2022-09-29 10:08:03 +02:00
Manuel Romero
b5af6c0959 fix cli 2022-09-28 11:17:01 +02:00
Manuel Romero
7ec3c6b1c5 fixed cli conditions 2022-09-19 12:49:31 +02:00
Manuel Romero
12f0e627a1 fix2 2022-09-19 11:41:56 +02:00
Manuel Romero
ec6149c127 fixes 2022-09-19 11:24:10 +02:00
Manuel Romero
06050b2796 accomodation for old vmImage POST 2022-09-19 11:10:51 +02:00
Manuel Romero
704319b27a fix cli 2022-09-16 14:13:18 +02:00
Manuel Romero
c868e7000e update versions 2022-09-16 13:46:47 +02:00
Manuel Romero
85f9c826cf envs 2022-09-16 11:39:50 +02:00
Manuel Romero
7d65d736bc fix env 2022-09-16 11:19:25 +02:00
Manuel Romero
2208e362c2 env variables through env.js 2022-09-16 11:17:10 +02:00
Manuel Romero
ac1933d881 some fixes on names 2022-09-16 10:42:23 +02:00
Manuel Romero
b95570b769 more 2022-09-15 14:46:52 +02:00
Manuel Romero
b52b5a0df7 changing versions by values 2022-09-15 14:35:17 +02:00
Manuel Romero
1cedcb791d vmImage by options 2022-09-09 15:54:45 +02:00
Manuel Romero
a20e677954 check is a Qlik email 2022-09-09 09:50:22 +02:00
Manuel Romero
2ea8877a76 Cost text 2022-09-02 13:19:02 +02:00
Manuel Romero
f272b030fb Merge branch 'dev' 2022-09-01 10:30:49 +02:00
Manuel Romero
0de582a750 interceptor 301 2022-09-01 10:11:49 +02:00
Manuel Romero
df6d28fd37 401 error mashup 2022-09-01 09:48:16 +02:00
Manuel Romero
d3149ad1b3 fixeds 2022-06-30 15:17:42 +02:00
Manuel Romero
e35873ab54 fixeds 2022-06-30 15:12:07 +02:00
Manuel Romero
7071eb6907 fix 2022-06-30 12:36:37 +02:00
Manuel Romero
852b68eaba fix 2022-06-30 12:00:55 +02:00
Manuel Romero
9063951aa7 fix 2022-06-30 11:43:19 +02:00
Manuel Romero
f69abbcda9 usin angular 2022-06-30 11:13:18 +02:00
Manuel Romero
45f936ee7c usin angular 2022-06-30 11:12:57 +02:00
Manuel Romero
39663ba9ad usin angular 2022-06-30 11:08:31 +02:00
Manuel Romero
58d50b2552 new version client 2022-06-30 10:32:18 +02:00
Manuel Romero
32a1abce22 revert config 2022-06-30 10:26:16 +02:00
Manuel Romero
30388bf064 New cost analysis stuff 2022-06-30 10:25:28 +02:00
Manuel Romero
a4bc5e4e7b adding subject to users 2022-06-29 10:30:49 +02:00
Manuel Romero
1e450bf019 new certs for barracuda 2022-04-26 11:14:26 +02:00
Manuel Romero
e65215448a new version worker 2022-02-08 17:03:20 +01:00
Manuel Romero
fbd2d8d829 Merge branch 'dev' 2022-02-08 17:02:47 +01:00
Manuel Romero
d531463e67 remove scenario from tf 2022-02-08 15:55:29 +01:00
Manuel Romero
28b792fe76 refactor tf 2022-02-08 15:52:27 +01:00
Manuel Romero
c19e4ca60a refactor tf 2022-02-08 15:51:19 +01:00
Manuel Romero
9749e98aee Merge branch 'dev' 2022-02-03 12:56:20 +01:00
Manuel Romero
b49895045d user_email and user.oid as TF_VARs 2022-02-03 11:54:42 +01:00
Manuel Romero
ca21757199 Merge branch 'dev' 2022-02-01 13:14:32 +01:00
Manuel Romero
2e98be3fca gitbranch to tf execution as env variable 2022-02-01 13:14:11 +01:00
Manuel Romero
3b86ac7777 gitbranch to tf execution as env variable 2022-02-01 12:36:43 +01:00
Manuel Romero
295d1ffb30 Env branch to docker tf 2022-02-01 12:08:23 +01:00
Manuel Romero
26eb86193c Env branch to docker tf 2022-02-01 12:06:43 +01:00
Manuel Romero
9e2cf17ab7 sending email for shared provisions 2022-01-28 16:46:26 +01:00
Manuel Romero
4600969f16 no redshift 90 days expire 2022-01-27 11:42:04 +01:00
Manuel Romero
332df80b1d no redshift 90 days expire 2022-01-27 11:41:34 +01:00
Manuel Romero
0a534eed37 using const 2022-01-25 13:08:03 +01:00
Manuel Romero
900a6abeb7 fix 2022-01-20 10:08:25 +01:00
Manuel Romero
9b81646219 fix multiple warnngs s3 bucket 2022-01-20 10:06:13 +01:00
Manuel Romero
ca8d26c119 Merge branch 'dev' 2022-01-20 10:03:53 +01:00
Manuel Romero
245bd0d90e no needed config 2022-01-11 11:39:38 +01:00
Manuel Romero
67ec379e1d docker image at provision 2022-01-11 11:38:24 +01:00
Manuel Romero
3e73e1a561 fix 2 2022-01-07 16:19:18 +01:00
Manuel Romero
55ccfab66c fis 2022-01-07 15:55:33 +01:00
Manuel Romero
23ae12bf2e test others 2022-01-07 14:53:06 +01:00
Manuel Romero
e8de86da66 fix shared 2021-12-21 09:04:09 +01:00
Manuel Romero
13a297d58d fix 2021-12-20 14:13:01 +01:00
Manuel Romero
f2aa774acd runForever 2021-12-20 14:04:14 +01:00
Manuel Romero
721b478d00 fix admin 2021-12-20 13:51:10 +01:00
Manuel Romero
4ee672ea3b fix 2021-11-30 12:17:21 +01:00
Manuel Romero
acd210da93 Share in user 2021-11-30 11:43:03 +01:00
Manuel Romero
4c853a7b20 fix getUsers 2021-11-29 10:17:13 +01:00
Manuel Romero
51f20a9e2d now one more fix 2021-11-26 14:29:53 +01:00
Manuel Romero
18a17ea761 fix 2021-11-26 13:48:03 +01:00
Manuel Romero
60c5bc543a small fix 2021-11-26 13:40:52 +01:00
Manuel Romero
0aa1dc0c74 do not delete shares 2021-11-26 13:39:13 +01:00
Manuel Romero
f5b85b25ed small fix 2021-11-26 13:10:48 +01:00
Manuel Romero
4245694b10 final 2021-11-26 12:29:06 +01:00
Manuel Romero
24c99df65a new version 2021-11-26 11:12:48 +01:00
203 changed files with 11270 additions and 18588 deletions

View File

@@ -9,9 +9,9 @@
```json
{
"AZURE_TENANT_ID" : "xxxxxxxx",
"AZURE_CLIENT_ID": "yyyyyyyy",
"AZURE_CLIENT_SECRET": "zzzzzzzz"
"IDENTITY_METADATA" : "xxxxxxxx",
"CLIENT_ID": "yyyyyyyy",
"CLIENT_SECRET": "zzzzzzzz"
}
```

View File

@@ -23,8 +23,10 @@
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
"src/favicon.svg",
"src/assets",
"src/env.js",
"src/oauth-callback.html"
],
"styles": [
"node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss",
@@ -79,7 +81,8 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "qmi-cloud:build"
"browserTarget": "qmi-cloud:build",
"proxyConfig": "proxy.conf.json"
},
"configurations": {
"production": {
@@ -105,7 +108,7 @@
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/favicon.svg",
"src/assets"
]
}

View File

@@ -10,7 +10,7 @@ Just an assorted selection of questions and answers.
### 2\. Are these QMI scenarios ready to go End-To-End demos?
- Not at this moment, a global environment is being built for PreSales by GEAR.
- Not at this moment, a global environment is being built for PreSales by Innovation & Excellence Team.
- Provided scenarios are meant for (See 1). QMI works by installing licensing the products. QMI completes the main setup for the scenario to properly work where possible. You may need to put custom data, Qlik Sense applications or whatever you need for your customer engagement.
* * *
@@ -117,7 +117,6 @@ Just an assorted selection of questions and answers.
### 15\. How are we managing Costs?
- Excessive use for instances, will need to be justified, every instance logged it tagged with your trigram. The usage of QMI Cloud will be made public within a Qlik Sense Application.
- GEAR is looking at estimating the price for each server that you initiate.
* * *

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

8
dist/qmi-cloud/assets/favicon.svg vendored Normal file
View File

@@ -0,0 +1,8 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M23.3002 25.1984C21.3002 26.7984 18.7002 27.6984 16.0002 27.6984C9.5002 27.7984 4.2002 22.4984 4.2002 15.9984C4.2002 13.1984 5.2002 10.5984 6.8002 8.59844L4.7002 6.39844C2.5002 8.99844 1.2002 12.2984 1.2002 15.9984C1.2002 24.1984 7.8002 30.7984 16.0002 30.7984C19.6002 30.7984 22.9002 29.4984 25.5002 27.3984L23.3002 25.1984Z"
fill="#54565A" />
<path
d="M27.9002 30.0992H32.0002L27.6002 25.2992C29.6002 22.7992 30.8002 19.4992 30.8002 15.9992C30.8002 7.79922 24.2002 1.19922 16.0002 1.19922C11.5002 1.19922 7.4002 3.29922 4.7002 6.49922L6.9002 8.69922C9.0002 5.89922 12.3002 4.19922 16.0002 4.19922C22.5002 4.19922 27.8002 9.49922 27.8002 15.9992C27.8002 19.6992 26.1002 23.0992 23.3002 25.1992L25.3002 27.1992L25.4002 27.2992L27.9002 30.0992Z"
fill="#009845" />
</svg>

After

Width:  |  Height:  |  Size: 914 B

1
dist/qmi-cloud/assets/logo.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="30" fill="none"><path fill="#54565A" d="M38.188.66h-2.985v28.562h2.985V.66ZM46.298 9.27h-2.974v19.953h2.974V9.27ZM46.842 3.02a2.033 2.033 0 1 0-4.014-.652 2.033 2.033 0 0 0 4.014.651ZM22.408 24.334A11.885 11.885 0 0 1 15 26.907C8.425 26.918 3.093 21.587 3.093 15c0-2.833.984-5.429 2.628-7.473L3.536 5.332A14.912 14.912 0 0 0 0 15c0 8.285 6.716 15 15 15 3.645 0 6.986-1.297 9.582-3.46l-2.174-2.206Z"/><path fill="#009845" d="M27.035 29.232h4.132l-4.456-4.856A14.951 14.951 0 0 0 30 15c0-8.284-6.716-15-15-15A14.974 14.974 0 0 0 3.535 5.332L5.72 7.527a11.878 11.878 0 0 1 9.29-4.445c6.586 0 11.917 5.332 11.917 11.918 0 3.785-1.762 7.16-4.51 9.333l2.045 2.055.086.097.022.022 2.465 2.725Z"/><path fill="#54565A" d="M67.191 9.269H63.05l-8.597 7.527L54.44.66h-2.974v28.562h2.974v-9.214l8.825 9.214h4.13L56.875 18.548l10.317-9.28ZM71.495 27.816v1.417h-.248v-.973l-.422.973h-.13l-.433-.973v.973h-.248v-1.417h.303l.443 1.039.432-1.039h.303ZM69.776 27.816v.206h-.433v1.211h-.248v-1.211h-.433v-.206h1.114Z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

5
dist/qmi-cloud/env.js vendored Normal file
View File

@@ -0,0 +1,5 @@
(function (window) {
window.__env = window.__env || {};
window.__env.disabledProvisions = false;
}(this));

Binary file not shown.

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 699 KiB

After

Width:  |  Height:  |  Size: 713 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!--
Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
Font Awesome Free 5.14.0 by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<metadata>
Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020
Created by FontForge 20200314 at Wed Jul 15 11:59:40 2020
By Robert Madole
Copyright (c) Font Awesome
</metadata>
@@ -20,7 +20,7 @@ Copyright (c) Font Awesome
panose-1="2 0 5 3 0 0 0 0 0 0"
ascent="448"
descent="-64"
bbox="-0.0663408 -64.0662 640.01 448.1"
bbox="-0.0663408 -64.0662 640.004 448.1"
underline-thickness="25"
underline-position="-50"
unicode-range="U+0020-F5C8"
@@ -50,7 +50,7 @@ s-36 16.1182 -36 36s16.1182 36 36 36s36 -16.1182 36 -36zM164 192c0 -19.8818 -16.
<glyph glyph-name="flag" unicode="&#xf024;"
d="M336.174 368c35.4668 0 73.0195 12.6914 108.922 28.1797c31.6406 13.6514 66.9043 -9.65723 66.9043 -44.1162v-239.919c0 -16.1953 -8.1543 -31.3057 -21.7129 -40.1631c-26.5762 -17.3643 -70.0693 -39.9814 -128.548 -39.9814c-68.6084 0 -112.781 32 -161.913 32
c-56.5674 0 -89.957 -11.2803 -127.826 -28.5566v-83.4434c0 -8.83691 -7.16309 -16 -16 -16h-16c-8.83691 0 -16 7.16309 -16 16v406.438c-14.3428 8.2998 -24 23.7979 -24 41.5615c0 27.5693 23.2422 49.71 51.2012 47.8965
c22.9658 -1.49023 41.8662 -19.4717 44.4805 -42.3379c0.177734 -1.52441 0.321289 -4.00781 0.321289 -5.54199c0 -4.30176 -1.10352 -11.1035 -2.46289 -15.1846c22.418 8.68555 49.4199 15.168 80.7207 15.168c68.6084 0 112.781 -32 161.913 -32zM464 112v240
c22.9658 -1.49023 41.8662 -19.4717 44.4805 -42.3379c0.213867 -1.83398 0.308594 -3.65918 0.308594 -5.5498c0 -5.30273 -0.860352 -10.4053 -2.4502 -15.1768c22.418 8.68555 49.4199 15.168 80.7207 15.168c68.6084 0 112.781 -32 161.913 -32zM464 112v240
c-31.5059 -14.6338 -84.5547 -32 -127.826 -32c-59.9111 0 -101.968 32 -161.913 32c-41.4365 0 -80.4766 -16.5879 -102.261 -32v-232c31.4473 14.5967 84.4648 24 127.826 24c59.9111 0 101.968 -32 161.913 -32c41.4365 0 80.4775 16.5879 102.261 32z" />
<glyph glyph-name="bookmark" unicode="&#xf02e;" horiz-adv-x="384"
d="M336 448c26.5098 0 48 -21.4902 48 -48v-464l-192 112l-192 -112v464c0 26.5098 21.4902 48 48 48h288zM336 19.5703v374.434c0 3.31348 -2.68555 5.99609 -6 5.99609h-276c-3.31152 0 -6 -2.68848 -6 -6v-374.43l144 84z" />
@@ -77,17 +77,17 @@ c0 -110.569 89.4678 -200 200 -200zM363.244 247.2c0 -67.0518 -72.4209 -68.084 -72
c17.5615 9.84473 28.3242 16.541 28.3242 29.5791c0 17.2461 -21.999 28.6934 -39.7842 28.6934c-23.1885 0 -33.8936 -10.9775 -48.9424 -29.9697c-4.05664 -5.11914 -11.46 -6.07031 -16.666 -2.12402l-27.8232 21.0986
c-5.10742 3.87207 -6.25098 11.0654 -2.64453 16.3633c23.627 34.6934 53.7217 54.1846 100.575 54.1846c49.0713 0 101.45 -38.3037 101.45 -88.7998zM298 80c0 -23.1592 -18.8408 -42 -42 -42s-42 18.8408 -42 42s18.8408 42 42 42s42 -18.8408 42 -42z" />
<glyph glyph-name="eye" unicode="&#xf06e;" horiz-adv-x="576"
d="M288 304c0.0927734 0 0.244141 0.000976562 0.336914 0.000976562c61.6641 0 111.71 -50.0469 111.71 -111.711c0 -61.6631 -50.0459 -111.71 -111.71 -111.71s-111.71 50.0469 -111.71 111.71c0 8.71289 1.95898 22.5781 4.37305 30.9502
c6.93066 -3.94141 19.0273 -7.18457 27 -7.24023c30.9121 0 56 25.0879 56 56c-0.0556641 7.97266 -3.29883 20.0693 -7.24023 27c8.42383 2.62207 22.4189 4.8623 31.2402 5zM572.52 206.6c1.9209 -3.79883 3.47949 -10.3379 3.47949 -14.5947
s-1.55859 -10.7959 -3.47949 -14.5947c-54.1992 -105.771 -161.59 -177.41 -284.52 -177.41s-230.29 71.5898 -284.52 177.4c-1.9209 3.79883 -3.47949 10.3379 -3.47949 14.5947s1.55859 10.7959 3.47949 14.5947c54.1992 105.771 161.59 177.41 284.52 177.41
s230.29 -71.5898 284.52 -177.4zM288 48c98.6602 0 189.1 55 237.93 144c-48.8398 89 -139.27 144 -237.93 144s-189.09 -55 -237.93 -144c48.8398 -89 139.279 -144 237.93 -144z" />
d="M288 304c0.114258 0 0.240234 -0.0175781 0.354492 -0.0175781c61.6543 0 111.71 -50.0557 111.71 -111.71s-50.0557 -111.71 -111.71 -111.71s-111.71 50.0557 -111.71 111.71c0 10.7422 1.51953 21.1328 4.35547 30.9678
c7.95898 -4.52637 17.2129 -7.17188 27 -7.24023c30.9072 0 56 25.0928 56 56c-0.0683594 9.78711 -2.71387 19.041 -7.24023 27c9.88379 3.07617 20.3896 4.83008 31.2402 5zM572.52 206.6c2.21387 -4.37793 3.46094 -9.38965 3.46094 -14.626
c0 -5.2373 -1.24707 -10.1855 -3.46094 -14.5635c-54.1992 -105.771 -161.59 -177.41 -284.52 -177.41s-230.29 71.5898 -284.52 177.4c-2.21387 4.37793 -3.46094 9.38965 -3.46094 14.626c0 5.2373 1.24707 10.1855 3.46094 14.5635
c54.1992 105.771 161.59 177.41 284.52 177.41s230.29 -71.5898 284.52 -177.4zM288 48c98.6602 0 189.1 55 237.93 144c-48.8398 89 -139.27 144 -237.93 144s-189.09 -55 -237.93 -144c48.8398 -89 139.279 -144 237.93 -144z" />
<glyph glyph-name="eye-slash" unicode="&#xf070;" horiz-adv-x="640"
d="M634 -23c3.31738 -2.65137 6.00977 -8.25098 6.00977 -12.498c0 -3.10449 -1.57715 -7.58984 -3.51953 -10.0117l-10 -12.4902c-2.65234 -3.31152 -8.24707 -6 -12.4902 -6c-3.09961 0 -7.58008 1.57227 -10 3.50977l-598 467.49
c-3.31738 2.65137 -6.00977 8.25098 -6.00977 12.498c0 3.10449 1.57715 7.58984 3.51953 10.0117l10 12.4902c2.65234 3.31152 8.24707 6 12.4902 6c3.09961 0 7.58008 -1.57227 10 -3.50977zM296.79 301.53c6.33496 1.35059 16.7324 2.45801 23.21 2.46973
c60.4805 0 109.36 -47.9102 111.58 -107.85zM343.21 82.46c-6.33496 -1.34375 -16.7334 -2.44629 -23.21 -2.45996c-60.4697 0 -109.35 47.9102 -111.58 107.84zM320 336c-19.8799 0 -39.2803 -2.7998 -58.2197 -7.09961l-46.4102 36.29
c32.9199 11.8096 67.9297 18.8096 104.63 18.8096c122.93 0 230.29 -71.5898 284.57 -177.4c1.91992 -3.79883 3.47949 -10.3379 3.47949 -14.5947s-1.55957 -10.7959 -3.47949 -14.5947c-11.7197 -22.7598 -35.4189 -56.4092 -52.9004 -75.1104l-37.7402 29.5
c14.333 15.0156 34.0449 41.9854 44 60.2002c-48.8398 89 -139.279 144 -237.93 144zM320 48c19.8896 0 39.2803 2.7998 58.2197 7.08984l46.4102 -36.2803c-32.9199 -11.7598 -67.9297 -18.8096 -104.63 -18.8096c-122.92 0 -230.28 71.5898 -284.51 177.4
c-1.9209 3.79883 -3.47949 10.3379 -3.47949 14.5947s1.55859 10.7959 3.47949 14.5947c11.7168 22.7568 35.4111 56.4014 52.8896 75.1006l37.7402 -29.5c-14.3467 -15.0107 -34.0811 -41.9756 -44.0498 -60.1904c48.8496 -89 139.279 -144 237.93 -144z" />
d="M634 -23c3.66895 -2.93262 6.00391 -7.45117 6.00391 -12.5088c0 -3.7832 -1.31543 -7.26074 -3.51367 -10.001l-10 -12.4902c-2.93359 -3.66309 -7.44824 -5.99414 -12.502 -5.99414c-3.77637 0 -7.25 1.31152 -9.98828 3.50391l-598 467.49
c-3.66895 2.93262 -6.00391 7.45117 -6.00391 12.5088c0 3.7832 1.31543 7.26074 3.51367 10.001l10 12.4902c2.93359 3.66309 7.44824 5.99414 12.502 5.99414c3.77637 0 7.25 -1.31152 9.98828 -3.50391zM296.79 301.53c7.51172 1.60254 15.2266 2.45508 23.21 2.46973
c60.4805 0 109.36 -47.9102 111.58 -107.85zM343.21 82.46c-7.51367 -1.59375 -15.2285 -2.44336 -23.21 -2.45996c-60.4697 0 -109.35 47.9102 -111.58 107.84zM320 336c-19.8799 0 -39.2803 -2.7998 -58.2197 -7.09961l-46.4102 36.29
c32.9199 11.8096 67.9297 18.8096 104.63 18.8096c122.93 0 230.29 -71.5898 284.57 -177.4c2.21289 -4.37793 3.45996 -9.38965 3.45996 -14.626c0 -5.2373 -1.24707 -10.1855 -3.45996 -14.5635c-14.1924 -27.5625 -31.9229 -52.6689 -52.9004 -75.1104l-37.7402 29.5
c17.2305 18.0527 31.9385 38.1318 44 60.2002c-48.8398 89 -139.279 144 -237.93 144zM320 48c19.8896 0 39.2803 2.7998 58.2197 7.08984l46.4102 -36.2803c-32.9199 -11.7598 -67.9297 -18.8096 -104.63 -18.8096c-122.92 0 -230.28 71.5898 -284.51 177.4
c-2.21387 4.37793 -3.46094 9.38965 -3.46094 14.626c0 5.2373 1.24707 10.1855 3.46094 14.5635c14.1885 27.5586 31.916 52.6621 52.8896 75.1006l37.7402 -29.5c-17.249 -18.0469 -31.9727 -38.1221 -44.0498 -60.1904c48.8496 -89 139.279 -144 237.93 -144z" />
<glyph glyph-name="calendar-alt" unicode="&#xf073;" horiz-adv-x="448"
d="M148 160h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12zM256 172c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40
c6.59961 0 12 -5.40039 12 -12v-40zM352 172c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40zM256 76c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40
@@ -131,47 +131,47 @@ d="M527.9 416c26.5996 0 48.0996 -21.5 48.0996 -48v-352c0 -26.5 -21.5 -48 -48.099
h-467.801zM521.9 16c3.2998 0 6 2.7002 6 6v170h-479.801v-170c0 -3.2998 2.7002 -6 6 -6h467.801zM192 116v-40c0 -6.59961 -5.40039 -12 -12 -12h-72c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h72c6.59961 0 12 -5.40039 12 -12zM384 116v-40
c0 -6.59961 -5.40039 -12 -12 -12h-136c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h136c6.59961 0 12 -5.40039 12 -12z" />
<glyph glyph-name="hdd" unicode="&#xf0a0;" horiz-adv-x="576"
d="M567.403 212.358c5.59668 -8.04688 8.59668 -17.6113 8.59668 -27.4121v-136.946c0 -26.5098 -21.4902 -48 -48 -48h-480c-26.5098 0 -48 21.4902 -48 48v136.946c0 8.30957 3.85156 20.5898 8.59668 27.4121l105.08 151.053
c7.90625 11.3652 25.5596 20.5889 39.4033 20.5889h0.000976562h269.838h0.000976562c13.8438 0 31.4971 -9.22363 39.4033 -20.5889zM153.081 336l-77.9131 -112h425.664l-77.9131 112h-269.838zM528 48v128h-480v-128h480zM496 112c0 -17.6729 -14.3271 -32 -32 -32
s-32 14.3271 -32 32s14.3271 32 32 32s32 -14.3271 32 -32zM400 112c0 -17.6729 -14.3271 -32 -32 -32s-32 14.3271 -32 32s14.3271 32 32 32s32 -14.3271 32 -32z" />
d="M567.403 212.358c5.59668 -8.04688 8.59668 -17.6113 8.59668 -27.4121v-136.946c0 -26.5098 -21.4902 -48 -48 -48h-480c-26.5098 0 -48 21.4902 -48 48v136.946c0 10.167 3.19531 19.6465 8.59668 27.4121l105.08 151.053
c8.67383 12.4678 23.0791 20.5889 39.4043 20.5889h269.838c16.3252 0 30.7305 -8.12109 39.4043 -20.5889zM153.081 336l-77.9131 -112h425.664l-77.9131 112h-269.838zM528 48v128h-480v-128h480zM496 112c0 -17.6729 -14.3271 -32 -32 -32s-32 14.3271 -32 32
s14.3271 32 32 32s32 -14.3271 32 -32zM400 112c0 -17.6729 -14.3271 -32 -32 -32s-32 14.3271 -32 32s14.3271 32 32 32s32 -14.3271 32 -32z" />
<glyph glyph-name="hand-point-right" unicode="&#xf0a4;"
d="M428.8 310.4c45.0996 0 83.2002 -38.1016 83.2002 -83.2002c0 -45.6162 -37.7646 -83.2002 -83.2002 -83.2002h-35.6475c-1.41602 -6.36719 -4.96875 -16.252 -7.92969 -22.0645c2.50586 -22.0059 -3.50293 -44.9775 -15.9844 -62.791
d="M428.8 310.4c45.0996 0 83.2002 -38.1016 83.2002 -83.2002c0 -45.6162 -37.7646 -83.2002 -83.2002 -83.2002h-35.6475c-1.71387 -7.70605 -4.43555 -15.2051 -7.92969 -22.0645c2.50586 -22.0059 -3.50293 -44.9775 -15.9844 -62.791
c-1.14062 -52.4863 -37.3984 -91.1445 -99.9404 -91.1445h-21.2988c-60.0635 0 -98.5117 40 -127.2 40h-2.67871c-5.74707 -4.95215 -13.5361 -8 -22.1201 -8h-64c-17.6729 0 -32 12.8936 -32 28.7998v230.4c0 15.9062 14.3271 28.7998 32 28.7998h64.001
c8.58398 0 16.373 -3.04785 22.1201 -8h2.67871c6.96387 0 14.8623 6.19336 30.1816 23.6689l0.128906 0.148438l0.130859 0.145508c8.85645 9.93652 18.1162 20.8398 25.8506 33.2529c18.7051 30.2471 30.3936 78.7842 75.707 78.7842c56.9277 0 92 -35.2861 92 -83.2002
v-0.0839844c0 -6.21777 -0.974609 -16.2148 -2.17578 -22.3154h86.1768zM428.8 192c18.9756 0 35.2002 16.2246 35.2002 35.2002c0 18.7002 -16.7754 35.2002 -35.2002 35.2002h-158.399c0 17.3242 26.3994 35.1992 26.3994 70.3994c0 26.4004 -20.625 35.2002 -44 35.2002
c-8.79395 0 -20.4443 -32.7119 -34.9258 -56.0996c-9.07422 -14.5752 -19.5244 -27.2256 -30.7988 -39.875c-16.1094 -18.374 -33.8359 -36.6328 -59.0752 -39.5967v-176.753c42.79 -3.7627 74.5088 -39.6758 120 -39.6758h21.2988
c0 -0.0283203 0 0.0361328 0 0.0078125c0 -7.66602 -0.748047 -15.1582 -2.17578 -22.4072h86.1768zM428.8 192c18.9756 0 35.2002 16.2246 35.2002 35.2002c0 18.7002 -16.7754 35.2002 -35.2002 35.2002h-158.399c0 17.3242 26.3994 35.1992 26.3994 70.3994
c0 26.4004 -20.625 35.2002 -44 35.2002c-8.79395 0 -20.4443 -32.7119 -34.9258 -56.0996c-9.07422 -14.5752 -19.5244 -27.2256 -30.7988 -39.875c-16.1094 -18.374 -33.8359 -36.6328 -59.0752 -39.5967v-176.753c42.79 -3.7627 74.5088 -39.6758 120 -39.6758h21.2988
c40.5244 0 57.124 22.1973 50.6006 61.3252c14.6113 8.00098 24.1514 33.9785 12.9248 53.625c19.3652 18.2246 17.7871 46.3809 4.9502 61.0498h91.0254zM88 64c0 13.2549 -10.7451 24 -24 24s-24 -10.7451 -24 -24s10.7451 -24 24 -24s24 10.7451 24 24z" />
<glyph glyph-name="hand-point-left" unicode="&#xf0a5;"
d="M0 227.2c0 45.0986 38.1006 83.2002 83.2002 83.2002h86.1758c-1.3623 6.91016 -2.17578 14.374 -2.17578 22.3994c0 47.9141 35.0723 83.2002 92 83.2002c45.3135 0 57.002 -48.5371 75.7061 -78.7852c7.73438 -12.4121 16.9951 -23.3154 25.8506 -33.2529
l0.130859 -0.145508l0.128906 -0.148438c15.3213 -17.4746 23.2197 -23.668 30.1836 -23.668h2.67871c5.74707 4.95215 13.5361 8 22.1201 8h64c17.6729 0 32 -12.8936 32 -28.7998v-230.4c0 -15.9062 -14.3271 -28.7998 -32 -28.7998h-64
c-8.58398 0 -16.373 3.04785 -22.1201 8h-2.67871c-28.6885 0 -67.1367 -40 -127.2 -40h-21.2988c-62.542 0 -98.8008 38.6582 -99.9404 91.1445c-12.4814 17.8135 -18.4922 40.7852 -15.9844 62.791c-2.96094 5.8125 -6.51367 15.6973 -7.92969 22.0645h-35.6465
c-8.58398 0 -16.373 3.04785 -22.1201 8h-2.67871c-28.6885 0 -67.1367 -40 -127.2 -40h-21.2988c-62.542 0 -98.8008 38.6582 -99.9404 91.1445c-12.4814 17.8135 -18.4922 40.7852 -15.9844 62.791c-3.49414 6.85938 -6.21582 14.3584 -7.92969 22.0645h-35.6465
c-45.4355 0 -83.2002 37.584 -83.2002 83.2002zM48 227.2c0 -18.9756 16.2246 -35.2002 35.2002 -35.2002h91.0244c-12.8369 -14.6689 -14.415 -42.8252 4.9502 -61.0498c-11.2256 -19.6465 -1.68652 -45.624 12.9248 -53.625
c-6.52246 -39.1279 10.0771 -61.3252 50.6016 -61.3252h21.2988c45.4912 0 77.21 35.9131 120 39.6768v176.752c-25.2393 2.96289 -42.9658 21.2227 -59.0752 39.5967c-11.2744 12.6494 -21.7246 25.2998 -30.7988 39.875
c-14.4814 23.3877 -26.1318 56.0996 -34.9258 56.0996c-23.375 0 -44 -8.7998 -44 -35.2002c0 -35.2002 26.3994 -53.0752 26.3994 -70.3994h-158.399c-18.4248 0 -35.2002 -16.5 -35.2002 -35.2002zM448 88c-13.2549 0 -24 -10.7451 -24 -24s10.7451 -24 24 -24
s24 10.7451 24 24s-10.7451 24 -24 24z" />
<glyph glyph-name="hand-point-up" unicode="&#xf0a6;" horiz-adv-x="448"
d="M105.6 364.8c0 45.0996 38.1016 83.2002 83.2002 83.2002c45.6162 0 83.2002 -37.7646 83.2002 -83.2002v-35.6465c6.36719 -1.41602 16.252 -4.96875 22.0645 -7.92969c22.0059 2.50684 44.9775 -3.50293 62.791 -15.9844
d="M105.6 364.8c0 45.0996 38.1016 83.2002 83.2002 83.2002c45.6162 0 83.2002 -37.7646 83.2002 -83.2002v-35.6465c7.70605 -1.71387 15.2051 -4.43555 22.0645 -7.92969c22.0059 2.50684 44.9775 -3.50293 62.791 -15.9844
c52.4863 -1.14062 91.1445 -37.3984 91.1445 -99.9404v-21.2988c0 -60.0635 -40 -98.5117 -40 -127.2v-2.67871c4.95215 -5.74707 8 -13.5361 8 -22.1201v-64c0 -17.6729 -12.8936 -32 -28.7998 -32h-230.4c-15.9062 0 -28.7998 14.3271 -28.7998 32v64
c0 8.58398 3.04785 16.373 8 22.1201v2.67871c0 6.96387 -6.19336 14.8623 -23.6689 30.1816l-0.148438 0.128906l-0.145508 0.130859c-9.93652 8.85645 -20.8398 18.1162 -33.2529 25.8506c-30.2471 18.7051 -78.7842 30.3936 -78.7842 75.707
c0 56.9277 35.2861 92 83.2002 92h0.0839844c6.21777 0 16.2148 -0.974609 22.3154 -2.17578v86.1768zM224 364.8c0 18.9756 -16.2246 35.2002 -35.2002 35.2002c-18.7002 0 -35.2002 -16.7754 -35.2002 -35.2002v-158.399c-17.3242 0 -35.1992 26.3994 -70.3994 26.3994
c-26.4004 0 -35.2002 -20.625 -35.2002 -44c0 -8.79395 32.7119 -20.4443 56.0996 -34.9258c14.5752 -9.07422 27.2256 -19.5244 39.875 -30.7988c18.374 -16.1094 36.6328 -33.8359 39.5967 -59.0752h176.753c3.7627 42.79 39.6758 74.5088 39.6758 120v21.2988
c0 40.5244 -22.1973 57.124 -61.3252 50.6006c-8.00098 14.6113 -33.9785 24.1514 -53.625 12.9248c-18.2246 19.3652 -46.3809 17.7871 -61.0498 4.9502v91.0254zM352 24c-13.2549 0 -24 -10.7451 -24 -24s10.7451 -24 24 -24s24 10.7451 24 24s-10.7451 24 -24 24z" />
c0 56.9277 35.2861 92 83.2002 92c0.0283203 0 -0.0361328 0 -0.0078125 0c7.66602 0 15.1582 -0.748047 22.4072 -2.17578v86.1768zM224 364.8c0 18.9756 -16.2246 35.2002 -35.2002 35.2002c-18.7002 0 -35.2002 -16.7754 -35.2002 -35.2002v-158.399
c-17.3242 0 -35.1992 26.3994 -70.3994 26.3994c-26.4004 0 -35.2002 -20.625 -35.2002 -44c0 -8.79395 32.7119 -20.4443 56.0996 -34.9258c14.5752 -9.07422 27.2256 -19.5244 39.875 -30.7988c18.374 -16.1094 36.6328 -33.8359 39.5967 -59.0752h176.753
c3.7627 42.79 39.6758 74.5088 39.6758 120v21.2988c0 40.5244 -22.1973 57.124 -61.3252 50.6006c-8.00098 14.6113 -33.9785 24.1514 -53.625 12.9248c-18.2246 19.3652 -46.3809 17.7871 -61.0498 4.9502v91.0254zM352 24c-13.2549 0 -24 -10.7451 -24 -24
s10.7451 -24 24 -24s24 10.7451 24 24s-10.7451 24 -24 24z" />
<glyph glyph-name="hand-point-down" unicode="&#xf0a7;" horiz-adv-x="448"
d="M188.8 -64c-45.0986 0 -83.2002 38.1006 -83.2002 83.2002v86.1758c-6.91016 -1.3623 -14.374 -2.17578 -22.3994 -2.17578c-47.9141 0 -83.2002 35.0723 -83.2002 92c0 45.3135 48.5371 57.002 78.7852 75.707c12.4121 7.73438 23.3154 16.9951 33.2529 25.8506
l0.145508 0.130859l0.148438 0.128906c17.4746 15.3213 23.668 23.2197 23.668 30.1836v2.67871c-4.95215 5.74707 -8 13.5361 -8 22.1201v64c0 17.6729 12.8936 32 28.7998 32h230.4c15.9062 0 28.7998 -14.3271 28.7998 -32v-64.001
c0 -8.58398 -3.04785 -16.373 -8 -22.1201v-2.67871c0 -28.6885 40 -67.1367 40 -127.2v-21.2988c0 -62.542 -38.6582 -98.8008 -91.1445 -99.9404c-17.8135 -12.4814 -40.7852 -18.4922 -62.791 -15.9844c-5.8125 -2.96094 -15.6973 -6.51367 -22.0645 -7.92969v-35.6465
c0 -8.58398 -3.04785 -16.373 -8 -22.1201v-2.67871c0 -28.6885 40 -67.1367 40 -127.2v-21.2988c0 -62.542 -38.6582 -98.8008 -91.1445 -99.9404c-17.8135 -12.4814 -40.7852 -18.4922 -62.791 -15.9844c-6.85938 -3.49414 -14.3584 -6.21582 -22.0645 -7.92969v-35.6465
c0 -45.4355 -37.584 -83.2002 -83.2002 -83.2002zM188.8 -16c18.9756 0 35.2002 16.2246 35.2002 35.2002v91.0244c14.6689 -12.8369 42.8252 -14.415 61.0498 4.9502c19.6465 -11.2256 45.624 -1.68652 53.625 12.9248c39.1279 -6.52246 61.3252 10.0771 61.3252 50.6016
v21.2988c0 45.4912 -35.9131 77.21 -39.6768 120h-176.752c-2.96289 -25.2393 -21.2227 -42.9658 -39.5967 -59.0752c-12.6494 -11.2744 -25.2998 -21.7246 -39.875 -30.7988c-23.3877 -14.4814 -56.0996 -26.1318 -56.0996 -34.9258c0 -23.375 8.7998 -44 35.2002 -44
c35.2002 0 53.0752 26.3994 70.3994 26.3994v-158.399c0 -18.4248 16.5 -35.2002 35.2002 -35.2002zM328 384c0 -13.2549 10.7451 -24 24 -24s24 10.7451 24 24s-10.7451 24 -24 24s-24 -10.7451 -24 -24z" />
<glyph glyph-name="copy" unicode="&#xf0c5;" horiz-adv-x="448"
d="M433.941 382.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-268.118c0 -26.5098 -21.4902 -48 -48 -48h-80v-48c0 -26.5098 -21.4902 -48 -48 -48h-224c-26.5098 0 -48 21.4902 -48 48v320c0 26.5098 21.4902 48 48 48h80v48c0 26.5098 21.4902 48 48 48
h172.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM266 -16c3.31152 0 6 2.68848 6 6v42h-96c-26.5098 0 -48 21.4902 -48 48v224h-74c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h212zM394 80c3.31152 0 6 2.68848 6 6v202h-88
c-13.2549 0 -24 10.7451 -24 24v88h-106c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h212zM400 336v9.63184v0.000976562c0 1.37207 -0.787109 3.27246 -1.75684 4.24219l-48.3682 48.3682c-1.12598 1.125 -2.65234 1.75684 -4.24316 1.75684h-9.63184
v-64h64z" />
d="M433.941 382.059c8.68848 -8.68848 14.0586 -20.6943 14.0586 -33.9404v-268.118c0 -26.5098 -21.4902 -48 -48 -48h-80v-48c0 -26.5098 -21.4902 -48 -48 -48h-224c-26.5098 0 -48 21.4902 -48 48v320c0 26.5098 21.4902 48 48 48h80v48c0 26.5098 21.4902 48 48 48
h172.118c13.2461 0 25.252 -5.37012 33.9404 -14.0586zM266 -16c3.31152 0 6 2.68848 6 6v42h-96c-26.5098 0 -48 21.4902 -48 48v224h-74c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h212zM394 80c3.31152 0 6 2.68848 6 6v202h-88
c-13.2549 0 -24 10.7451 -24 24v88h-106c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h212zM400 336v9.63184c0 1.65527 -0.670898 3.15723 -1.75684 4.24316l-48.3682 48.3682c-1.12598 1.125 -2.65234 1.75684 -4.24316 1.75684h-9.63184v-64h64z" />
<glyph glyph-name="save" unicode="&#xf0c7;" horiz-adv-x="448"
d="M433.941 318.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-268.118c0 -26.5098 -21.4902 -48 -48 -48h-352c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h268.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM272 368h-128v-80h128v80
zM394 16c3.31152 0 6 2.68848 6 6v259.632v0.000976562c0 1.37207 -0.787109 3.27246 -1.75684 4.24219l-78.2432 78.2432v-100.118c0 -13.2549 -10.7451 -24 -24 -24h-176c-13.2549 0 -24 10.7451 -24 24v104h-42c-3.31152 0 -6 -2.68848 -6 -6v-340
c0 -3.31152 2.68848 -6 6 -6h340zM224 216c48.5234 0 88 -39.4766 88 -88s-39.4766 -88 -88 -88s-88 39.4766 -88 88s39.4766 88 88 88zM224 88c22.0557 0 40 17.9443 40 40s-17.9443 40 -40 40s-40 -17.9443 -40 -40s17.9443 -40 40 -40z" />
d="M433.941 318.059c8.68848 -8.68848 14.0586 -20.6943 14.0586 -33.9404v-268.118c0 -26.5098 -21.4902 -48 -48 -48h-352c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h268.118c13.2461 0 25.252 -5.37012 33.9404 -14.0586zM272 368h-128v-80h128v80z
M394 16c3.31152 0 6 2.68848 6 6v259.632c0 1.65527 -0.670898 3.15723 -1.75684 4.24316l-78.2432 78.2432v-100.118c0 -13.2549 -10.7451 -24 -24 -24h-176c-13.2549 0 -24 10.7451 -24 24v104h-42c-3.31152 0 -6 -2.68848 -6 -6v-340c0 -3.31152 2.68848 -6 6 -6h340z
M224 216c48.5234 0 88 -39.4766 88 -88s-39.4766 -88 -88 -88s-88 39.4766 -88 88s39.4766 88 88 88zM224 88c22.0557 0 40 17.9443 40 40s-17.9443 40 -40 40s-40 -17.9443 -40 -40s17.9443 -40 40 -40z" />
<glyph glyph-name="square" unicode="&#xf0c8;" horiz-adv-x="448"
d="M400 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h352zM394 16c3.2998 0 6 2.7002 6 6v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h340z" />
<glyph glyph-name="envelope" unicode="&#xf0e0;"
@@ -181,7 +181,7 @@ c-22.5439 -17.748 -60.3359 -55.1787 -103.053 -54.9473c-42.9277 -0.231445 -81.205
<glyph glyph-name="lightbulb" unicode="&#xf0eb;" horiz-adv-x="352"
d="M176 368c8.83984 0 16 -7.16016 16 -16s-7.16016 -16 -16 -16c-35.2803 0 -64 -28.7002 -64 -64c0 -8.83984 -7.16016 -16 -16 -16s-16 7.16016 -16 16c0 52.9404 43.0596 96 96 96zM96.0596 -11.1699l-0.0400391 43.1797h159.961l-0.0507812 -43.1797
c-0.00976562 -3.13965 -0.939453 -6.21973 -2.67969 -8.83984l-24.5098 -36.8398c-2.95996 -4.45996 -7.95996 -7.14062 -13.3203 -7.14062h-78.8496c-5.35059 0 -10.3506 2.68066 -13.3203 7.14062l-24.5098 36.8398c-1.75 2.62012 -2.68066 5.68945 -2.68066 8.83984z
M176 448c97.2002 0 176 -78.7998 176 -176c0 -44.3701 -16.4502 -84.8496 -43.5498 -115.79c-16.6406 -18.9795 -42.7402 -58.79 -52.4199 -92.1602v-0.0498047h-48v0.0996094c0.00390625 4.04199 0.999023 10.4482 2.21973 14.3008
M176 448c97.2002 0 176 -78.7998 176 -176c0 -44.3701 -16.4502 -84.8496 -43.5498 -115.79c-16.6406 -18.9795 -42.7402 -58.79 -52.4199 -92.1602v-0.0498047h-48v0.0996094c0.00488281 4.98145 0.790039 9.78809 2.21973 14.3008
c5.67969 17.9893 22.9902 64.8496 62.0996 109.46c20.4102 23.29 31.6504 53.1699 31.6504 84.1396c0 70.5801 -57.4199 128 -128 128c-68.2803 0 -128.15 -54.3604 -127.95 -128c0.0898438 -30.9902 11.0703 -60.71 31.6104 -84.1396
c39.3496 -44.9004 56.5801 -91.8604 62.1699 -109.67c1.42969 -4.56055 2.13965 -9.30078 2.15039 -14.0703v-0.120117h-48v0.0595703c-9.68066 33.3604 -35.7803 73.1709 -52.4209 92.1602c-27.1094 30.9307 -43.5596 71.4102 -43.5596 115.78
c0 93.0303 73.7197 176 176 176z" />
@@ -241,13 +241,13 @@ c4.70508 4.66699 12.3027 4.63672 16.9697 -0.0683594l22.5361 -22.7178c4.66699 -4.
<glyph glyph-name="share-square" unicode="&#xf14d;" horiz-adv-x="576"
d="M561.938 289.94c18.75 -18.7402 18.75 -49.1406 0 -67.8809l-143.998 -144c-29.9727 -29.9727 -81.9404 -9.05273 -81.9404 33.9404v53.7998c-101.266 -7.83691 -99.625 -31.6406 -84.1104 -78.7598c14.2285 -43.0889 -33.4736 -79.248 -71.0195 -55.7402
c-51.6924 32.3057 -84.8701 83.0635 -84.8701 144.76c0 39.3408 12.2197 72.7402 36.3301 99.3008c19.8398 21.8398 47.7402 38.4697 82.9102 49.4199c36.7295 11.4395 78.3096 16.1094 120.76 17.9893v57.1982c0 42.9355 51.9258 63.9541 81.9404 33.9404zM384 112l144 144
l-144 144v-104.09c-110.86 -0.90332 -240 -10.5166 -240 -119.851c0 -52.1396 32.79 -85.6094 62.3096 -104.06c-39.8174 120.65 48.999 141.918 177.69 143.84v-103.84zM408.74 27.5068c6.14844 1.75684 15.5449 5.92383 20.9736 9.30273
l-144 144v-104.09c-110.86 -0.90332 -240 -10.5166 -240 -119.851c0 -52.1396 32.79 -85.6094 62.3096 -104.06c-39.8174 120.65 48.999 141.918 177.69 143.84v-103.84zM408.74 27.5068c7.4375 2.125 14.5508 5.30566 20.9736 9.30273
c7.97656 4.95215 18.2861 -0.825195 18.2861 -10.2139v-42.5957c0 -26.5098 -21.4902 -48 -48 -48h-352c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h132c6.62695 0 12 -5.37305 12 -12v-4.48633c0 -4.91699 -2.9873 -9.36914 -7.56934 -11.1514
c-13.7021 -5.33105 -26.3955 -11.5371 -38.0498 -18.585c-1.59668 -0.974609 -4.41016 -1.77051 -6.28027 -1.77734h-86.1006c-3.31152 0 -6 -2.68848 -6 -6v-340c0 -3.31152 2.68848 -6 6 -6h340c3.31152 0 6 2.68848 6 6v25.9658c0 5.37012 3.5791 10.0596 8.74023 11.541
z" />
c-13.7021 -5.33105 -26.3955 -11.5371 -38.0498 -18.585c-1.82715 -1.11523 -3.98633 -1.76953 -6.28027 -1.77734h-86.1006c-3.31152 0 -6 -2.68848 -6 -6v-340c0 -3.31152 2.68848 -6 6 -6h340c3.31152 0 6 2.68848 6 6v25.9658c0 5.37012 3.5791 10.0596 8.74023 11.541z
" />
<glyph glyph-name="compass" unicode="&#xf14e;" horiz-adv-x="496"
d="M347.94 318.14c16.6592 7.61035 33.8096 -9.54004 26.1992 -26.1992l-65.9697 -144.341c-2.73047 -5.97363 -9.7959 -13.0391 -15.7695 -15.7695l-144.341 -65.9697c-16.6592 -7.61035 -33.8096 9.5498 -26.1992 26.1992l65.9697 144.341
c2.73047 5.97363 9.7959 13.0391 15.7695 15.7695zM270.58 169.42c12.4697 12.4697 12.4697 32.6904 0 45.1602s-32.6904 12.4697 -45.1602 0s-12.4697 -32.6904 0 -45.1602s32.6904 -12.4697 45.1602 0zM248 440c136.97 0 248 -111.03 248 -248s-111.03 -248 -248 -248
d="M347.94 318.14c16.6592 7.61035 33.8096 -9.54004 26.1992 -26.1992l-65.9697 -144.341c-3.19238 -6.9834 -8.78613 -12.5771 -15.7695 -15.7695l-144.341 -65.9697c-16.6592 -7.61035 -33.8096 9.5498 -26.1992 26.1992l65.9697 144.341
c3.19238 6.9834 8.78613 12.5771 15.7695 15.7695zM270.58 169.42c12.4697 12.4697 12.4697 32.6904 0 45.1602s-32.6904 12.4697 -45.1602 0s-12.4697 -32.6904 0 -45.1602s32.6904 -12.4697 45.1602 0zM248 440c136.97 0 248 -111.03 248 -248s-111.03 -248 -248 -248
s-248 111.03 -248 248s111.03 248 248 248zM248 -8c110.28 0 200 89.7197 200 200s-89.7197 200 -200 200s-200 -89.7197 -200 -200s89.7197 -200 200 -200z" />
<glyph glyph-name="caret-square-down" unicode="&#xf150;" horiz-adv-x="448"
d="M125.1 240h197.801c10.6992 0 16.0996 -13 8.5 -20.5l-98.9004 -98.2998c-4.7002 -4.7002 -12.2002 -4.7002 -16.9004 0l-98.8994 98.2998c-7.7002 7.5 -2.2998 20.5 8.39941 20.5zM448 368v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352
@@ -287,7 +287,7 @@ l40.4004 -59.8994l70.8994 13.6992c13 2.60059 26.6006 -1.59961 36.2002 -11.0996c9
l-91 17.5996l17.5996 -91.2002l-76.7998 -52l76.7998 -52l-17.5996 -91.1992l90.8994 17.5996l51.9004 -77l51.9004 76.9004l91 -17.6006zM256 296c57.2998 0 104 -46.7002 104 -104s-46.7002 -104 -104 -104s-104 46.7002 -104 104s46.7002 104 104 104zM256 136
c30.9004 0 56 25.0996 56 56s-25.0996 56 -56 56s-56 -25.0996 -56 -56s25.0996 -56 56 -56z" />
<glyph glyph-name="moon" unicode="&#xf186;"
d="M279.135 -64c-141.424 0 -256 114.64 -256 256c0 141.425 114.641 256 256 256c13.0068 -0.00195312 33.9443 -1.91797 46.7354 -4.27734c44.0205 -8.13086 53.7666 -66.8691 15.0215 -88.9189c-41.374 -23.5439 -67.4336 -67.4121 -67.4336 -115.836
d="M279.135 -64c-141.424 0 -256 114.64 -256 256c0 141.425 114.641 256 256 256c16.0342 -0.00292969 31.5078 -1.46875 46.7354 -4.27734c44.0205 -8.13086 53.7666 -66.8691 15.0215 -88.9189c-41.374 -23.5439 -67.4336 -67.4121 -67.4336 -115.836
c0 -83.5234 75.9238 -146.475 158.272 -130.792c43.6904 8.32129 74.5186 -42.5693 46.248 -77.4004c-47.8613 -58.9717 -120.088 -94.7754 -198.844 -94.7754zM279.135 400c-114.875 0 -208 -93.125 -208 -208s93.125 -208 208 -208
c65.2314 0 123.439 30.0361 161.575 77.0244c-111.611 -21.2568 -215.252 64.0957 -215.252 177.943c0 67.5127 36.9326 126.392 91.6934 157.555c-12.3271 2.27637 -25.0312 3.47754 -38.0166 3.47754z" />
<glyph glyph-name="caret-square-left" unicode="&#xf191;" horiz-adv-x="448"
@@ -334,12 +334,12 @@ c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM256 396.1v-76.0996h76.0996zM336 -1
c-33.2002 0 -58 30.4004 -51.4004 62.9004l19.7002 97.0996v32h32v-32h22.1006c5.7998 0 10.6992 -4.09961 11.7998 -9.7002zM160.3 57.9004c17.9004 0 32.4004 12.0996 32.4004 27c0 14.8994 -14.5 27 -32.4004 27c-17.8994 0 -32.3994 -12.1006 -32.3994 -27
c0 -14.9004 14.5 -27 32.3994 -27zM192.3 256v-32h-32v32h32z" />
<glyph glyph-name="file-audio" unicode="&#xf1c7;" horiz-adv-x="384"
d="M369.941 350.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-332.118c0 -26.5098 -21.4902 -48 -48 -48h-288c-26.5098 0 -48 21.4902 -48 48v416c0 26.5098 21.4902 48 48 48h204.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM332.118 320
d="M369.941 350.059c8.68848 -8.68848 14.0586 -20.6943 14.0586 -33.9404v-332.118c0 -26.5098 -21.4902 -48 -48 -48h-288c-26.5098 0 -48 21.4902 -48 48v416c0 26.5098 21.4902 48 48 48h204.118c13.2461 0 25.252 -5.37012 33.9404 -14.0586zM332.118 320
l-76.1182 76.1182v-76.1182h76.1182zM48 -16h288v288h-104c-13.2549 0 -24 10.7451 -24 24v104h-160v-416zM192 60.0244c0 -10.6914 -12.9258 -16.0459 -20.4854 -8.48535l-35.5146 35.9746h-28c-6.62695 0 -12 5.37305 -12 12v56c0 6.62695 5.37305 12 12 12h28
l35.5146 36.9473c7.56055 7.56055 20.4854 2.20605 20.4854 -8.48535v-135.951zM233.201 107.154c9.05078 9.29688 9.05957 24.1328 0.000976562 33.4385c-22.1494 22.752 12.2344 56.2461 34.3945 33.4814c27.1982 -27.9404 27.2119 -72.4443 0.000976562 -100.401
c-21.793 -22.3857 -56.9463 10.3154 -34.3965 33.4814z" />
<glyph glyph-name="file-video" unicode="&#xf1c8;" horiz-adv-x="384"
d="M369.941 350.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-332.118c0 -26.5098 -21.4902 -48 -48 -48h-288c-26.5098 0 -48 21.4902 -48 48v416c0 26.5098 21.4902 48 48 48h204.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM332.118 320
d="M369.941 350.059c8.68848 -8.68848 14.0586 -20.6943 14.0586 -33.9404v-332.118c0 -26.5098 -21.4902 -48 -48 -48h-288c-26.5098 0 -48 21.4902 -48 48v416c0 26.5098 21.4902 48 48 48h204.118c13.2461 0 25.252 -5.37012 33.9404 -14.0586zM332.118 320
l-76.1182 76.1182v-76.1182h76.1182zM48 -16h288v288h-104c-13.2549 0 -24 10.7451 -24 24v104h-160v-416zM276.687 195.303c10.0049 10.0049 27.3135 2.99707 27.3135 -11.3135v-111.976c0 -14.2939 -17.2959 -21.332 -27.3135 -11.3135l-52.6865 52.6738v-37.374
c0 -11.0459 -8.9541 -20 -20 -20h-104c-11.0459 0 -20 8.9541 -20 20v104c0 11.0459 8.9541 20 20 20h104c11.0459 0 20 -8.9541 20 -20v-37.374z" />
<glyph glyph-name="file-code" unicode="&#xf1c9;" horiz-adv-x="384"
@@ -376,9 +376,9 @@ c73.46 -15.2598 127.939 -77.46 127.939 -155.16c0 -41.3604 6.03027 -70.7197 14.33
c-35.3203 0 -63.9697 28.6504 -63.9697 64h127.939c0 -35.3496 -28.6494 -64 -63.9697 -64z" />
<glyph glyph-name="copyright" unicode="&#xf1f9;"
d="M256 440c136.967 0 248 -111.033 248 -248s-111.033 -248 -248 -248s-248 111.033 -248 248s111.033 248 248 248zM256 -8c110.549 0 200 89.4678 200 200c0 110.549 -89.4678 200 -200 200c-110.549 0 -200 -89.4688 -200 -200c0 -110.549 89.4678 -200 200 -200z
M363.351 93.0645c-9.61328 -9.71289 -45.5293 -41.3965 -104.064 -41.3965c-82.4297 0 -140.484 61.4248 -140.484 141.567c0 79.1514 60.2754 139.4 139.763 139.4c55.5303 0 88.7373 -26.6201 97.5928 -34.7783c2.13379 -1.96289 3.86523 -5.9082 3.86523 -8.80762
c0 -1.95508 -0.864258 -4.87402 -1.92969 -6.51465l-18.1543 -28.1133c-3.8418 -5.9502 -11.9668 -7.28223 -17.499 -2.9209c-8.5957 6.77637 -31.8145 22.5381 -61.708 22.5381c-48.3037 0 -77.916 -35.3301 -77.916 -80.082c0 -41.5889 26.8877 -83.6924 78.2764 -83.6924
c32.6572 0 56.8428 19.0391 65.7266 27.2256c5.26953 4.85645 13.5957 4.03906 17.8193 -1.73828l19.8652 -27.1699c1.28613 -1.74512 2.33008 -4.91992 2.33008 -7.08789c0 -2.72363 -1.56055 -6.5 -3.48242 -8.42969z" />
M363.351 93.0645c-9.61328 -9.71289 -45.5293 -41.3965 -104.064 -41.3965c-82.4297 0 -140.484 61.4248 -140.484 141.567c0 79.1514 60.2754 139.4 139.763 139.4c55.5303 0 88.7373 -26.6201 97.5928 -34.7783c2.37793 -2.1875 3.86914 -5.3252 3.86914 -8.80762
c0 -2.39746 -0.717773 -4.64258 -1.93359 -6.51465l-18.1543 -28.1133c-3.8418 -5.9502 -11.9668 -7.28223 -17.499 -2.9209c-8.5957 6.77637 -31.8145 22.5381 -61.708 22.5381c-48.3037 0 -77.916 -35.3301 -77.916 -80.082c0 -41.5889 26.8877 -83.6924 78.2764 -83.6924
c32.6572 0 56.8428 19.0391 65.7266 27.2256c5.26953 4.85645 13.5957 4.03906 17.8193 -1.73828l19.8652 -27.1699c1.45996 -1.98145 2.32422 -4.42969 2.32422 -7.07715c0 -3.28809 -1.32422 -6.2793 -3.47656 -8.44043z" />
<glyph glyph-name="closed-captioning" unicode="&#xf20a;"
d="M464 384c26.5 0 48 -21.5 48 -48v-288c0 -26.5 -21.5 -48 -48 -48h-416c-26.5 0 -48 21.5 -48 48v288c0 26.5 21.5 48 48 48h416zM458 48c3.2998 0 6 2.7002 6 6v276c0 3.2998 -2.7002 6 -6 6h-404c-3.2998 0 -6 -2.7002 -6 -6v-276c0 -3.2998 2.7002 -6 6 -6h404z
M246.9 133.7c1.69922 -2.40039 1.5 -5.60059 -0.5 -7.7002c-53.6006 -56.7998 -172.801 -32.0996 -172.801 67.9004c0 97.2998 121.7 119.5 172.5 70.0996c2.10059 -2 2.5 -3.2002 1 -5.7002l-17.5 -30.5c-1.89941 -3.09961 -6.19922 -4 -9.09961 -1.7002
@@ -398,7 +398,7 @@ c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12h-12v-24h88v12c
h-32v-32h32zM96 136h224v12c0 6.62695 5.37305 12 12 12h12v160h-12c-6.62695 0 -12 5.37305 -12 12v12h-224v-12c0 -6.62695 -5.37305 -12 -12 -12h-12v-160h12c6.62695 0 12 -5.37305 12 -12v-12zM224 0v32h-32v-32h32zM504 64v160h-12c-6.62695 0 -12 5.37305 -12 12v12
h-88v-88h12c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12h-72c-6.62695 0 -12 5.37305 -12 12v12h-88v-24h12c6.62695 0 12 -5.37305 12 -12v-12h224v12c0 6.62695 5.37305 12 12 12h12zM544 0v32h-32v-32h32zM544 256v32h-32v-32h32z" />
<glyph glyph-name="sticky-note" unicode="&#xf249;" horiz-adv-x="448"
d="M448 99.8936c0 -10.9746 -6.29883 -26.1797 -14.0586 -33.9404l-83.8828 -83.8818c-7.75977 -7.76074 -22.9658 -14.0596 -33.9404 -14.0596h-268.118c-26.5098 0 -48 21.4902 -48 48v351.988c0 26.5098 21.4902 48 48 48h352c26.5098 0 48 -21.4902 48 -48v-268.106z
d="M448 99.8936c0 -13.2451 -5.37012 -25.252 -14.0586 -33.9404l-83.8828 -83.8818c-8.68848 -8.68848 -20.6943 -14.0596 -33.9404 -14.0596h-268.118c-26.5098 0 -48 21.4902 -48 48v351.988c0 26.5098 21.4902 48 48 48h352c26.5098 0 48 -21.4902 48 -48v-268.106z
M320 19.8936l76.1182 76.1182h-76.1182v-76.1182zM400 368h-352v-351.988h224v104c0 13.2549 10.7451 24 24 24h104v223.988z" />
<glyph glyph-name="clone" unicode="&#xf24d;"
d="M464 448c26.5098 0 48 -21.4902 48 -48v-320c0 -26.5098 -21.4902 -48 -48 -48h-48v-48c0 -26.5098 -21.4902 -48 -48 -48h-320c-26.5098 0 -48 21.4902 -48 48v320c0 26.5098 21.4902 48 48 48h48v48c0 26.5098 21.4902 48 48 48h320zM362 -16c3.31152 0 6 2.68848 6 6
@@ -412,11 +412,11 @@ d="M408.864 368.948c48.8213 20.751 103.136 -15.0723 103.136 -67.9111v-114.443c0
c-17.6729 0 -32 14.3271 -32 32c0 27.3301 1.1416 29.2012 -3.11035 32.9033l-97.71 85.0811c-24.8994 21.6797 -39.1797 52.8926 -39.1797 85.6338v56.9531c0 47.4277 44.8457 82.0215 91.0459 71.1807c1.96094 55.751 63.5107 87.8262 110.671 60.8057
c29.1895 31.0713 78.8604 31.4473 108.334 -0.0214844c32.7051 18.6846 76.4121 10.3096 98.8135 -23.5879zM464 186.594v114.445c0 34.29 -52 33.8232 -52 0.676758c0 -8.83594 -7.16309 -16 -16 -16h-7c-8.83691 0 -16 7.16406 -16 16v26.751
c0 34.457 -52 33.707 -52 0.676758v-27.4287c0 -8.83594 -7.16309 -16 -16 -16h-7c-8.83691 0 -16 7.16406 -16 16v40.4658c0 34.3525 -52 33.8115 -52 0.677734v-41.1436c0 -8.83594 -7.16406 -16 -16 -16h-7c-8.83594 0 -16 7.16406 -16 16v26.751
c0 34.4023 -52 33.7744 -52 0.676758v-116.571c0 -8.83203 -7.16797 -16 -16 -16c-3.30664 0 -8.01367 1.7627 -10.5068 3.93359l-7 6.09473c-3.03223 2.64062 -5.49316 8.04688 -5.49316 12.0674v0v41.2275c0 34.2148 -52 33.8857 -52 0.677734v-56.9531
c0 -18.8555 8.27441 -36.874 22.7002 -49.4365l97.71 -85.0801c12.4502 -10.8398 19.5898 -26.4463 19.5898 -42.8164v-10.2861h220v7.07617c0 13.21 2.65332 26.0791 7.88281 38.25l42.835 99.6553c2.91602 6.75391 5.28223 18.207 5.28223 25.5635v0.0488281z" />
c0 34.4023 -52 33.7744 -52 0.676758v-116.571c0 -8.83105 -7.17773 -15.9961 -16.0078 -15.9961c-4.0166 0 -7.68848 1.48242 -10.499 3.92969l-7 6.09473c-3.37012 2.93457 -5.49316 7.25293 -5.49316 12.0674v41.2275c0 34.2148 -52 33.8857 -52 0.677734v-56.9531
c0 -18.8555 8.27441 -36.874 22.7002 -49.4365l97.71 -85.0801c12.4502 -10.8398 19.5898 -26.4463 19.5898 -42.8164v-10.2861h220v7.07617c0 13.21 2.65332 26.0791 7.88281 38.25l42.835 99.6553c3.37891 7.82715 5.28223 16.501 5.28223 25.5625v0.0498047z" />
<glyph glyph-name="hand-paper" unicode="&#xf256;" horiz-adv-x="448"
d="M372.57 335.359c39.9062 5.63281 75.4297 -25.7393 75.4297 -66.3594v-131.564c-0.00195312 -12.7666 -2.33008 -33.2246 -5.19531 -45.666l-30.1836 -130.958c-3.34668 -14.5234 -16.2783 -24.8125 -31.1816 -24.8125h-222.897
c-9.10352 0 -20.7793 6.01758 -26.0615 13.4316l-119.97 168.415c-21.2441 29.8203 -14.8047 71.3574 14.5498 93.1533c18.7754 13.9395 42.1309 16.2979 62.083 8.87109v126.13c0 44.0547 41.125 75.5439 82.4053 64.9834c23.8926 48.1963 92.3535 50.2471 117.982 0.74707
d="M372.57 335.359c39.9062 5.63281 75.4297 -25.7393 75.4297 -66.3594v-131.564c-0.00292969 -15.7393 -1.80566 -30.9482 -5.19531 -45.666l-30.1836 -130.958c-3.34668 -14.5234 -16.2783 -24.8125 -31.1816 -24.8125h-222.897
c-10.7539 0 -20.2588 5.28613 -26.0615 13.4316l-119.97 168.415c-21.2441 29.8203 -14.8047 71.3574 14.5498 93.1533c18.7754 13.9395 42.1309 16.2979 62.083 8.87109v126.13c0 44.0547 41.125 75.5439 82.4053 64.9834c23.8926 48.1963 92.3535 50.2471 117.982 0.74707
c42.5186 11.1445 83.0391 -21.9346 83.0391 -65.5469v-10.8242zM399.997 137.437l-0.00195312 131.563c0 24.9492 -36.5703 25.5508 -36.5703 -0.691406v-76.3086c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16v154.184
c0 25.501 -36.5703 26.3633 -36.5703 0.691406v-154.875c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16v188.309c0 25.501 -36.5703 26.3545 -36.5703 0.691406v-189c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16
v153.309c0 25.501 -36.5713 26.3359 -36.5713 0.691406v-206.494c0 -15.5703 -20.0352 -21.9092 -29.0303 -9.2832l-27.1279 38.0791c-14.3711 20.1709 -43.833 -2.33496 -29.3945 -22.6045l115.196 -161.697h201.92l27.3252 118.551
@@ -424,45 +424,46 @@ c2.63086 11.417 3.96484 23.1553 3.96484 34.8857z" />
<glyph glyph-name="hand-scissors" unicode="&#xf257;"
d="M256 -32c-44.9561 0 -77.3428 43.2627 -64.0244 85.8535c-21.6484 13.71 -34.0156 38.7617 -30.3408 65.0068h-87.6348c-40.8037 0 -74 32.8105 -74 73.1406c0 40.3291 33.1963 73.1396 74 73.1396l94 -9.14062l-78.8496 18.6787
c-38.3076 14.7422 -57.04 57.4707 -41.9424 95.1123c15.0303 37.4736 57.7549 55.7803 95.6416 41.2012l144.929 -55.7568c24.9551 30.5566 57.8086 43.9932 92.2178 24.7324l97.999 -54.8525c20.9746 -11.7393 34.0049 -33.8457 34.0049 -57.6904v-205.702
c0 -30.7422 -21.4404 -57.5576 -51.7979 -64.5537l-118.999 -27.4268c-4.97168 -1.14648 -10.0889 -1.72949 -15.2031 -1.72949zM256 16.0127l70 -0.000976562c1.23633 0 3.21777 0.225586 4.42285 0.501953l119.001 27.4277
c8.58203 1.97754 14.5762 9.29102 14.5762 17.7812v205.701c0 6.4873 -3.62109 12.542 -9.44922 15.8047l-98 54.8545c-8.13965 4.55566 -18.668 2.61914 -24.4873 -4.50781l-21.7646 -26.6475c-2.65039 -3.24512 -8.20215 -5.87891 -12.3926 -5.87891
c-1.64062 0 -4.21484 0.477539 -5.74609 1.06738l-166.549 64.0908c-32.6543 12.5664 -50.7744 -34.5771 -19.2227 -46.7168l155.357 -59.7852c5.66016 -2.17773 10.2539 -8.86816 10.2539 -14.9326v0v-11.6328c0 -8.83691 -7.16309 -16 -16 -16h-182
c0 -30.7422 -21.4404 -57.5576 -51.7979 -64.5537l-118.999 -27.4268c-4.97168 -1.14648 -10.0889 -1.72949 -15.2031 -1.72949zM256 16.0127l70 -0.000976562c1.52441 0 2.99707 0.174805 4.42285 0.501953l119.001 27.4277
c8.58203 1.97754 14.5762 9.29102 14.5762 17.7812v205.701c0 6.4873 -3.62109 12.542 -9.44922 15.8047l-98 54.8545c-8.13965 4.55566 -18.668 2.61914 -24.4873 -4.50781l-21.7646 -26.6475c-2.93457 -3.59375 -7.40332 -5.87305 -12.4004 -5.87305
c-2.02246 0 -3.95703 0.375977 -5.73828 1.06152l-166.549 64.0908c-32.6543 12.5664 -50.7744 -34.5771 -19.2227 -46.7168l155.357 -59.7852c6 -2.30859 10.2539 -8.12402 10.2539 -14.9326v-11.6328c0 -8.83691 -7.16309 -16 -16 -16h-182
c-34.375 0 -34.4297 -50.2803 0 -50.2803h182c8.83691 0 16 -7.16309 16 -16v-6.85645c0 -8.83691 -7.16309 -16 -16 -16h-28c-25.1221 0 -25.1592 -36.5674 0 -36.5674h28c8.83691 0 16 -7.16211 16 -16v-6.85547c0 -8.83691 -7.16309 -16 -16 -16
c-25.1201 0 -25.1602 -36.5674 0 -36.5674z" />
<glyph glyph-name="hand-lizard" unicode="&#xf258;" horiz-adv-x="576"
d="M556.686 157.458c12.6357 -19.4863 19.3145 -42.0615 19.3145 -65.2871v-124.171h-224v71.582l-99.751 38.7871c-2.7832 1.08203 -5.70996 1.63086 -8.69727 1.63086h-131.552c-30.8789 0 -56 25.1211 -56 56c0 48.5234 39.4766 88 88 88h113.709l18.333 48h-196.042
c-44.1123 0 -80 35.8877 -80 80v8c0 30.8779 25.1211 56 56 56h293.917c24.5 0 47.084 -12.2725 60.4111 -32.8291zM528 16v76.1709v0.0478516c0 11.7461 -5.19141 29.2734 -11.5879 39.124l-146.358 225.715c-4.44336 6.85254 -11.9707 10.9424 -20.1367 10.9424h-293.917
c-4.41113 0 -8 -3.58887 -8 -8v-8c0 -17.6445 14.3555 -32 32 -32h213.471c25.2021 0 42.626 -25.293 33.6299 -48.8457l-24.5518 -64.2812c-7.05371 -18.4658 -25.0732 -30.873 -44.8398 -30.873h-113.709c-22.0557 0 -40 -17.9443 -40 -40c0 -4.41113 3.58887 -8 8 -8
h131.552h0.0517578c7.44141 0 19.1074 -2.19238 26.041 -4.89355l99.752 -38.7881c18.5898 -7.22852 30.6035 -24.7881 30.6035 -44.7363v-23.582h128z" />
c-44.1123 0 -80 35.8877 -80 80v8c0 30.8779 25.1211 56 56 56h293.917c24.5 0 47.084 -12.2725 60.4111 -32.8291zM528 16v76.1709c0 0.0166016 -0.0439453 0.106445 -0.0439453 0.12207c0 14.3945 -4.24219 27.8057 -11.5439 39.0498l-146.358 225.715
c-4.44336 6.85254 -11.9707 10.9424 -20.1367 10.9424h-293.917c-4.41113 0 -8 -3.58887 -8 -8v-8c0 -17.6445 14.3555 -32 32 -32h213.471c25.2021 0 42.626 -25.293 33.6299 -48.8457l-24.5518 -64.2812c-7.05371 -18.4658 -25.0732 -30.873 -44.8398 -30.873h-113.709
c-22.0557 0 -40 -17.9443 -40 -40c0 -4.41113 3.58887 -8 8 -8h131.552c0.0175781 0 0.0712891 -0.0273438 0.0888672 -0.0273438c9.16992 0 17.9404 -1.72461 26.0039 -4.86621l99.752 -38.7881c18.5898 -7.22852 30.6035 -24.7881 30.6035 -44.7363v-23.582h128z" />
<glyph glyph-name="hand-spock" unicode="&#xf259;"
d="M501.03 331.824c6.05762 -9.77832 10.9746 -27.0498 10.9746 -38.5518c0 -4.80664 -0.915039 -12.499 -2.04297 -17.1709l-57.623 -241.963c-12.748 -54.1729 -68.2627 -98.1387 -123.915 -98.1387h-0.345703h-107.455h-0.224609
c-33.8135 0 -81.2148 18.834 -105.807 42.041l-91.3652 85.9766c-12.8213 12.0469 -23.2266 36.1016 -23.2266 53.6943c0 16.1299 8.97266 38.7529 20.0273 50.499c5.31836 5.66406 29.875 29.3926 68.1152 21.8477l-24.3594 82.1973
c-1.68164 5.66406 -3.0459 15.0576 -3.0459 20.9668c0 37.5938 30.417 70.502 67.8955 73.4551c-0.204102 2.03125 -0.369141 5.33691 -0.369141 7.37891c0 31.627 24.8594 63.6895 55.4902 71.5684c43.248 10.9785 80.5645 -17.7012 89.6602 -53.0723l13.6836 -53.207
l4.64648 22.6602c6.76074 32.417 39.123 58.8115 72.2373 58.916c8.73438 0 56.625 -3.26953 70.7383 -54.0801c15.0664 0.710938 46.9199 -3.50977 66.3105 -35.0176zM463.271 287.219c7.86914 32.9844 -42.1211 45.2695 -50.0859 11.9219l-24.8008 -104.146
c-4.38867 -18.4141 -31.7783 -11.8926 -28.0557 6.2168l28.5479 139.166c7.39844 36.0703 -43.3076 45.0703 -50.1182 11.9629l-31.791 -154.971c-3.54883 -17.3086 -28.2832 -18.0469 -32.7109 -0.804688l-47.3262 184.035
c-8.43359 32.8105 -58.3691 20.2676 -49.8652 -12.8359l42.4414 -165.039c4.81641 -18.7207 -23.3711 -26.9121 -28.9648 -8.00781l-31.3438 105.779c-9.6875 32.6465 -59.1191 18.2578 -49.3867 -14.625l36.0137 -121.539
c5.61816 -18.9521 10.1777 -50.377 10.1777 -70.1436v-0.00878906c0 -6.54297 -8.05664 -10.9355 -13.4824 -5.82617l-51.123 48.1074c-24.7852 23.4082 -60.0527 -14.1875 -35.2793 -37.4902l91.3691 -85.9805c16.9629 -16.0068 49.6592 -28.998 72.9824 -28.998h0.154297
h107.455h0.216797c34.7402 0 69.3936 27.4443 77.3525 61.2598z" />
d="M501.03 331.824c6.92773 -11.1826 10.9697 -24.4053 10.9697 -38.5146c0 -5.92676 -0.706055 -11.6885 -2.03809 -17.208l-57.623 -241.963c-13.2236 -56.1904 -63.707 -98.1387 -123.908 -98.1387h-0.352539h-107.455
c-0.0761719 0 -0.193359 0.00195312 -0.270508 0.00195312c-40.9248 0 -78.1475 15.9814 -105.761 42.0391l-91.3652 85.9766c-14.3076 13.4434 -23.2246 32.5547 -23.2246 53.7168c0 19.5254 7.61035 37.2861 20.0254 50.4766
c5.31836 5.66406 29.875 29.3926 68.1152 21.8477l-24.3594 82.1973c-1.97363 6.64844 -2.97656 13.6836 -2.97656 20.9688c0 38.6953 29.8926 70.4639 67.8262 73.4531c-0.246094 2.45117 -0.34082 4.85547 -0.34082 7.37207c0 34.4199 23.585 63.376 55.4619 71.5752
c43.248 10.9785 80.5645 -17.7012 89.6602 -53.0723l13.6836 -53.207l4.64648 22.6602c6.99023 33.5186 36.6826 58.8037 72.2373 58.916c8.73438 0 56.625 -3.26953 70.7383 -54.0801c15.0664 0.710938 46.9199 -3.50977 66.3105 -35.0176zM463.271 287.219
c7.86914 32.9844 -42.1211 45.2695 -50.0859 11.9219l-24.8008 -104.146c-4.38867 -18.4141 -31.7783 -11.8926 -28.0557 6.2168l28.5479 139.166c7.39844 36.0703 -43.3076 45.0703 -50.1182 11.9629l-31.791 -154.971
c-3.54883 -17.3086 -28.2832 -18.0469 -32.7109 -0.804688l-47.3262 184.035c-8.43359 32.8105 -58.3691 20.2676 -49.8652 -12.8359l42.4414 -165.039c4.81641 -18.7207 -23.3711 -26.9121 -28.9648 -8.00781l-31.3438 105.779
c-9.6875 32.6465 -59.1191 18.2578 -49.3867 -14.625l36.0137 -121.539c6.59375 -22.2441 10.1777 -45.7803 10.1777 -70.1523c0 -6.54297 -8.05664 -10.9355 -13.4824 -5.82617l-51.123 48.1074c-24.7852 23.4082 -60.0527 -14.1875 -35.2793 -37.4902l91.3691 -85.9805
c19.0469 -17.9736 44.75 -28.998 72.9795 -28.998h0.157227h107.455c0.0732422 0 0.138672 0.0429688 0.212891 0.0429688c37.5791 0 69.1016 26.1416 77.3564 61.2168z" />
<glyph glyph-name="hand-pointer" unicode="&#xf25a;" horiz-adv-x="448"
d="M358.182 268.639c43.1934 16.6348 89.8184 -15.7949 89.8184 -62.6387v-84c-0.000976562 -4.25 -0.775391 -11.0615 -1.72754 -15.2041l-27.4297 -118.999c-6.98242 -30.2969 -33.7549 -51.7969 -64.5566 -51.7969h-178.286c-21.2588 0 -41.3682 10.4102 -53.791 27.8457
l-109.699 154.001c-21.2432 29.8193 -14.8047 71.3574 14.5498 93.1523c18.8115 13.9658 42.1748 16.2822 62.083 8.87207v161.129c0 36.9443 29.7363 67 66.2861 67s66.2861 -30.0557 66.2861 -67v-73.6338c20.4131 2.85742 41.4678 -3.94238 56.5947 -19.6289
c27.1934 12.8467 60.3799 5.66992 79.8721 -19.0986zM80.9854 168.303c-14.4004 20.2119 -43.8008 -2.38281 -29.3945 -22.6055l109.712 -154c3.43457 -4.81934 8.92871 -7.69727 14.6973 -7.69727h178.285c8.49219 0 15.8037 5.99414 17.7822 14.5762l27.4297 119.001
c0.333008 1.44629 0.501953 2.93457 0.501953 4.42285v84c0 25.1602 -36.5713 25.1211 -36.5713 0c0 -8.83594 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16406 -16 16v21c0 25.1602 -36.5713 25.1201 -36.5713 0v-21c0 -8.83594 -7.16309 -16 -16 -16h-6.85938
c-8.83691 0 -16 7.16406 -16 16v35c0 25.1602 -36.5703 25.1201 -36.5703 0v-35c0 -8.83594 -7.16309 -16 -16 -16h-6.85742c-8.83691 0 -16 7.16406 -16 16v175c0 25.1602 -36.5713 25.1201 -36.5713 0v-241.493c0 -15.5703 -20.0352 -21.9092 -29.0303 -9.2832z
M176.143 48v96c0 8.83691 6.26855 16 14 16h6c7.73242 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26758 -16 -14 -16h-6c-7.73242 0 -14 7.16309 -14 16zM251.571 48v96c0 8.83691 6.26758 16 14 16h6c7.73145 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26855 -16 -14 -16h-6
c-7.73242 0 -14 7.16309 -14 16zM327 48v96c0 8.83691 6.26758 16 14 16h6c7.73242 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26758 -16 -14 -16h-6c-7.73242 0 -14 7.16309 -14 16z" />
d="M358.182 268.639c43.1934 16.6348 89.8184 -15.7949 89.8184 -62.6387v-84c-0.000976562 -5.24023 -0.600586 -10.3037 -1.72754 -15.2041l-27.4297 -118.999c-6.98242 -30.2969 -33.7549 -51.7969 -64.5566 -51.7969h-178.286
c-21.2588 0 -41.3682 10.4102 -53.791 27.8457l-109.699 154.001c-21.2432 29.8193 -14.8047 71.3574 14.5498 93.1523c18.8115 13.9658 42.1748 16.2822 62.083 8.87207v161.129c0 36.9443 29.7363 67 66.2861 67s66.2861 -30.0557 66.2861 -67v-73.6338
c20.4131 2.85742 41.4678 -3.94238 56.5947 -19.6289c27.1934 12.8467 60.3799 5.66992 79.8721 -19.0986zM80.9854 168.303c-14.4004 20.2119 -43.8008 -2.38281 -29.3945 -22.6055l109.712 -154c3.43457 -4.81934 8.92871 -7.69727 14.6973 -7.69727h178.285
c8.49219 0 15.8037 5.99414 17.7822 14.5762l27.4297 119.001c0.333008 1.44629 0.501953 2.93457 0.501953 4.42285v84c0 25.1602 -36.5713 25.1211 -36.5713 0c0 -8.83594 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16406 -16 16v21
c0 25.1602 -36.5713 25.1201 -36.5713 0v-21c0 -8.83594 -7.16309 -16 -16 -16h-6.85938c-8.83691 0 -16 7.16406 -16 16v35c0 25.1602 -36.5703 25.1201 -36.5703 0v-35c0 -8.83594 -7.16309 -16 -16 -16h-6.85742c-8.83691 0 -16 7.16406 -16 16v175
c0 25.1602 -36.5713 25.1201 -36.5713 0v-241.493c0 -15.5703 -20.0352 -21.9092 -29.0303 -9.2832zM176.143 48v96c0 8.83691 6.26855 16 14 16h6c7.73242 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26758 -16 -14 -16h-6c-7.73242 0 -14 7.16309 -14 16zM251.571 48v96
c0 8.83691 6.26758 16 14 16h6c7.73145 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26855 -16 -14 -16h-6c-7.73242 0 -14 7.16309 -14 16zM327 48v96c0 8.83691 6.26758 16 14 16h6c7.73242 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26758 -16 -14 -16h-6
c-7.73242 0 -14 7.16309 -14 16z" />
<glyph glyph-name="hand-peace" unicode="&#xf25b;" horiz-adv-x="448"
d="M362.146 256.024c42.5908 13.3184 85.8535 -19.0684 85.8535 -64.0244l-0.0117188 -70.001c-0.000976562 -4.25 -0.775391 -11.0615 -1.72949 -15.2031l-27.4268 -118.999c-6.99707 -30.3564 -33.8105 -51.7969 -64.5547 -51.7969h-205.702
d="M362.146 256.024c42.5908 13.3184 85.8535 -19.0684 85.8535 -64.0244l-0.0117188 -70.001c-0.000976562 -5.24023 -0.600586 -10.3027 -1.72949 -15.2031l-27.4268 -118.999c-6.99707 -30.3564 -33.8105 -51.7969 -64.5547 -51.7969h-205.702
c-23.8447 0 -45.9502 13.0303 -57.6904 34.0059l-54.8525 97.999c-19.2607 34.4092 -5.82422 67.2617 24.7324 92.2178l-55.7568 144.928c-14.5791 37.8867 3.72754 80.6113 41.2012 95.6416c37.6406 15.0977 80.3691 -3.63477 95.1123 -41.9424l18.6787 -78.8496
l-9.14062 94c0 40.8037 32.8096 74 73.1396 74s73.1406 -33.1963 73.1406 -74v-87.6348c26.2451 3.6748 51.2959 -8.69238 65.0068 -30.3408zM399.987 122l-0.000976562 70c0 25.1602 -36.5674 25.1201 -36.5674 0c0 -8.83691 -7.16309 -16 -16 -16h-6.85547
c-8.83789 0 -16 7.16309 -16 16v28c0 25.1592 -36.5674 25.1221 -36.5674 0v-28c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16v182c0 34.4297 -50.2803 34.375 -50.2803 0v-182c0 -8.83691 -7.16309 -16 -16 -16h-11.6328v0
c-6.06445 0 -12.7549 4.59375 -14.9326 10.2539l-59.7842 155.357c-12.1396 31.5518 -59.2842 13.4326 -46.7168 -19.2227l64.0898 -166.549c0.589844 -1.53125 1.06738 -4.10547 1.06738 -5.74609c0 -4.19043 -2.63379 -9.74219 -5.87891 -12.3926l-26.6475 -21.7646
c-8.83789 0 -16 7.16309 -16 16v28c0 25.1592 -36.5674 25.1221 -36.5674 0v-28c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16v182c0 34.4297 -50.2803 34.375 -50.2803 0v-182c0 -8.83691 -7.16309 -16 -16 -16h-11.6328
c-6.80859 0 -12.624 4.25391 -14.9326 10.2539l-59.7842 155.357c-12.1396 31.5518 -59.2842 13.4326 -46.7168 -19.2227l64.0898 -166.549c0.685547 -1.78125 1.07812 -3.71875 1.07812 -5.74121c0 -4.99707 -2.2959 -9.46289 -5.88965 -12.3975l-26.6475 -21.7646
c-7.12695 -5.81934 -9.06445 -16.3467 -4.50781 -24.4873l54.8535 -98c3.26367 -5.82812 9.31934 -9.44922 15.8057 -9.44922h205.701c8.49121 0 15.8037 5.99414 17.7812 14.5762l27.4277 119.001c0.333008 1.44629 0.501953 2.93457 0.501953 4.42285z" />
<glyph glyph-name="registered" unicode="&#xf25d;"
d="M256 440c136.967 0 248 -111.033 248 -248s-111.033 -248 -248 -248s-248 111.033 -248 248s111.033 248 248 248zM256 -8c110.549 0 200 89.4678 200 200c0 110.549 -89.4678 200 -200 200c-110.549 0 -200 -89.4688 -200 -200c0 -110.549 89.4678 -200 200 -200z
M366.442 73.791c4.40332 -7.99219 -1.37012 -17.791 -10.5107 -17.791h-42.8096h-0.0126953c-3.97559 0 -8.71582 2.84961 -10.5801 6.36035l-47.5156 89.3027h-31.958v-83.6631c0 -6.61719 -5.38281 -12 -12 -12h-38.5674c-6.61719 0 -12 5.38281 -12 12v248.304
c0 6.61719 5.38281 12 12 12h78.667c71.251 0 101.498 -32.749 101.498 -85.252c0 -31.6123 -15.2148 -59.2969 -39.4824 -73.1758c3.02148 -4.61719 0.225586 0.199219 53.2715 -96.085zM256.933 208.094c20.9131 0 32.4307 11.5186 32.4316 32.4316
c0 19.5752 -6.5127 31.709 -38.9297 31.709h-27.377v-64.1406h33.875z" />
M366.442 73.791c4.40332 -7.99219 -1.37012 -17.791 -10.5107 -17.791h-42.8096c-0.00488281 0 -0.000976562 -0.0126953 -0.00585938 -0.0126953c-4.58594 0 -8.57422 2.58301 -10.5869 6.37305l-47.5156 89.3027h-31.958v-83.6631c0 -6.61719 -5.38281 -12 -12 -12
h-38.5674c-6.61719 0 -12 5.38281 -12 12v248.304c0 6.61719 5.38281 12 12 12h78.667c71.251 0 101.498 -32.749 101.498 -85.252c0 -31.6123 -15.2148 -59.2969 -39.4824 -73.1758c3.02148 -4.61719 0.225586 0.199219 53.2715 -96.085zM256.933 208.094
c20.9131 0 32.4307 11.5186 32.4316 32.4316c0 19.5752 -6.5127 31.709 -38.9297 31.709h-27.377v-64.1406h33.875z" />
<glyph glyph-name="calendar-plus" unicode="&#xf271;" horiz-adv-x="448"
d="M336 156v-24c0 -6.59961 -5.40039 -12 -12 -12h-76v-76c0 -6.59961 -5.40039 -12 -12 -12h-24c-6.59961 0 -12 5.40039 -12 12v76h-76c-6.59961 0 -12 5.40039 -12 12v24c0 6.59961 5.40039 12 12 12h76v76c0 6.59961 5.40039 12 12 12h24c6.59961 0 12 -5.40039 12 -12
v-76h76c6.59961 0 12 -5.40039 12 -12zM448 336v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h48v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h128v52c0 6.59961 5.40039 12 12 12h40
@@ -481,9 +482,9 @@ c6.62695 0 12 -5.37305 12 -12v-52h48zM394 -16c3.31152 0 6 2.68848 6 6v298h-352v-
c-4.66699 4.70508 -4.6377 12.3027 0.0673828 16.9707l22.7197 22.5361c4.70508 4.66699 12.3027 4.63672 16.9697 -0.0693359l44.1035 -44.4609l111.072 110.182c4.70508 4.66699 12.3027 4.63672 16.9707 -0.0683594l22.5361 -22.7178
c4.66699 -4.70508 4.63672 -12.3027 -0.0683594 -16.9697z" />
<glyph glyph-name="map" unicode="&#xf279;" horiz-adv-x="576"
d="M560.02 416c8.4502 0 15.9805 -6.83008 15.9805 -16.0195v-346.32c0 -11.9609 -9.01367 -25.2705 -20.1201 -29.71l-151.83 -52.8105c-5.32617 -1.7334 -14.1953 -3.13965 -19.7969 -3.13965c-5.7373 0 -14.8105 1.47363 -20.2529 3.29004l-172 60.71l-170.05 -62.8398
c-1.99023 -0.790039 -4 -1.16016 -5.95996 -1.16016c-8.45996 0 -15.9902 6.83008 -15.9902 16.0195v346.32c0.00292969 11.959 9.0166 25.2686 20.1201 29.71l151.83 52.8105c6.43945 2.08984 13.1201 3.13965 19.8096 3.13965
c5.73242 -0.00195312 14.8008 -1.47168 20.2402 -3.28027l172 -60.7197h0.00976562l170.05 62.8398c1.98047 0.790039 4 1.16016 5.95996 1.16016zM224 357.58v-285.97l128 -45.1904v285.97zM48 29.9502l127.36 47.0801l0.639648 0.229492v286.2l-128 -44.5303v-288.979z
d="M560.02 416c8.4502 0 15.9805 -6.83008 15.9805 -16.0195v-346.32c0 -13.4707 -8.32422 -24.9951 -20.1201 -29.71l-151.83 -52.8105c-6.23242 -2.02832 -12.9023 -3.12305 -19.8076 -3.12305c-7.07324 0 -13.8799 1.15039 -20.2422 3.27344l-172 60.71l-170.05 -62.8398
c-1.99023 -0.790039 -4 -1.16016 -5.95996 -1.16016c-8.45996 0 -15.9902 6.83008 -15.9902 16.0195v346.32c0.00292969 13.4697 8.32617 24.9932 20.1201 29.71l151.83 52.8105c6.43945 2.08984 13.1201 3.13965 19.8096 3.13965
c7.06641 -0.00292969 13.8789 -1.16602 20.2402 -3.28027l172 -60.7197h0.00976562l170.05 62.8398c1.98047 0.790039 4 1.16016 5.95996 1.16016zM224 357.58v-285.97l128 -45.1904v285.97zM48 29.9502l127.36 47.0801l0.639648 0.229492v286.2l-128 -44.5303v-288.979z
M528 65.0801v288.97l-127.36 -47.0693l-0.639648 -0.240234v-286.19z" />
<glyph glyph-name="comment-alt" unicode="&#xf27a;"
d="M448 448c35.2998 0 64 -28.7002 64 -64v-288c0 -35.2998 -28.7002 -64 -64 -64h-144l-124.9 -93.5996c-2.19922 -1.7002 -4.69922 -2.40039 -7.09961 -2.40039c-6.2002 0 -12 4.90039 -12 12v84h-96c-35.2998 0 -64 28.7002 -64 64v288c0 35.2998 28.7002 64 64 64h384z
@@ -497,16 +498,16 @@ c-8.7998 0 -16 7.2002 -16 16v160c0 8.7998 7.2002 16 16 16h160c8.7998 0 16 -7.200
<glyph glyph-name="handshake" unicode="&#xf2b5;" horiz-adv-x="640"
d="M519.2 320.1h120.8v-255.699h-64c-17.5 0 -31.7998 14.1992 -31.9004 31.6992h-57.8994c-1.7998 -8.19922 -5.2998 -16.0996 -10.9004 -23l-26.2002 -32.2998c-15.7998 -19.3994 -41.8994 -25.5 -64 -16.7998c-13.5 -16.5996 -30.5996 -24 -48.7998 -24
c-15.0996 0 -28.5996 5.09961 -41.0996 15.9004c-31.7998 -21.9004 -74.7002 -21.3008 -105.601 3.7998l-84.5996 76.3994h-9.09961c-0.100586 -17.5 -14.3008 -31.6992 -31.9004 -31.6992h-64v255.699h118l47.5996 47.6006c10.5 10.3994 24.8008 16.2998 39.6006 16.2998
h226.8v0c12.7812 0 30.5225 -7.30273 39.5996 -16.2998zM48 96.4004c8.7998 0 16 7.09961 16 16c0 8.7998 -7.2002 16 -16 16s-16 -7.2002 -16 -16c0 -8.80078 7.2002 -16 16 -16zM438 103.3c2.7002 3.40039 2.2002 8.5 -1.2002 11.2998l-108.2 87.8008l-8.19922 -7.5
h226.8c15.4326 0 29.4326 -6.22168 39.5996 -16.2998zM48 96.4004c8.7998 0 16 7.09961 16 16c0 8.7998 -7.2002 16 -16 16s-16 -7.2002 -16 -16c0 -8.80078 7.2002 -16 16 -16zM438 103.3c2.7002 3.40039 2.2002 8.5 -1.2002 11.2998l-108.2 87.8008l-8.19922 -7.5
c-40.3008 -36.8008 -86.7002 -11.8008 -101.5 4.39941c-26.7002 29 -25 74.4004 4.39941 101.3l38.7002 35.5h-56.7002c-2 -0.799805 -3.7002 -1.5 -5.7002 -2.2998l-61.6992 -61.5996h-41.9004v-128.101h27.7002l97.2998 -88
c16.0996 -13.0996 41.4004 -10.5 55.2998 6.60059l15.6006 19.2002l36.7998 -31.5c3 -2.40039 12 -4.90039 18 2.39941l30 36.5l23.8994 -19.3994c3.5 -2.80078 8.5 -2.2002 11.3008 1.19922zM544 144.1v128h-44.7002l-61.7002 61.6006
c-1.39941 1.5 -3.39941 2.2998 -5.5 2.2998l-83.6992 -0.200195c-10 0 -19.6006 -3.7002 -27 -10.5l-65.6006 -60.0996c-9.7002 -8.7998 -10.5 -24 -1.2002 -33.9004c8.90039 -9.39941 25.1006 -8.7002 34.6006 0l55.2002 50.6006c6.5 5.89941 16.5996 5.5 22.5996 -1
l10.9004 -11.7002c6 -6.5 5.5 -16.6006 -1 -22.6006l-12.5 -11.3994l102.699 -83.4004c2.80078 -2.2998 5.40039 -4.89941 7.7002 -7.7002h69.2002zM592 96.4004c8.7998 0 16 7.09961 16 16c0 8.7998 -7.2002 16 -16 16s-16 -7.2002 -16 -16c0 -8.80078 7.2002 -16 16 -16z
" />
<glyph glyph-name="envelope-open" unicode="&#xf2b6;"
d="M494.586 283.484c9.6123 -7.94824 17.4141 -24.5205 17.4141 -36.9932v-262.491c0 -26.5098 -21.4902 -48 -48 -48h-416c-26.5098 0 -48 21.4902 -48 48v262.515c0 12.5166 7.84668 29.1279 17.5146 37.0771c4.08008 3.35449 110.688 89.0996 135.15 108.549
c22.6992 18.1426 60.1299 55.8594 103.335 55.8594c43.4365 0 81.2314 -38.1914 103.335 -55.8594c23.5283 -18.707 130.554 -104.773 135.251 -108.656zM464 -10v253.632v0.00488281c0 1.5791 -0.996094 3.66602 -2.22363 4.6582
c-15.8633 12.8232 -108.793 87.5752 -132.366 106.316c-17.5527 14.0195 -49.7168 45.3887 -73.4102 45.3887c-23.6016 0 -55.2451 -30.8799 -73.4102 -45.3887c-23.5713 -18.7393 -116.494 -93.4795 -132.364 -106.293
d="M494.586 283.484c10.6523 -8.80762 17.4141 -22.1064 17.4141 -36.9932v-262.491c0 -26.5098 -21.4902 -48 -48 -48h-416c-26.5098 0 -48 21.4902 -48 48v262.515c0 14.9355 6.80469 28.2705 17.5146 37.0771c4.08008 3.35449 110.688 89.0996 135.15 108.549
c22.6992 18.1426 60.1299 55.8594 103.335 55.8594c43.4365 0 81.2314 -38.1914 103.335 -55.8594c23.5283 -18.707 130.554 -104.773 135.251 -108.656zM464 -10v253.632c0 0.00195312 0.00390625 0.000976562 0.00390625 0.00292969
c0 1.88184 -0.869141 3.56152 -2.22754 4.66016c-15.8633 12.8232 -108.793 87.5752 -132.366 106.316c-17.5527 14.0195 -49.7168 45.3887 -73.4102 45.3887c-23.6016 0 -55.2451 -30.8799 -73.4102 -45.3887c-23.5713 -18.7393 -116.494 -93.4795 -132.364 -106.293
c-1.40918 -1.13965 -2.22559 -2.85254 -2.22559 -4.66504v-253.653c0 -3.31152 2.68848 -6 6 -6h404c3.31152 0 6 2.68848 6 6zM432.009 177.704c4.24902 -5.15918 3.46484 -12.7949 -1.74512 -16.9814c-28.9746 -23.2822 -59.2734 -47.5967 -70.9287 -56.8623
c-22.6992 -18.1436 -60.1299 -55.8604 -103.335 -55.8604c-43.4521 0 -81.2871 38.2373 -103.335 55.8604c-11.2793 8.9668 -41.7441 33.4131 -70.9268 56.8643c-5.20996 4.1875 -5.99316 11.8223 -1.74512 16.9814l15.2578 18.5283
c4.17773 5.07227 11.6572 5.84277 16.7793 1.72559c28.6182 -23.001 58.5654 -47.0352 70.5596 -56.5713c17.5527 -14.0195 49.7168 -45.3887 73.4102 -45.3887c23.6016 0 55.2461 30.8799 73.4102 45.3887c11.9941 9.53516 41.9434 33.5703 70.5625 56.5684
@@ -555,10 +556,11 @@ c6.09961 -6.2002 6.09961 -16.4004 0 -22.6006l-58.2998 -59.2998v-84.5l71.8994 42.
c7.5 4.39941 17.2002 1.7998 21.5 -5.90039l7.90039 -13.9004c4.2998 -7.69922 1.7002 -17.5 -5.7998 -21.8994l-39.2002 -23l34.0996 -9.2998c8.40039 -2.30078 13.3008 -11.1006 11.1006 -19.6006l-4.10059 -15.5c-2.2998 -8.5 -10.8994 -13.5996 -19.2998 -11.2998
l-79.7002 21.7002l-71.8994 -42.2002l71.7998 -42.2002l79.7002 21.7002c8.39941 2.2998 17.0996 -2.7998 19.2998 -11.2998l4.09961 -15.5c2.30078 -8.5 -2.69922 -17.2998 -11.0996 -19.6006l-34.0996 -9.2998z" />
<glyph glyph-name="trash-alt" unicode="&#xf2ed;" horiz-adv-x="448"
d="M268 32c-6.62402 0 -12 5.37598 -12 12v216c0 6.62402 5.37598 12 12 12h24c6.62402 0 12 -5.37598 12 -12v-216c0 -6.62402 -5.37598 -12 -12 -12h-24zM432 368c8.83203 0 16 -7.16797 16 -16v-16c0 -8.83203 -7.16797 -16 -16 -16h-16v-336
c0 -26.4961 -21.5039 -48 -48 -48h-288c-26.4961 0 -48 21.5039 -48 48v336h-16c-8.83203 0 -16 7.16797 -16 16v16c0 8.83203 7.16797 16 16 16h82.4102l34.0195 56.7002c7.71875 12.8613 26.1572 23.2998 41.1572 23.2998h0.00292969h100.82h0.0224609
c15 0 33.4385 -10.4385 41.1572 -23.2998l34 -56.7002h82.4102zM171.84 397.09l-17.4502 -29.0898h139.221l-17.46 29.0898c-0.96582 1.60645 -3.26953 2.91016 -5.14355 2.91016h-0.00683594h-94h-0.0166016c-1.87402 0 -4.17871 -1.30371 -5.14355 -2.91016zM368 -16v336
h-288v-336h288zM156 32c-6.62402 0 -12 5.37598 -12 12v216c0 6.62402 5.37598 12 12 12h24c6.62402 0 12 -5.37598 12 -12v-216c0 -6.62402 -5.37598 -12 -12 -12h-24z" />
d="M268 32c-6.62305 0 -12 5.37695 -12 12v216c0 6.62305 5.37695 12 12 12h24c6.62305 0 12 -5.37695 12 -12v-216c0 -6.62305 -5.37695 -12 -12 -12h-24zM432 368c8.83105 0 16 -7.16895 16 -16v-16c0 -8.83105 -7.16895 -16 -16 -16h-16v-336
c0 -26.4922 -21.5078 -48 -48 -48h-288c-26.4922 0 -48 21.5078 -48 48v336h-16c-8.83105 0 -16 7.16895 -16 16v16c0 8.83105 7.16895 16 16 16h82.4102l34.0195 56.7002c8.39258 13.9844 23.6777 23.2998 41.1602 23.2998h100.82
c0.0078125 0 -0.015625 0.0517578 -0.0078125 0.0517578c17.4824 0 32.7949 -9.36719 41.1875 -23.3516l34 -56.7002h82.4102zM171.84 397.09l-17.4502 -29.0898h139.221l-17.46 29.0898c-1.0498 1.74707 -2.95898 2.91016 -5.14355 2.91016h-0.00683594h-94
c-0.00585938 0 -0.00683594 0.00683594 -0.0126953 0.00683594c-2.18457 0 -4.09766 -1.16992 -5.14746 -2.91699zM368 -16v336h-288v-336h288zM156 32c-6.62305 0 -12 5.37695 -12 12v216c0 6.62305 5.37695 12 12 12h24c6.62305 0 12 -5.37695 12 -12v-216
c0 -6.62305 -5.37695 -12 -12 -12h-24z" />
<glyph glyph-name="images" unicode="&#xf302;" horiz-adv-x="576"
d="M480 32v-16c0 -26.5098 -21.4902 -48 -48 -48h-384c-26.5098 0 -48 21.4902 -48 48v256c0 26.5098 21.4902 48 48 48h16v-48h-10c-3.31152 0 -6 -2.68848 -6 -6v-244c0 -3.31152 2.68848 -6 6 -6h372c3.31152 0 6 2.68848 6 6v10h48zM522 368h-372
c-3.31152 0 -6 -2.68848 -6 -6v-244c0 -3.31152 2.68848 -6 6 -6h372c3.31152 0 6 2.68848 6 6v244c0 3.31152 -2.68848 6 -6 6zM528 416c26.5098 0 48 -21.4902 48 -48v-256c0 -26.5098 -21.4902 -48 -48 -48h-384c-26.5098 0 -48 21.4902 -48 48v256
@@ -584,9 +586,9 @@ d="M464 448c4.09961 0 7.7998 -2 10.0996 -5.40039l99.9004 -147.199c2.90039 -4.400
c2.2002 3.40039 6 5.40039 10 5.40039h352zM444.7 400h-56.7998l51.6992 -96h68.4004zM242.6 400l-51.5996 -96h194l-51.7002 96h-90.7002zM131.3 400l-63.2998 -96h68.4004l51.6992 96h-56.7998zM88.2998 256l119.7 -160l-68.2998 160h-51.4004zM191.2 256l96.7998 -243.3
l96.7998 243.3h-193.6zM368 96l119.6 160h-51.3994z" />
<glyph glyph-name="money-bill-alt" unicode="&#xf3d1;" horiz-adv-x="640"
d="M320 304c53.0195 0 96 -50.1396 96 -112c0 -61.8701 -43 -112 -96 -112c-53.0195 0 -96 50.1504 -96 112c0 61.8604 42.9805 112 96 112zM360 136v16c0 4.41992 -3.58008 8 -8 8h-16v88c0 4.41992 -3.58008 8 -8 8h-13.5801h-0.000976562
c-4.01074 0 -9.97266 -1.80566 -13.3086 -4.03027l-15.3301 -10.2197c-1.96777 -1.30957 -3.56445 -4.29004 -3.56445 -6.65332c0 -1.33691 0.601562 -3.32422 1.34375 -4.43652l8.88086 -13.3105c1.30859 -1.9668 4.29004 -3.56445 6.65332 -3.56445
c1.33691 0 3.32422 0.602539 4.43652 1.34473l0.469727 0.310547v-55.4404h-16c-4.41992 0 -8 -3.58008 -8 -8v-16c0 -4.41992 3.58008 -8 8 -8h64c4.41992 0 8 3.58008 8 8zM608 384c17.6699 0 32 -14.3301 32 -32v-320c0 -17.6699 -14.3301 -32 -32 -32h-576
d="M320 304c53.0195 0 96 -50.1396 96 -112c0 -61.8701 -43 -112 -96 -112c-53.0195 0 -96 50.1504 -96 112c0 61.8604 42.9805 112 96 112zM360 136v16c0 4.41992 -3.58008 8 -8 8h-16v88c0 4.41992 -3.58008 8 -8 8h-13.5801
c-4.91113 0 -9.50586 -1.49316 -13.3096 -4.03027l-15.3301 -10.2197c-2.15332 -1.43262 -3.55957 -3.88379 -3.55957 -6.66113c0 -1.6377 0.493164 -3.16113 1.33887 -4.42871l8.88086 -13.3105c1.43164 -2.15234 3.88379 -3.55957 6.66113 -3.55957
c1.6377 0 3.16016 0.494141 4.42871 1.33984l0.469727 0.310547v-55.4404h-16c-4.41992 0 -8 -3.58008 -8 -8v-16c0 -4.41992 3.58008 -8 8 -8h64c4.41992 0 8 3.58008 8 8zM608 384c17.6699 0 32 -14.3301 32 -32v-320c0 -17.6699 -14.3301 -32 -32 -32h-576
c-17.6699 0 -32 14.3301 -32 32v320c0 17.6699 14.3301 32 32 32h576zM592 112v160c-35.3496 0 -64 28.6504 -64 64h-416c0 -35.3496 -28.6504 -64 -64 -64v-160c35.3496 0 64 -28.6504 64 -64h416c0 35.3496 28.6504 64 64 64z" />
<glyph glyph-name="window-close" unicode="&#xf410;"
d="M464 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-416c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h416zM464 22v340c0 3.2998 -2.7002 6 -6 6h-404c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h404c3.2998 0 6 2.7002 6 6z

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 876 KiB

After

Width:  |  Height:  |  Size: 893 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -5,9 +5,29 @@
<title>QMI Cloud</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/favicon.ico">
<link rel="stylesheet" href="styles.921aafa95031aeb74181.css"></head>
<link rel="icon" href="assets/favicon.svg">
<!-- Load environment variables -->
<script src="env.js"></script>
<!--
<script crossorigin="anonymous"
type="application/javascript"
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
data-host="https://qmicloud.qliktech.com/qcsproxy"></script>
-->
<script crossorigin="anonymous"
type="application/javascript"
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
data-host="https://innovation.us.qlikcloud.com"
data-auth-type="Oauth2"
data-client-id="21be5044bba1072c16a803a3e6e4dca0"
data-redirect-uri="https://qmicloud-dev.qliktech.com/oauth-callback.html"
data-access-token-storage="session"
data-auto-redirect="true"></script>
<link rel="stylesheet" href="styles.3b2b6672156f20378f8f.css"></head>
<body>
<app-root></app-root>
<script src="runtime.689ba4fd6cadb82c1ac2.js" defer></script><script src="polyfills-es5.feb8e3dfdd8e1cace860.js" nomodule defer></script><script src="polyfills.60117177d3b4f4827ace.js" defer></script><script src="scripts.73c34722d75b092f2620.js" defer></script><script src="main.eca58c33a1ad840ee769.js" defer></script></body>
<script src="runtime.c51bd5b1c616d9ffddc1.js" defer></script><script src="polyfills-es5.6fef7e679f78bcc42760.js" nomodule defer></script><script src="polyfills.51f5cc3d1309de3a873d.js" defer></script><script src="scripts.1af868998801499c8755.js" defer></script><script src="main.0eb476bbc192df0d888a.js" defer></script></body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

12
dist/qmi-cloud/oauth-callback.html vendored Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script
crossorigin="anonymous"
type="application/javascript"
data-host="https://innovation.us.qlikcloud.com"
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components@0/dist/oauth-callback.js"
></script>
</head>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -104,7 +104,7 @@ services:
- GIT_TAG=dev
- SSHPATH=/Users/aor/.ssh
- DOCKERIMAGE_AZURE_POWERSHELL=mcr.microsoft.com/azure-powershell:4.2.0-ubuntu-18.04
- DOCKERIMAGE_TERRAFORM=qlikgear/terraform:1.0.1
- DOCKERIMAGE_TERRAFORM=qlikgear/terraform:1.3.5
command: "sh -c 'npm run start:dev'"
volumes:
# -- Dev only volumes

View File

@@ -16,7 +16,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<link rel="shortcut icon" href="favicon.ico" />
<link rel="shortcut icon" href="favicon.svg" />
<!-- <script type="text/javascript" src="https://qdt-apps.qlik.com/qdt-components/v3/3.0.0/qdt-components.js"></script> -->
<script type="text/javascript" src="qdt-components.js"></script>
<script type="text/javascript" src="index.js"></script>

15490
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-app",
"version": "1.3.1",
"version": "5.0.1",
"scripts": {
"start": "node -r esm server/server.js",
"start:dev": "nodemon -r esm server/server.js",
@@ -33,6 +33,7 @@
"bull-arena": "^2.6.4",
"chart.js": "^2.9.3",
"connect-mongo": "^3.0.0",
"cookie": "^0.6.0",
"cookie-parser": "^1.4.4",
"core-js": "^2.5.4",
"esm": "^3.2.25",
@@ -41,20 +42,23 @@
"font-awesome": "^4.7.0",
"fs-extra": "^8.1.0",
"hammerjs": "^2.0.8",
"js-sha1": "^0.6.0",
"jsonwebtoken": "^8.5.1",
"leonardo-ui": "^1.7.1",
"moment": "^2.24.0",
"moment-timezone": "^0.5.31",
"mongoose": "^5.7.4",
"mongoose": "^6.11.1",
"ngx-markdown": "^9.0.0",
"nodemon": "^1.19.1",
"passport": "^0.4.0",
"passport-azure-ad": "^4.1.0",
"passport-openidconnect": "^0.1.2",
"qdt-components": "^2.5.5",
"qmi-cloud-common": "./qmi-cloud-common",
"rxjs": "~6.5.4",
"swagger-jsdoc": "3.5.0",
"swagger-ui-express": "4.1.3",
"uuid": "^9.0.1",
"ws": "^8.14.2",
"zone.js": "~0.10.3"
},
"devDependencies": {

9
proxy.conf.json Normal file
View File

@@ -0,0 +1,9 @@
{
"/api/*": {
"target": "http://localhost:3000/api",
"secure": false,
"logLevel": "debug",
"changeOrigin": true,
"pathRewrite": { "^/api": "" }
}
}

View File

@@ -4,12 +4,12 @@
"isWafPolicyAppGw": true,
"isExternal": true,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Qlik Sense",
"vmTypeDefault": "Standard_D4s_v3",
"index": "vm1",
"versions": [
"values": [
{
"name": "Qlik Sense February 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qliksense-base-feb20-2"
@@ -35,12 +35,12 @@
"isWafPolicyAppGw": true,
"isExternal": true,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Qlik Sense",
"vmTypeDefault": "Standard_D4s_v3",
"index": "vm1",
"versions": [
"values": [
{
"name": "Qlik Sense February 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qliksense-base-feb20-2"
@@ -55,7 +55,7 @@
"product": "QDC",
"vmTypeDefault": "Standard_D4s_v3",
"index": "vm2",
"versions": [
"values": [
{
"name": "QDC Single Server December 2019",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qdc-base-4.4-2"
@@ -81,12 +81,12 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Oracle",
"vmTypeDefault": "Standard_D4s_v3",
"index": "vm1",
"versions": []
"values": []
}
],
"name": "azqmi-database-lkn",
@@ -102,12 +102,12 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "packer",
"vmTypeDefault": "Standard_D8s_v3",
"index": "vm1",
"versions": []
"values": []
}
],
"name": "azqmi-box-builder",
@@ -123,12 +123,12 @@
"isWafPolicyAppGw": false,
"isExternal": true,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "QDC",
"vmTypeDefault": "Standard_D4s_v3",
"index": "vm1",
"versions": [
"values": [
{
"name": "QDC Single Server December 2019",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qdc-base-4.4-2"
@@ -154,13 +154,13 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Windows",
"vmTypeDefault": "Standard_D2s_v3",
"diskSizeGbDefault": 128,
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D2s_v3",
@@ -177,12 +177,12 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Windows",
"vmTypeDefault": "Standard_D2s_v3",
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D2s_v3",
@@ -198,13 +198,13 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Ubuntu 18.04",
"vmTypeDefault": "Standard_B2s",
"diskSizeGbDefault": 128,
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_B2s",
@@ -221,13 +221,13 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Centos 7.5",
"vmTypeDefault": "Standard_B2s",
"diskSizeGbDefault": 128,
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_B2s",
@@ -244,13 +244,13 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Centos 7.5",
"vmTypeDefault": "Standard_D4s_v3",
"diskSizeGbDefault": 128,
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D4s_v3",
@@ -268,13 +268,13 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Windows",
"vmTypeDefault": "Standard_D8s_v3",
"diskSizeGbDefault": 128,
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D8s_v3",
@@ -292,12 +292,12 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "QlikView",
"vmTypeDefault": "Standard_D4s_v3",
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D4s_v3",
@@ -314,13 +314,13 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "each node",
"vmTypeDefault": "Standard_D8s_v3",
"diskSizeGbDefault": 250,
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D8s_v3",
@@ -337,12 +337,12 @@
"isWafPolicyAppGw": true,
"isExternal": true,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "QIB",
"vmTypeDefault": "Standard_D8s_v3",
"index": "vm1",
"versions": [
"values": [
{
"name": "Qlik Sense Feb 2020 and QIB Feb 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/images/qliksense-qib-base-feb20-1"
@@ -363,12 +363,12 @@
"isWafPolicyAppGw": true,
"isExternal": true,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Windows VM",
"vmTypeDefault": "Standard_D8s_v3",
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D8s_v3",
@@ -384,13 +384,13 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Ubuntu 18.04",
"vmTypeDefault": "Standard_D4s_v3",
"diskSizeGbDefault": 128,
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D4s_v3",
@@ -406,12 +406,12 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Attunity Replicate/Compose DW",
"vmTypeDefault": "Standard_D2s_v3",
"index": "vm1",
"versions": []
"values": []
}
],
"vmTypeDefault": "Standard_D2s_v3",

View File

@@ -4,16 +4,16 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Qlik Sense",
"vmTypeDefault": "Standard_D4s_v3",
"diskSizeGbDefault": "500",
"index": "vm1",
"versions": [
"values": [
{
"name": "Qlik Sense April 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/versions/13.72.3"
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/values/13.72.3"
}
]
},
@@ -22,10 +22,10 @@
"vmTypeDefault": "Standard_D4s_v3",
"diskSizeGbDefault": "500",
"index": "vm2",
"versions": [
"values": [
{
"name": "QDC Single Server April 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/versions/4.5.0"
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/values/4.5.0"
}
]
}
@@ -42,16 +42,16 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "QDC",
"vmTypeDefault": "Standard_D8s_v3",
"diskSizeGbDefault": 500,
"index": "vm1",
"versions": [
"values": [
{
"name": "QDC Single Server April 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/versions/4.5.0"
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QDC/values/4.5.0"
}
]
}
@@ -68,20 +68,20 @@
"isWafPolicyAppGw": false,
"isExternal": false,
"isDisabled": false,
"availableProductVersions": [
"availableProductvalues": [
{
"product": "Qlik Sense",
"vmTypeDefault": "Standard_D8s_v3",
"diskSizeGbDefault": 250,
"index": "vm1",
"versions": [
"values": [
{
"name": "Qlik Sense February 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/versions/13.62.0"
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/values/13.62.0"
},
{
"name": "Qlik Sense April 2020",
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/versions/13.72.3"
"image": "/subscriptions/62ebff8f-c40b-41be-9239-252d6c0c8ad9/resourceGroups/QMI-Machines/providers/Microsoft.Compute/galleries/QMICloud/images/QlikSenseEnterprise/values/13.72.3"
}
]
}

View File

@@ -50,6 +50,15 @@ function timeRunning(p) {
};
}
function timeFromCreated(p) {
let totalTime = Math.abs(new Date().getTime() - new Date(p.created).getTime());
let duration = moment.duration(totalTime);
p.duration = {
hours: Math.floor(duration.asHours()),
complete: Math.floor(duration.asDays()) +"d "+duration.hours()+"h "+duration.minutes()
};
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
@@ -61,9 +70,12 @@ async function init(type) {
var filter = {
"isDestroyed":false,
"isDeleted": false,
"$or": [ {"runForever":{ "$exists": false }}, {"runForever": false}],
"statusVms": "Stopped",
"vmImage": {"$exists": true},
"vmImage.vm1": { "$exists": true }
"$and": [{"$or": [
{ "options": {"$exists": true}, "options.vm1": { "$exists": true } },
{ "vmImage": {"$exists": true}, "vmImage.vm1": { "$exists": true } },
]}]
};
if ( type === "warning" ) {
filter.pendingNextAction = {$ne: "destroy"};
@@ -103,6 +115,55 @@ async function init(type) {
});
}
async function initOthers() {
const WARN_DAYS = 82;
const DEST_DAYS = 91;
var dWarning = new Date();
var dDestroy = new Date();
dWarning.setDate(dWarning.getDate() - WARN_DAYS);
dDestroy.setDate(dDestroy.getDate() - DEST_DAYS);
dWarning = dWarning.getTime();
dDestroy = dDestroy.getTime();
var filter = {
"isDestroyed":false,
"isDeleted": false,
"$or": [ {"runForever":{ "$exists": false }}, {"runForever": false}],
"scenario": { "$in": ["awsqmi-s3bucket"] }
};
let provisions = await db.provision.get(filter);
await asyncForEach(provisions.results, async function(p) {
var typeSchedule = "24x7";
timeFromCreated(p);
let pCreatedAt = new Date(p.created);
pCreatedAt = pCreatedAt.getTime();
var limit;
if ( pCreatedAt <= dDestroy ) {
//TODO Destroy
limit = DEST_DAYS*24;
await doDestroy(p, limit, "24x7");
} else if ( pCreatedAt > dDestroy && pCreatedAt <= dWarning && !p.pendingNextAction) {
//TODO Warning
limit = WARN_DAYS*24;
let msg = `Send warning DESTROY email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Inactive' more than ${limit} hours, (exactly ${p.duration.complete})`;
console.log(msg);
if ( IS_REAL ) {
db.event.add({ provision: p._id, type: 'vms.warning-destroy' });
await db.provision.update(p._id, {"pendingNextAction": "destroy"});
await db.notification.add({ provision: p._id.toString(), type: 'warningDestroy', message: msg });
await sendEmail.sendWillDestroyInDays(p, p._scenarioDoc, WARN_DAYS, 10);
}
}
});
}
const doSendEmailDestroyWarning = async function(p, limit, typeSchedule) {
if ( p.pendingNextAction === 'destroy') {
console.log(`Warning email Destroy already sent. Wait for pending action to complete.`);
@@ -110,7 +171,7 @@ const doSendEmailDestroyWarning = async function(p, limit, typeSchedule) {
let msg = `Send warning DESTROY email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Inactive' more than ${limit} hours, (exactly ${p.duration.complete})`;
console.log(msg);
if ( IS_REAL ) {
db.event.add({ user: p.user._id, provision: p._id, type: 'vms.warning-destroy' });
db.event.add({ provision: p._id, type: 'vms.warning-destroy' });
await db.provision.update(p._id, {"pendingNextAction": "destroy"});
await db.notification.add({ provision: p._id.toString(), type: 'warningDestroy', message: msg });
await sendEmail.sendWillDestroyIn24(p, p._scenarioDoc, Math.floor(limit/24), WARNING_DAYS);
@@ -124,7 +185,7 @@ const doDestroy = async function(p, limit, typeSchedule) {
let msg = `Provision destroyed - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Inactive' more than ${limit} hours (exactly ${p.duration.complete})`
console.log(msg);
if ( IS_REAL ) {
db.event.add({ user: p.user._id, provision: p._id, type: 'vms.exec-destroy' });
db.event.add({ provision: p._id, type: 'vms.exec-destroy' });
await postDestroy(p);
await db.notification.add({ provision: p._id.toString(), type: 'destroy', message: msg });
}
@@ -145,6 +206,18 @@ function check(type) {
});
}
function checkOthers() {
initOthers().then(function(){
db.mongoose.connection.close()
process.exit(0);
}).catch(function(e){
db.mongoose.connection.close()
console.log("Error", e);
process.exit(0);
});
}
// --------------------------------
switch (myArgs[0]) {
@@ -154,6 +227,9 @@ switch (myArgs[0]) {
case 'exec':
check("exec");
break;
case 'others':
checkOthers();
break;
default:
console.log('Sorry, that is not something I know how to do.');
process.exit(0);

View File

@@ -24,7 +24,7 @@ const doSendEmailErrorProvision = async function(p) {
let msg = `Send prrovision ERROR email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id}) provisioned with 'Errors'`;
console.log(msg);
if ( IS_REAL ) {
db.event.add({ user: p.user._id, provision: p._id, type: 'vms.warning-error' });
db.event.add({ provision: p._id, type: 'vms.warning-error' });
await db.notification.add({ provision: p._id.toString(), type: 'warningError', message: msg });
await sendEmail.sendProvisionError(p, p._scenarioDoc);
}

View File

@@ -61,10 +61,14 @@ async function init(type) {
var filter = {
"isDestroyed": false,
"isDeleted": false,
"$or": [ {"runForever":{ "$exists": false }}, {"runForever": false}],
"statusVms": "Running",
"vmImage": { "$exists": true },
"vmImage.vm1": { "$exists": true }
"$and": [{"$or": [
{ "options": {"$exists": true}, "options.vm1": { "$exists": true } },
{ "vmImage": {"$exists": true}, "vmImage.vm1": { "$exists": true } },
]}]
};
if ( type === "warning" ) {
filter.pendingNextAction = { $ne: "stopVms" };
cb = doSendEmailWarning;
@@ -114,7 +118,7 @@ const doSendEmailWarning = async function(p, limit, typeSchedule) {
let msg = `Send warning STOP email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenarioDoc.title}' (${p._id} - ${typeSchedule}) being 'Running' more than ${limit} hours, (exactly ${p.duration.complete})`;
console.log(msg);
if ( IS_REAL ) {
db.event.add({ user: p.user._id, provision: p._id, type: 'vms.warning-stop' });
db.event.add({ provision: p._id, type: 'vms.warning-stop' });
await db.provision.update(p._id, {"pendingNextAction": "stopVms"});
await db.notification.add({ provision: p._id.toString(), type: 'warningStop', message: msg });
await sendEmail.sendWillStopIn24(p, p._scenarioDoc, Math.floor(limit/24), WARNING_DAYS);
@@ -138,8 +142,8 @@ const doStop = async function(p, limit, typeSchedule) {
});
}
//Stop VMs indefinitely
db.event.add({ user: p.user._id, provision: p._id, type: 'vms.exec-stop' });
await cli.deallocate(p._id, true);
db.event.add({ provision: p._id, type: 'vms.exec-stop' });
await cli.deallocate(p._id, null, true);
await db.notification.add({ provision: p._id.toString(), type: 'stop', message: msg });
}

View File

@@ -57,8 +57,8 @@ async function getProvisions(filter){
console.log(res.results.length);
await asyncForEach(res.results, async function(prov){
if ( prov.vmType ) {
//console.log("vmType", prov.vmType, prov.vmImage)
prov["vmImage"] = {
//console.log("vmType", prov.vmType, prov.options)
prov["options"] = {
"vm1": {
"vmType": prov.vmType,
"diskSizeGb": 128

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-cli",
"version": "1.3.0",
"version": "2.0.5",
"scripts": {
},
"private": true,

View File

@@ -2,7 +2,6 @@ const AWS = require("aws-sdk");
const db = require("./mongo");
const utils = require("./utils");
function _getRgName(provision) {
let rgName = provision.scenario.toUpperCase();
rgName = rgName.replace(/AZQMI/g, 'QMI');
@@ -20,7 +19,98 @@ function _getRegion(provision) {
return region;
}
function deallocate(provision, isSendEmailAfter) {
function _getDbIndentifier(provision) {
var out;
if ( provision.outputs['db_instance_id'] ) {
out = provision.outputs['db_instance_id'];
} else if (provision.outputs['db_instance_endpoint']) {
out = provision.outputs['db_instance_endpoint'].split(".")[0];
}
return out;
}
async function stopDbInstance(provision, triggerUserId, isSendEmailAfter) {
if (provision.statusVms === 'Stopped' || provision.statusVms === 'Stopping'){
return;
}
var identifier = _getDbIndentifier(provision);
if ( !identifier ) {
return;
}
await db.provision.update(provision._id.toString(), {"statusVms": "Stopping" });
db.event.add({ user: triggerUserId, provision: provision._id, type: `db.stopping` });
return new Promise((resolve, reject) => {
let region = _getRegion(provision);
var rds = new AWS.RDS({apiVersion: '2014-10-31', region: region});
var params = {
DBInstanceIdentifier: identifier
};
console.log(`AWSCLI# DB (${identifier}) stopping...`);
rds.stopDBInstance(params, async function(err, data) {
if (err) {
console.log("AWSCLI# ERROR stopping DB: "+identifier, err.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
reject(err);
} else {
console.log(`AWSCLI# DB (${identifier}) stopped!`);
resolve(data);
}
});
});
}
async function startDbInstance(provision, triggerUserId) {
if (provision.statusVms === 'Running' || provision.statusVms === 'Starting'){
return;
}
var identifier = _getDbIndentifier(provision);
if ( !identifier ) {
return;
}
await db.provision.update(provision._id.toString(), {"statusVms": "Starting" });
db.event.add({ user: triggerUserId, provision: provision._id, type: `db.starting` });
return new Promise((resolve, reject) => {
let region = _getRegion(provision);
var rds = new AWS.RDS({apiVersion: '2014-10-31', region: region});
var params = {
DBInstanceIdentifier: identifier
};
console.log(`AWSCLI# DB (${identifier}) starting...`);
rds.startDBInstance(params, async function(err, data) {
if (err) {
console.log("AWSCLI# ERROR starting DB: "+identifier, err.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
reject(err);
} else {
console.log(`AWSCLI# DB (${identifier}) starting (2)...`);
rds.waitFor('dBInstanceAvailable', params, async function(err, data) {
if (err) {
console.log("AWSCLI# ERROR waiting for DB to become available: "+identifier, err.stack);
reject(err);
} else {
console.log(`AWSCLI# DB (${identifier}) started!`);
await utils.afterStartVms( provision, triggerUserId, "db" );
}
});
resolve(data);
}
});
});
}
function deallocate(provision, triggerUserId, isSendEmailAfter) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
@@ -62,7 +152,7 @@ function deallocate(provision, isSendEmailAfter) {
reject(err1);
} else {
console.log("AWSCLI# Ec2s stopped!");
await utils.afterStopVms( provision, isSendEmailAfter );
await utils.afterStopVms( provision, triggerUserId, isSendEmailAfter );
resolve(data);
}
});
@@ -74,7 +164,7 @@ function deallocate(provision, isSendEmailAfter) {
});
}
function start(provision) {
function start(provision, triggerUserId) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
@@ -114,7 +204,7 @@ function start(provision) {
reject(err1);
} else {
console.log("AWSCLI# Ec2s started!");
await utils.afterStartVms( provision );
await utils.afterStartVms( provision, triggerUserId );
resolve(data);
}
});
@@ -128,3 +218,5 @@ function start(provision) {
module.exports.deallocate = deallocate;
module.exports.start = start;
module.exports.stopDbInstance = stopDbInstance;
module.exports.startDbInstance = startDbInstance;

View File

@@ -42,7 +42,7 @@ async function asyncForEach(array, callback) {
}
}
async function deallocate(provision, isSendEmailAfter ) {
async function deallocate(provision, triggerUserId, isSendEmailAfter ) {
let rgName = _getRgName(provision);
console.log("AzureCLI# Deallocating VMs for resource group: "+rgName);
@@ -58,7 +58,7 @@ async function deallocate(provision, isSendEmailAfter ) {
await computeClient.virtualMachines.deallocate(rgName, vm.name);
});
await utils.afterStopVms(provision, isSendEmailAfter);
await utils.afterStopVms(provision, triggerUserId, isSendEmailAfter);
console.log("AzureCLI# All VMs DEALLOCATED for resource group: "+rgName);
} catch ( error ) {
@@ -67,7 +67,7 @@ async function deallocate(provision, isSendEmailAfter ) {
}
}
async function start(provision){
async function start(provision, triggerUserId){
let rgName = _getRgName(provision);
console.log("AzureCLI# Starting VMs for resource group: "+rgName);
@@ -85,7 +85,7 @@ async function start(provision){
await computeClient.virtualMachines.start(rgName, vm.name);
});
await utils.afterStartVms( provision );
await utils.afterStartVms( provision, triggerUserId );
console.log("AzureCLI# All VMs RUNNING for resource group: "+rgName);
} catch ( error ) {

View File

@@ -40,7 +40,7 @@ async function _createApp(provision, backendPort, backendType) {
console.log("Barracuda# Creating DNS record in Azure: "+cname);
azurecli.createDNSRecord(provision, cname);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'provision.is-public' });
db.event.add({ provision: provision._id, type: 'provision.is-public' });
}).catch(function(err){
console.log("Barracuda# Error creating Barracuda App", err);
@@ -62,7 +62,7 @@ async function deleteApp(provision) {
client.delete(provision.barracudaAppId).then(function() {
console.log("Barracuda# Barracuda App deleted! ID: "+ provision.barracudaAppId);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'provision.is-private' });
db.event.add({ provision: provision._id, type: 'provision.is-private' });
}).catch(function(err){
console.log("Barracuda# Error deleting Barracuda App", err);
});

View File

@@ -3,31 +3,32 @@ const azurecli = require("./azurecli");
const db = require("./mongo");
async function deallocate(provId, isSendEmailAfter ) {
async function deallocate(provId, userId, isSendEmailAfter ) {
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
if (provision.scenario === 'azqmi-fort'){
return awscli.deallocate(provision, isSendEmailAfter);
return awscli.deallocate(provision, userId, isSendEmailAfter);
} else {
return azurecli.deallocate(provision, isSendEmailAfter);
return azurecli.deallocate(provision, userId, isSendEmailAfter);
}
} catch (err) {
console.log("CLI# ERROR stopping VMs", err);
}
}
async function start(provId){
async function start(provId, userId){
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
if (provision.scenario === 'azqmi-fort'){
return awscli.start(provision);
return awscli.start(provision, userId);
} else {
return azurecli.start(provision);
return azurecli.start(provision, userId);
}
} catch (err) {
console.log("CLI# ERROR starting VMs", err);
@@ -42,6 +43,30 @@ async function updateVmsTags(provId, tagsEdit) {
}
}
async function stopDb(provId, userId, isSendEmailAfter) {
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
return awscli.stopDbInstance(provision, userId, isSendEmailAfter);
} catch (err) {
console.log("CLI# ERROR stopping DB", err);
}
}
async function startDb(provId, userId) {
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
return awscli.startDbInstance(provision, userId);
} catch (err) {
console.log("CLI# ERROR stopping DB", err);
}
}
module.exports.deallocate = deallocate;
module.exports.start = start;
module.exports.updateVmsTags = updateVmsTags;
module.exports.updateVmsTags = updateVmsTags;
module.exports.stopDb = stopDb;
module.exports.startDb = startDb;

View File

@@ -1,4 +0,0 @@
module.exports = {
DOCKERIMAGE_TERRAFORM: "qlikgear/terraform:0.13.4",
PROVISION_VERSION: 20201124
}

View File

@@ -1,7 +1,5 @@
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
const crypto = require("crypto");
//mongoose.set('debug', true)
const schema = new mongoose.Schema({

View File

@@ -1,6 +1,4 @@
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const destroySchema = new mongoose.Schema({

View File

@@ -1,8 +1,4 @@
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
const crypto = require("crypto");
//mongoose.set('debug', true)
const schema = new mongoose.Schema({
created: {
@@ -12,12 +8,12 @@ const schema = new mongoose.Schema({
},
user: {
type: mongoose.Types.ObjectId,
ref: 'User',
index : true
ref: 'User'
},
provision: {
type: mongoose.Types.ObjectId,
ref: 'Provision'
ref: 'Provision',
index : true
},
type: {
type: String

View File

@@ -1,7 +1,4 @@
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const sc = new mongoose.Schema({
created: {

View File

@@ -1,6 +1,4 @@
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const provisionSchema = new mongoose.Schema({
user: {
@@ -27,6 +25,7 @@ const provisionSchema = new mongoose.Schema({
},
description: String,
vmImage: Object,
options: Object,
status: {
type: String,
default: "queued"
@@ -106,6 +105,13 @@ const provisionSchema = new mongoose.Schema({
parent: {
type: mongoose.Types.ObjectId,
ref: 'Provision'
},
runForever: {
type: Boolean,
default: false
},
guacaConnId: {
type: String
}
},{
toObject: {virtuals:true},

View File

@@ -1,6 +1,4 @@
const mongoose = require('mongoose')
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const scenarioSchema = new mongoose.Schema({
@@ -35,6 +33,10 @@ const scenarioSchema = new mongoose.Schema({
type: Boolean,
default: false
},
forceExternalAccess: {
type: Boolean,
default: false
},
isDivvyEnabled: {
type: Boolean,
default: false
@@ -91,6 +93,9 @@ const scenarioSchema = new mongoose.Schema({
allowedOnScheduleRunningDays: {
type: Number,
default: 4
},
terraformImage: {
type: String
}
});

View File

@@ -1,6 +1,4 @@
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const sc = new mongoose.Schema({

View File

@@ -1,6 +1,5 @@
const mongoose = require('mongoose')
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const schema = new mongoose.Schema({
created: {
type: Date,

View File

@@ -1,6 +1,4 @@
const mongoose = require('mongoose')
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const subSchema = new mongoose.Schema({
created: {

View File

@@ -0,0 +1,67 @@
const mongoose = require('mongoose');
const crypto = require("crypto");
const schema = new mongoose.Schema({
description: {
type: String
},
user: {
type: mongoose.Types.ObjectId,
ref: 'User'
},
template: {
type: mongoose.Types.ObjectId,
ref: 'TrainingTemplate'
},
created: {
type: Date,
default: Date.now
},
updated: {
type: Date,
default: Date.now
},
status: {
type: String,
default: "inactive" //active, inactive, terminated
},
qcsTenantHost: {
type: String
},
qcsApiKey: {
type: String
},
qaUrl: {
type: String
},
qaToken: {
type: String
},
cloudshareClass: {
type: String
},
qcsSharedSpace: {
type: String
},
qcsDataSpace: {
type: String
},
passwd: {
type: String,
default: function() {
return crypto.randomBytes(4).toString('hex');
}
},
studentsCount: {
type: Number,
default: 0
},
studentEmailFilter: {
type: String // qlik.com,talend.com,gmail.com
}
});
module.exports = mongoose.model('TrainingSession', schema);

View File

@@ -0,0 +1,22 @@
const mongoose = require('mongoose');
const schema = new mongoose.Schema({
created: {
type: Date,
default: Date.now
},
email: {
type: String
},
session: {
type: mongoose.Types.ObjectId, ref: 'TrainingSession'
},
status: {
type: String,
default: 'pending'
}
});
module.exports = mongoose.model('TrainingStudent', schema);

View File

@@ -0,0 +1,35 @@
const mongoose = require('mongoose');
const schema = new mongoose.Schema({
index: {
type: String
},
created: {
type: Date,
default: Date.now
},
title: {
type: String
},
description: {
type: String
},
publicDescription: {
type: String
},
cloudshare: {
type: Boolean
},
qcs: {
type: Boolean
},
needQcsAutomation: {
type: Array //['main']
},
needQcsApiKey: {
type: Boolean
}
});
module.exports = mongoose.model('TrainingTemplate', schema);

View File

@@ -1,6 +1,4 @@
const mongoose = require('mongoose')
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const userSchema = new mongoose.Schema({
@@ -14,7 +12,11 @@ const userSchema = new mongoose.Schema({
default: Date.now
},
displayName: String,
upn: String,
upn: {
type: String,
index: true
},
sub: String,
oid: {
type: String,
index: true
@@ -31,6 +33,19 @@ const userSchema = new mongoose.Schema({
},
qcsUserSubject: {
type: String
},
active: {
type: Boolean,
default: true
},
jobTitle: {
type: String
},
mail: {
type: String
},
featureFlags: {
type: Array
}
});

View File

@@ -1,7 +1,4 @@
const mongoose = require('mongoose')
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const userSchema = new mongoose.Schema({
type: String,

View File

@@ -1,18 +1,10 @@
const mongoose = require('mongoose');
const boom = require('@hapi/boom');
const options = {
loggerLevel: 'error',
useNewUrlParser: true,
//reconnectInterval: 2000,
//reconnectTries: 30, // Retry up to 30 times
useCreateIndex: true,
useUnifiedTopology: true
};
console.log("--- MongoDB connecting... ", process.env.MONGO_URI);
// Connect to DB
mongoose.connect(process.env.MONGO_URI, options);
mongoose.connect(process.env.MONGO_URI);
// When successfully connected
mongoose.connection.on('connected', () => {
@@ -44,7 +36,9 @@ const Notification = require('./models/Notification');
const Subscription = require('./models/Subscription');
const Event = require('./models/Event');
const SharedProvision = require('./models/SharedProvision');
const TrainingTemplate = require('./models/TrainingTemplate');
const TrainingSession = require('./models/TrainingSession');
const TrainingStudent = require('./models/TrainingStudent');
const getNewCountExtend = function(provision) {
return provision.countExtend !== undefined? (provision.countExtend + 1) : 1;
@@ -96,16 +90,24 @@ const getPage = async ( model, filter, page, populates, select ) => {
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "schedule"}).populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
if ( model === Scenario ) {
exec = exec.populate('subscription').populate('deployOpts');
}
if ( model === Event ) {
exec = exec.populate({path: 'user', select: 'displayName'});
}
if ( model === TrainingSession ) {
exec = exec.populate('user').populate('template');
}
}
const entity = await exec;
@@ -174,16 +176,23 @@ const get = async (model, filter, select, skip, limit, populates, reply) => {
},{
path: 'destroy',
select: "-user -jobId"
}]}).populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}]}).populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
if ( model === Scenario ) {
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
}
if ( model === Event ) {
exec = exec.populate({path: 'user', select: 'displayName'});
}
if ( model === TrainingSession ) {
exec = exec.populate('user').populate('template');
}
}
const entity = await exec;
@@ -210,14 +219,20 @@ const getById = async (model, id, reply) => {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
if ( model === Scenario ) {
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
}
if ( model === Event ) {
exec = exec.populate({path: 'user', select: 'displayName'});
}
if ( model === TrainingSession ) {
exec = exec.populate('user').populate('template');
}
const entity = await exec;
return entity;
} catch (err) {
@@ -232,14 +247,20 @@ const getOne = async (model, filter, reply) => {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
if ( model === Scenario ) {
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
}
if ( model === Event ) {
exec = exec.populate({path: 'user', select: 'displayName'});
}
if ( model === TrainingSession ) {
exec = exec.populate('user').populate('template');
}
const entity = await exec;
return entity;
} catch (err) {
@@ -266,14 +287,17 @@ const update = async (model, id, body, reply) => {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
if ( model === Scenario ) {
exec = exec.populate('subscription').populate('deployOpts');
}
if ( model === TrainingSession ) {
exec = exec.populate('user').populate('template');
}
const update = await exec;
return update;
} catch (err) {
@@ -293,14 +317,17 @@ const updateMany = async (model, filter, body, reply) => {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn oid active'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
if ( model === Scenario ) {
exec = exec.populate('subscription').populate('deployOpts');
}
if ( model === TrainingSession ) {
exec = exec.populate('user').populate('template');
}
return await exec;
@@ -382,6 +409,9 @@ module.exports = {
event: _m(Event),
user: _m(User),
sharedProvision: _m(SharedProvision),
trainingSession: _m(TrainingSession),
trainingTemplate: _m(TrainingTemplate),
trainingStudent: _m(TrainingStudent),
utils: {
getNewTimeRunning: getNewTimeRunning,
getNewCountExtend: getNewCountExtend
@@ -398,7 +428,10 @@ module.exports = {
ApiKey: ApiKey,
Subscription: Subscription,
Event: Event,
SharedProvision: SharedProvision
SharedProvision: SharedProvision,
TrainingSession: TrainingSession,
TrainingTemplate: TrainingTemplate,
TrainingStudent: TrainingStudent
}
};

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-common",
"version": "1.1.6",
"version": "2.0.0",
"dependencies": {
"@azure/arm-compute": "^15.0.0",
"@azure/arm-dns": "^4.0.0",
@@ -8,9 +8,9 @@
"@hapi/boom": "^9.1.0",
"aws-sdk": "^2.942.0",
"axios": "^0.21.1",
"barracuda-api": "https://gitlab.com/qlik_gear/barracuda-api-node.git#0.0.10",
"barracuda-api": "https://gitlab.com/qlik_gear/barracuda-api-node.git#1.1.0",
"bull": "^3.11.0",
"mongoose": "^5.7.4",
"mongoose": "^6.11.1",
"nodemailer": "^6.4.2",
"uuid": "^8.3.2"
}

View File

@@ -5,19 +5,22 @@ export const TF_APPLY_QUEUE = 'TF_APPLY_QUEUE';
export const TF_DESTROY_QUEUE = 'TF_DESTROY_QUEUE';
export const TF_APPLY_QSEOK_QUEUE = 'TF_APPLY_QSEOK_QUEUE';
export const STOP_CONTAINER_QUEUE = 'STOP_CONTAINER_QUEUE';
export const SYNAPSE_QUEUE = 'SYNAPSE_QUEUE';
var terraformApplyQueue = new Queue(TF_APPLY_QUEUE, process.env.REDIS_URL);
var terraformDestroyQueue = new Queue(TF_DESTROY_QUEUE, process.env.REDIS_URL);
var terraformApplyQseokQueue = new Queue(TF_APPLY_QSEOK_QUEUE, process.env.REDIS_URL);
var stopContainerQueue = new Queue(STOP_CONTAINER_QUEUE, process.env.REDIS_URL);
var synapseQueue = new Queue(SYNAPSE_QUEUE, process.env.REDIS_URL);
export const queues = {
[TF_APPLY_QUEUE]: terraformApplyQueue,
[TF_DESTROY_QUEUE]: terraformDestroyQueue,
[TF_APPLY_QSEOK_QUEUE]: terraformApplyQseokQueue,
[STOP_CONTAINER_QUEUE]: stopContainerQueue
[STOP_CONTAINER_QUEUE]: stopContainerQueue,
[SYNAPSE_QUEUE]: synapseQueue
};

View File

@@ -4,7 +4,7 @@ const https = require("https");
const SMTP_EMAIL_SENDER = process.env.SMTP_EMAIL_SENDER;
const HOSTNAME_URL = process.env.HOSTNAME_URL || "https://qmicloud.qliktech.com";
const FOOTER = `<div style="color:#404040;font-size:16px;margin:30px 0px">
const FOOTER = `<div style="color:#404040;font-size:16px;padding:30px 0px 50px 0px">
<p style="margin:0px">Check it out at <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
</div>`;
@@ -86,6 +86,25 @@ function getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays) {
</div>`;
}
function getHtmlScenarioIndays( provision, scenario, period, warningDays) {
var common = _getCommonDetails(provision,scenario);
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
<p style="margin:0px">Provision '${scenario.title}' running more than ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario was meant to live active no more than 90 days. It's been 80 days since creation and it will be automatically DESTROYED in ${warningDays} days. Please, consider to backup it up manually if you don't want to lose data.</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlScenarioVMsStopped( provision, scenario) {
var common = _getCommonDetails(provision,scenario);
@@ -109,6 +128,26 @@ function getHtmlScenarioVMsStopped( provision, scenario) {
</div>`;
}
function getHtmlSharedProvision( provision, scenario) {
var common = _getCommonDetails(provision,scenario);
return `<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
<div style="background-color:white;height:100%;padding:20px 10px">
<div style="color:#404040;font-size:34px;text-align:center;margin:20px">
<p style="margin:0px">QMI Cloud</p>
</div>
<div style="color:#404040;font-size:22px;margin:20px 0px 40px 0px">
<p style="margin:0px">Provision '${scenario.title}'</p>
</div>
<div style="color:#404040;font-size:18px;margin:30px 0px">
<p style="margin:0px;color: #FF2020">'${provision.user.displayName}' is sharing with you!</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays ) {
var common = _getCommonDetails(provision,scenario);
return`<div style="width:600px;color:black!important;font-family:'Source Sans Pro',sans-serif;padding:50px">
@@ -136,15 +175,20 @@ function getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays )
function getHtmlNewProvision(provision, scenario) {
var htmlint;
if ( provision && provision.outputs ) {
htmlint = `<div style="color:#404040;font-size:18px;padding: 10px 0px;">Connection resources</div>`;
htmlint = `<div style="color:#404040;font-size:20px;padding: 10px 0px;">Connection resources</div>`;
htmlint += `<table style="width:100%" border="0">`;
} else {
htmlint = "";
}
for (let key in provision.outputs) {
htmlint += `<div>
<span style="color:#404040">${key}</span>
<pre style="color:#404040;">${provision.outputs[key]}</pre>
</div>`;
htmlint += `<tr>
<td style="padding-right: 15px"><b style="color:#404040">${key}</b></td>
<td><pre style="color:#404040;">${provision.outputs[key]}</pre></td>
</tr>`;
}
if ( provision && provision.outputs ) {
htmlint += `</table>`;
}
var common = _getCommonDetails(provision, scenario);
@@ -243,17 +287,30 @@ async function sendWillDestroyIn24( provision, scenario, period, warningDays ) {
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${(warningDays*24)} hours`, htmlText);
}
async function sendWillDestroyInDays( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioIndays( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${warningDays} days`, htmlText);
}
async function sendVMsStopped( provision, scenario ) {
const htmlText = getHtmlScenarioVMsStopped( provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - VMs stopped automatically', htmlText);
}
async function sendSharedProvision(provision, shareWithUser) {
const htmlText = getHtmlSharedProvision( provision, provision._scenarioDoc);
await _doSend(shareWithUser.upn, `${provision.user.displayName} shared a QMI Cloud provision with you.`, htmlText);
}
module.exports.sendProvisionSuccess = sendProvisionSuccess;
module.exports.sendProvisionError = sendProvisionError;
module.exports.sendDestroyedSuccess = sendDestroyedSuccess;
module.exports.sendWillStopIn24 = sendWillStopIn24;
module.exports.sendWillDestroyInDays = sendWillDestroyInDays;
module.exports.sendVMsStopped = sendVMsStopped;
module.exports.sendWillDestroyIn24 = sendWillDestroyIn24;
module.exports.sendSharedProvision= sendSharedProvision;
module.exports._doSend = _doSend;

View File

@@ -1,82 +1,72 @@
const db = require("./mongo");
const sendEmail = require("./send-email");
async function afterStopVms( provision, auto ) {
async function afterStopVms( provision, triggerUserId, auto = false, type = 'vms' ) {
let timeRunning = db.utils.getNewTimeRunning(provision);
const dateNow = new Date();
let patch = {
"statusVms": "Stopped",
"timeRunning": timeRunning,
"stoppedFrom": dateNow,
"pendingNextAction": undefined
"pendingNextAction": null
};
if ( auto && provision._scenarioDoc ) { //From CLI (auto stop)
let msg = "";
let msg = `[CLI] TotalTimeRunning: ${timeRunning} mins`;
// Actual onschedule reset
if ( provision.schedule && !provision.schedule.is24x7 ) {
patch["startDateOnSchedule"] = dateNow;
patch["endDateOnSchedule"] = dateNow;
msg = "Schedule time has been reset.";
msg += " - (Schedule) accumlated running time has been reset.";
} else {
msg = "24x7 vms auto-stopped due to limitted running time reached.";
msg += " - (24x7) accumlated running time was reached.";
}
msg += `New totalTimeRunning: ${timeRunning} mins`;
await db.provision.update(provision._id.toString(), patch);
await sendEmail.sendVMsStopped(provision, provision._scenarioDoc);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.stop-auto', message: msg });
db.event.add({ provision: provision._id, type: 'vms.stop', message: msg });
} else { //On Demand stop
let msg = "";
if ( provision.schedule && !provision.schedule.is24x7 ) {
patch["endDateOnSchedule"] = dateNow;
//This is temporary, only to make sure there is some initial value soon
if ( !provision["startDateOnSchedule"] ) {
patch["startDateOnSchedule"] = dateNow;
msg = "startDateOS: " + dateNow.toISOString();
} else {
msg = "startDateOS: " + new Date(provision.startDateOnSchedule).toISOString();
}
msg += (" - endDateOS: " + dateNow.toISOString());
}
msg += ` - New totalTimeRunning: ${timeRunning} mins`;
await db.provision.update(provision._id.toString(), patch);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.stop-ondemand', message: msg });
db.event.add({ user: triggerUserId, provision: provision._id, type: `${type}.stop`, message: `[Manual] TotalTimeRunning: ${timeRunning} mins` });
}
}
async function afterStartVms( provision ) {
async function afterStartVms( provision, triggerUserId, type = 'vms' ) {
const dateNow = new Date();
let countExtend = db.utils.getNewCountExtend(provision);
var patch = {
"statusVms": "Running",
"runningFrom": dateNow,
"countExtend": countExtend,
"pendingNextAction": undefined
"pendingNextAction": null
};
// Actual onschedule reset
let msg = "";
let msg = `[Manual] TotalTimeRunning: ${provision.timeRunning} mins`;
if ( provision.schedule && !provision.schedule.is24x7 ) {
msg = "Schedule time has been reset.";
msg += " - Schedule time has been reset.";
patch["startDateOnSchedule"] = dateNow;
patch["endDateOnSchedule"] = dateNow;
} else {
msg = `24x7, New count extend: ${countExtend}`;
}
msg += `TotalTimeRunning so far: ${provision.timeRunning} mins`;
msg += ` - 24x7, New count extend: ${countExtend}`;
}
await db.provision.update(provision._id.toString(), patch);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.start-ondemand', message: msg });
db.event.add({ user: triggerUserId, provision: provision._id, type: `${type}.start`, message: msg });
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,11 @@ const PROJECT_PATH = process.env.PROJECT_PATH;
const tf = require("./docker/tf");
const azure = require("./docker/azure");
const sendEmail = require("qmi-cloud-common/send-email");
const barracuda = require("qmi-cloud-common/barracuda");
const guaca = require("./guacamole");
module.exports = async function(job) {
const triggerUser = job.data.user;
var prov = await db.provision.update(job.data.id, {
"status": "initializing",
@@ -22,7 +24,7 @@ module.exports = async function(job) {
var idProv = prov._id.toString();
db.event.add({ user: prov.user._id, provision: prov._id, type: 'provision.init' });
db.event.add({ user: triggerUser._id, provision: prov._id, type: 'provision.init' });
// TERRAFORM INIT
return tf.init(prov)
@@ -32,7 +34,7 @@ module.exports = async function(job) {
return Promise.reject({"success": false, "error": "Error at Terraform Init", provStatus: "error_init"});
} else {
// TERRAFORM PLAN
return tf.plan(prov, job.data._scenario);
return tf.plan(prov);
}
} )
.then( async function(res) {
@@ -43,7 +45,7 @@ module.exports = async function(job) {
const dateNow = new Date();
let patch = {
"status": "provisioning",
"statusVms": (prov.vmImage && prov.vmImage.vm1)? "Running" : "N/A",
"statusVms": (prov.scenario === 'azqmi-synapse' || prov.scenario === 'awsqmi-rds' || prov.options && prov.options.vm1)? "Running" : "N/A",
"runningFrom": dateNow,
"runningTime": 0,
"countExtend": 0
@@ -58,51 +60,63 @@ module.exports = async function(job) {
// TERRAFORM APPLY
return tf.apply(prov);
} ).then( async function(res) {
// Save Provision status
if ( res.statusCode === 1 ) {
console.log(`ProcessorApply# Error at Terraform APPLY for provision (${idProv})`);
} else if ( res.statusCode === 137 ){
console.log(`ProcessorApply# Apply container must have been killed!`);
return Promise.reject({"success": false, "error": "Apply container must have been killed", provStatus: "aborted"});
}
var status = ( res.output.indexOf("Error:") !== -1 )? "error" : "provisioned";
return await db.provision.update(prov._id, {"status": status});
} ).then( async function(prov) {
return tf.outputs(prov).then( async function(outputs){
if ( outputs.barracudaAzureFqdn ) {
let fqdn = outputs.barracudaAzureFqdn;
delete outputs['barracudaAzureFqdn'];
return await db.provision.update(prov._id, {"barracudaAzureFqdn": fqdn, "outputs": outputs});
} else {
return await db.provision.update(prov._id, {"outputs": outputs});
}
});
} ).then( async function(prov) {
if ( prov.isExternalAccess && prov.barracudaAzureFqdn ) {
console.log(`ProcessorApply# Calling Barracuda service to create App and DNS CName for provision (${prov._id})`);
barracuda.createApp(prov);
}
return prov;
} ).then( async function(prov) {
// Generate Terraform OUTPUTS and store in provision object
return tf.outputs(prov).then( async function(outputs){
//if ( outputs.barracudaAzureFqdn ) {
// let fqdn = outputs.barracudaAzureFqdn;
// delete outputs['barracudaAzureFqdn'];
// return await db.provision.update(prov._id, {"barracudaAzureFqdn": fqdn, "outputs": outputs});
//} else {
return await db.provision.update(prov._id, {"outputs": outputs});
//}
});
} )
//.then( async function(prov) {
// if ( prov.isExternalAccess && prov.barracudaAzureFqdn ) {
// console.log(`ProcessorApply# Calling Barracuda service to create App and DNS CName for provision (${prov._id})`);
// barracuda.createApp(prov);
// }
// return prov;
//} )
.then( async function(prov) {
// Application Gateway assign policy
return azure.appgateway(prov, job.data._scenario);
} ).then( async function(prov) {
// Create Image
return azure.createimage(prov, job.data._scenario);
} ).then( async function(prov) {
// Guacamole Web access
return await guaca.setUserConnection(prov, job.data._scenario);
} ).then( function(prov) {
if (prov.status === "provisioned") {
db.event.add({ provision: prov._id, type: 'provision.finished' });
sendEmail.sendProvisionSuccess(prov, job.data._scenario);
} else {
db.event.add({ provision: prov._id, type: 'provision.error' });
sendEmail.sendProvisionError(prov, job.data._scenario);
}
db.event.add({ user: prov.user._id, provision: prov._id, type: 'provision.finished' });
return Promise.resolve({"success": true, provMongo: prov});
} ).catch( function(err) {
console.log("ProcessorApply# Provision: error", err);
var errormsg = err.provStatus? err.provStatus : 'error'
db.provision.update(prov._id, {"status": errormsg});
db.event.add({ user: prov.user._id, provision: prov._id, type: 'provision.'+errormsg });
db.event.add({ user: triggerUser._id, provision: prov._id, type: 'provision.'+errormsg });
if ( errormsg !== "aborted") {
sendEmail.sendProvisionError(prov, job.data._scenario);

View File

@@ -0,0 +1,170 @@
const Docker = require('dockerode');
const docker = new Docker({
'socketPath': '/home/docker.sock'
//'socketPath': '/var/run/docker.sock'
});
const fs = require("fs");
const path = require("path");
const SSHPATH = process.env.SSHPATH;
const DOCKERIMAGE = "mcr.microsoft.com/azure-cli";
const PROJECT_PATH = process.env.PROJECT_PATH;
const pause = function(provision) {
if ( provision.scenario !== 'azqmi-synapse' ) {
console.log(`SynapseAzCLI# provision (${provision._id}) is not AZ SYNAPSE`);
return {"message": `Won't do anything, provision (${provision._id}) is not AZ SYNAPSE`};
}
if ( !provision.outputs ) {
console.log(`SynapseAzCLI# provision (${provision._id}) AZ SYNAPSE has NO outputs`);
return {"message": `Won't do anything, provision (${provision._id}) AZ SYNAPSE has NO outputs`};
}
const name = `qmi-synpause-${provision._id}`;
console.log(`SynapseAzCLI# Pause Synapse: ${name}`);
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
var exec = ['synpause', provision.outputs.Synapse_WS_ID, provision.outputs.Synapse_Database];
console.log('SynapseAzCLI# Pause Synapse: exec: '+exec.join(" "));
return docker.run(DOCKERIMAGE, exec, processStream, {
//"Env": vars.envs,
"name": name,
//"WorkingDir": "/app",
"HostConfig": {
"Binds": [
`${provision.path}/synpause.sh:/bin/synpause`,
`${SSHPATH}:/root/.ssh`
]
}
}).then(function(data) {
var container = data[1];
console.log(`SynapseAzCLI# Pause Synapse: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove().then(function(){
console.log(`SynapseAzCLI# Pause Synapse: Container '${name}' removed!`);
return data[0].StatusCode;
});
}).then(async function(statusCode) {
return {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode};
});
}
const resume = function(provision) {
if ( provision.scenario !== 'azqmi-synapse' ) {
console.log(`SynapseAzCLI# provision (${provision._id}) is not AZ SYNAPSE`);
return {"message": `Won't do anything, provision (${provision._id}) is not AZ SYNAPSE`};
}
if ( !provision.outputs ) {
console.log(`SynapseAzCLI# provision (${provision._id}) AZ SYNAPSE has NO outputs`);
return {"message": `Won't do anything, provision (${provision._id}) AZ SYNAPSE has NO outputs`};
}
const name = `qmi-synresume-${provision._id}`;
console.log(`SynapseAzCLI# Resume Synapse: ${name}`);
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
var exec = ['synresume', provision.outputs.Synapse_WS_ID, provision.outputs.Synapse_Database];
console.log('SynapseAzCLI# Resume Synapse: exec: '+exec.join(" "));
return docker.run(DOCKERIMAGE, exec, processStream, {
//"Env": vars.envs,
"name": name,
//"WorkingDir": "/app",
"HostConfig": {
"Binds": [
`${provision.path}/synresume.sh:/bin/synresume`,
`${SSHPATH}:/root/.ssh`
]
}
}).then(function(data) {
var container = data[1];
console.log(`SynapseAzCLI# Resume Synapse: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove().then(function(){
console.log(`SynapseAzCLI# Resume Synapse: Container '${name}' removed!`);
return data[0].StatusCode;
});
}).then(async function(statusCode) {
return {"output": fs.readFileSync(provision.logFile), "statusCode": statusCode};
});
}
const copySnapshots = function(data) {
let snapName = data.snapName;
let regions = data.regions;
const name = `qmi-copysnaps`;
console.log(`CopySnapsAzCLI# container: ${name}`);
var processStream = fs.createWriteStream("/logs/general/qmi-snapshots.log", {flags:'a'});
var exec = ['copy-snapshots-to-sa', snapName, regions];
console.log('CopySnapsAzCLI# exec: '+exec.join(" "));
let scriptPath = path.join(PROJECT_PATH,'..');
return docker.run(DOCKERIMAGE, exec, processStream, {
//"Env": vars.envs,
"name": name,
//"WorkingDir": "/app",
"HostConfig": {
"Binds": [
`${scriptPath}/copy-snapshots-to-sa.sh:/bin/copy-snapshots-to-sa`,
`${SSHPATH}:/root/.ssh`
]
}
}).then(function(data) {
var container = data[1];
console.log(`CopySnapsAzCLI# '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove().then(function(){
console.log(`CopySnapsAzCLI# Container '${name}' removed!`);
return data[0].StatusCode;
});
}).then(async function(statusCode) {
return {"output": fs.readFileSync("/logs/general/qmi-snapshots.log"), "statusCode": statusCode};
});
}
const checkCopySnapshots = function(data) {
let snapName = data.snapName;
const name = `qmi-checkcopysnaps`;
console.log(`CheckCopySnapsAzCLI# container: ${name}`);
var processStream = fs.createWriteStream("/logs/general/qmi-snapshots.log", {flags:'a'});
var exec = ['checkcopystatus', snapName];
console.log('CheckCopySnapsAzCLI# exec: '+exec.join(" "));
let scriptPath = path.join(PROJECT_PATH,'..');
return docker.run(DOCKERIMAGE, exec, processStream, {
//"Env": vars.envs,
"name": name,
//"WorkingDir": "/app",
"HostConfig": {
"Binds": [
`${scriptPath}/checkcopystatus.sh:/bin/checkcopystatus`,
`${SSHPATH}:/root/.ssh`
]
}
}).then(function(data) {
var container = data[1];
console.log(`CheckCopySnapsAzCLI# '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove().then(function(){
console.log(`CheckCopySnapsAzCLI# Container '${name}' removed!`);
return data[0].StatusCode;
});
}).then(async function(statusCode) {
return {"output": fs.readFileSync("/logs/general/qmi-snapshots.log"), "statusCode": statusCode};
});
}
module.exports.pause = pause;
module.exports.resume = resume;
module.exports.copySnapshots = copySnapshots;
module.exports.checkCopySnapshots = checkCopySnapshots;

View File

@@ -3,11 +3,11 @@ const docker = new Docker({
'socketPath': '/home/docker.sock'
});
const fs = require('fs');
const config = require('qmi-cloud-common/config');
const GIT_SCENARIOS = process.env.GIT_SCENARIOS;
const GIT_TAG = process.env.GIT_TAG || "master";
const DOCKERIMAGE = process.env.DOCKERIMAGE_TERRAFORM || "qlikgear/terraform:1.0.1";
const SSHPATH = process.env.SSHPATH;
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
function hook_stdout(callback) {
var old_write = process.stdout.write
@@ -24,81 +24,53 @@ function hook_stdout(callback) {
}
}
function _buildVarsExec( exec, provision, scenario ) {
function _buildVarsExec( exec, provision ) {
let prefix = provision.scenario.toUpperCase();
prefix = prefix.replace(/AZQMI/g, 'QMI');
exec.push('-var');
exec.push(`prefix=${prefix}`);
if ( provision.deployOpts && provision.deployOpts.subsId ) {
exec.push('-var');
exec.push(`subscription_id=${provision.deployOpts.subsId}`);
}
//Deprecated
else if ( scenario && scenario.subscription && scenario.subscription.subsId ) {
exec.push('-var');
exec.push(`subscription_id=${scenario.subscription.subsId}`);
let gitBranch = GIT_TAG;
if ( provision._scenarioDoc && provision._scenarioDoc.gitBranch && provision._scenarioDoc.gitBranch.trim() !== "") {
gitBranch = provision._scenarioDoc.gitBranch;
}
let prefix = provision.scenario.toUpperCase();
prefix = prefix.replace(/AZQMI/g, 'QMI');
let envs = [
`AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}`,
`AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}`,
`AWS_DEFAULT_REGION=us-east-1`,
`TF_VAR_envbranch=${gitBranch}`,
`TF_VAR_user_email=${provision.user.upn}`,
`TF_VAR_user_oid=${provision.user.oid}`,
`TF_VAR_prefix=${prefix}`
];
if ( provision.deployOpts ) {
if ( provision.deployOpts.subsId ) {
exec.push('-var');
exec.push(`subscription_id=${provision.deployOpts.subsId}`);
}
if ( provision.deployOpts.location ) {
exec.push('-var');
exec.push(`location=${provision.deployOpts.location}`);
}
if ( provision.deployOpts.vnetExists ) {
exec.push('-var');
exec.push(`subnet_id=${provision.deployOpts.subnetId}`);
envs.push(`TF_VAR_subnet_id=${provision.deployOpts.subnetId}`);
if ( provision.isExternalAccess ) {
exec.push('-var');
exec.push(`app_gw_subnet=${provision.deployOpts.appGwSubnetId}`);
envs.push(`TF_VAR_app_gw_subnet=${provision.deployOpts.appGwSubnetId}`);
}
}
}
//Deprecated
else if ( scenario && scenario.subscription && scenario.subscription.vnetExists ) {
exec.push('-var');
exec.push(`subnet_id=${scenario.subscription.subnetId}`);
if ( scenario.isExternal || provision.isExternalAccess) {
exec.push('-var');
exec.push(`app_gw_subnet=${scenario.subscription.appGwSubnetId}`);
}
}
exec.push('-var');
exec.push(`provision_id=${provision._id}`);
exec.push('-var');
exec.push(`user_id=${provision.user.displayName}`);
if ( provision.scenario.indexOf('azqmi-qdi') !== -1 && provision.version && provision.version >= config.PROVISION_VERSION ) {
exec.push('-var');
exec.push(`user_email=${provision.user.upn}`);
}
if ( provision.scenario.indexOf('azqmi-synapse') !== -1 || provision.scenario.indexOf('azqmi-qdi') !== -1 ) {
exec.push('-var');
exec.push(`user_oid=${provision.user.oid}`);
}
if (!provision.vmImage) {
//Old way
if ( provision.vmType ) {
exec.push('-var');
exec.push(`vm_type=${provision.vmType}`);
}
if ( provision.nodeCount) {
exec.push('-var');
exec.push(`agent_count=${provision.nodeCount}`);
}
} else if ( provision.vmImage ) {
//New way
//DEPRECATED VMIMAGE
if ( provision.vmImage ) {
for ( let key in provision.vmImage ) {
if ( !provision.vmImage[key].disabled ) {
@@ -133,6 +105,50 @@ function _buildVarsExec( exec, provision, scenario ) {
}
}
}
// NEW ATTRS
if ( provision.options ) {
for ( let key in provision.options ) {
if ( !provision.options[key].disabled ) {
if ( key.indexOf("vm") !== -1 ) {
if ( provision.options[key].nodeCount ) {
exec.push('-var');
exec.push(`agent_count_${key}=${provision[key].nodeCount}`);
}
if ( provision.options[key].vmType ) {
exec.push('-var');
exec.push(`vm_type_${key}=${provision.options[key].vmType}`);
}else {
exec.push('-var');
exec.push(`vm_type_${key}=ENABLED`);
}
if ( provision.options[key].diskSizeGb ) {
exec.push('-var');
exec.push(`disk_size_gb_${key}=${provision.options[key].diskSizeGb}`);
}
if ( provision.options[key].selected && provision.options[key].selected.value) {
exec.push('-var');
exec.push(`image_reference_${key}=${provision.options[key].selected.value}`);
}
if ( provision.options[key].selected && provision.options[key].selected.init_password) {
exec.push('-var');
exec.push(`image_reference_${key}_init_password=${provision.options[key].selected.init_password}`);
}
} else {
if ( provision.options[key].selected && provision.options[key].selected.value) {
exec.push('-var');
exec.push(`attr_${key}=${provision.options[key].selected.value}`);
} else {
exec.push('-var');
exec.push(`attr_${key}=ENABLED`);
}
}
}
}
}
if ( provision.isExternalAccess ) {
exec.push('-var');
@@ -157,7 +173,7 @@ function _buildVarsExec( exec, provision, scenario ) {
}
}
return exec;
return {"exec": exec, "envs": envs};
}
const init = function( provision ) {
@@ -171,10 +187,9 @@ const init = function( provision ) {
}
let exec = ['terraform', 'init', '-no-color', `-from-module=${GIT_SCENARIOS}//${provision.scenario}?ref=${gitBranch}`];
console.log('Terraform# Init: exec: '+exec.join(" "));
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
console.log('Terraform# Init: version to use is : '+terraformImage);
return docker.run(terraformImage, exec, processStream, {
//"Env": ["VAR_ENV=whatever"],
console.log('Terraform# Init: version to use is : '+provision.terraformImage);
return docker.run(provision.terraformImage, exec, processStream, {
//"Env": [],
"name": name,
"WorkingDir": "/app",
"HostConfig": {
@@ -195,19 +210,19 @@ const init = function( provision ) {
});
};
const plan = function( provision, scenario ) {
const plan = function( provision ) {
const name = `qmi-tf-plan-${provision._id}`;
console.log(`Terraform# Plan: will spin up container: ${name}`);
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
//var processStream = process.stdout;
var exec = ['terraform', 'plan', '-no-color', '-input=false', '-out=tfplan' ];
exec = _buildVarsExec(exec, provision, scenario);
var vars = _buildVarsExec(exec, provision);
exec = vars.exec;
console.log('Terraform# Plan: exec: '+exec.join(" "));
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
console.log('Terraform# Plan: version to use is : '+terraformImage);
return docker.run(terraformImage, exec, processStream, {
//"Env": ["VAR_ENV=whatever"],
console.log('Terraform# Plan: version to use is : '+provision.terraformImage);
return docker.run(provision.terraformImage, exec, processStream, {
"Env": vars.envs,
"name": name,
"WorkingDir": "/app",
"HostConfig": {
@@ -238,10 +253,13 @@ const apply = function( provision ) {
var exec = ['terraform', 'apply', 'tfplan', '-no-color'];
console.log('Terraform# Apply: exec: '+exec.join(" "));
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
console.log('Terraform# Apply: version to use is : '+terraformImage);
return docker.run(terraformImage, exec, processStream, {
//"Env": ["VAR_ENV=whatever"],
console.log('Terraform# Apply: version to use is : '+provision.terraformImage);
return docker.run(provision.terraformImage, exec, processStream, {
"Env": [
`AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}`,
`AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}`,
`AWS_DEFAULT_REGION=us-east-1`
],
"name": name,
"WorkingDir": "/app",
"HostConfig": {
@@ -263,20 +281,19 @@ const apply = function( provision ) {
})
}
const destroy = function(destroyMongo, provision, scenario) {
const destroy = function(destroyMongo, provision) {
const name = `qmi-tf-destroy-${destroyMongo._id}`;
console.log(`Terraform# Destroy: will spin up container: ${name}`);
var processStream = fs.createWriteStream(destroyMongo.logFile, {flags:'a'});
var exec = ['terraform', 'destroy', '-auto-approve', '-no-color'];
exec = _buildVarsExec(exec, provision, scenario);
var vars = _buildVarsExec(exec, provision);
exec = vars.exec;
console.log('Terraform# Destroy: exec: '+exec.join(" "));
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
console.log('Terraform# Destroy: version to use is : '+terraformImage);
return docker.run(terraformImage, exec, processStream, {
//"Env": ["VAR_ENV=whatever"],
console.log('Terraform# Destroy: version to use is : '+provision.terraformImage);
return docker.run(provision.terraformImage, exec, processStream, {
"Env": vars.envs,
"name": name,
"WorkingDir": "/app",
"HostConfig": {
@@ -311,11 +328,8 @@ const outputs = function(provision) {
var unhook = hook_stdout(function(string, encoding, fd) {
tfout += string.trim();
});
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
return docker.run(terraformImage, exec, process.stdout, {
//"Env": ["VAR_ENV=whatever"],
return docker.run(provision.terraformImage, exec, process.stdout, {
//"Env": [],
"name": name,
"WorkingDir": "/app",
"HostConfig": {

View File

@@ -0,0 +1,302 @@
'use strict';
const axios = require('axios');
const https = require("https");
const db = require('qmi-cloud-common/mongo');
const URL = "https://qmicloud-dev.qliktech.com:8443/api/session/data/postgresql";
const GUACA_USERNAME = process.env.GUACA_USERNAME || "guacadmin";
const GUACA_PASSWORD = process.env.GUACA_PASSWORD;
const base64urlEncode = function(value) {
// Translate padded standard base64 to unpadded base64url
return Buffer.from(value).toString('base64').replace(/[+/=]/g,
(str) => ({
'+' : '-',
'/' : '_',
'=' : ''
})[str]
);
};
const guacamoleClientId = function(id, type, dataSource) {
return base64urlEncode([
id,
type,
dataSource
].join('\0'));
};
async function _auth() {
try {
const params = new URLSearchParams();
params.append('username', GUACA_USERNAME);
params.append('password', GUACA_PASSWORD);
var res = await axios.post('https://qmicloud-dev.qliktech.com:8443/api/tokens', params, {
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
return res.data.authToken;
} catch (err) {
// Handle Error Here
console.log("Guacamole# Could not auth", err);
return null;
}
}
async function _createUser(email, token) {
try {
var body = {
"username": email,
"attributes": {
"disabled": "",
"expired": "",
"access-window-start": "",
"access-window-end": "",
"valid-from": "",
"valid-until": "",
"timezone": null
}
};
var res = await axios({
url: `${URL}/users`,
method: "post",
data: body,
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
headers: {
'Guacamole-Token': token
}
});
console.log("Guacamole# User created: ", email);
return email;
} catch (err) {
// Handle Error Here
console.log("Guacamole# User already existed: ", email);
return email;
}
}
async function _createConnection(type, name, ip, username, password, token) {
try {
var body = {
"parentIdentifier": "ROOT",
"name": name,
"protocol": type,
"parameters": {
"port": type === "ssh"? "22" : "3389",
"read-only": "",
"swap-red-blue": "",
"cursor": "",
"color-depth": "",
"force-lossless": "",
"clipboard-encoding": "",
"disable-copy": "",
"disable-paste": "",
"dest-port": "",
"recording-exclude-output": "",
"recording-exclude-mouse": "",
"recording-include-keys": "",
"create-recording-path": "",
"enable-sftp": "",
"sftp-port": "",
"sftp-server-alive-interval": "",
"sftp-disable-download": "",
"sftp-disable-upload": "",
"enable-audio": "",
"wol-send-packet": "",
"wol-udp-port": "",
"wol-wait-time": "",
"security": "nla",
"disable-auth": "",
"ignore-cert": "true",
"gateway-port": "",
"server-layout": "",
"timezone": null,
"enable-touch": "",
"console": "",
"width": "",
"height": "",
"dpi": "",
"resize-method": "",
"normalize-clipboard": "",
"console-audio": "",
"disable-audio": "",
"enable-audio-input": "",
"enable-printing": "",
"enable-drive": "",
"disable-download": "",
"disable-upload": "",
"create-drive-path": "",
"enable-wallpaper": "",
"enable-theming": "",
"enable-font-smoothing": "",
"enable-full-window-drag": "",
"enable-desktop-composition": "",
"enable-menu-animations": "",
"disable-bitmap-caching": "",
"disable-offscreen-caching": "",
"disable-glyph-caching": "",
"preconnection-id": "",
"recording-exclude-touch": "",
"hostname": ip,
"username": username,
"password": password
},
"attributes": {
"max-connections": "2",
"max-connections-per-user": "2",
"weight": "",
"failover-only": "",
"guacd-port": "",
"guacd-encryption": ""
}
};
var res = await axios({
url: `${URL}/connections`,
method: "post",
data: body,
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
headers: {
'Guacamole-Token': token
}
});
console.log("Guacamole# Connection created: ", name);
return res.data;
} catch (err) {
// Handle Error Here
console.log("Guacamole# Could not create Guaca Connection", err.config.data);
return null;
}
}
async function _addConnectionToUser(email, identifier, token) {
try {
var body = [
{
"op": "add",
"path": "/connectionPermissions/"+identifier,
"value": "READ"
}
];
var res = await axios({
url: `${URL}/users/${email}/permissions`,
method: "patch",
data: body,
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
headers: {
'Guacamole-Token': token
}
});
console.log("Guacamole# Connection identifier ("+identifier+") added to user: ", email);
return res.data;
} catch (err) {
// Handle Error Here
console.log("Guacamole# Could not add connection "+identifier+" to Guaca User " + email);
return null;
}
}
async function setUserConnection(provision, scenario){
var resultProvision = provision;
if ( provision && provision.status === 'provisioned' && provision.options && provision.options.vm1 ) {
var token = await _auth();
if (token) {
await _createUser(provision.user.mail, token);
let ip = provision.outputs["RDP-ip"] || provision.outputs["Replicate__RDP_IP"] || provision.outputs["Private_IP"] || provision.outputs["IP"];
let credentials = provision.outputs["RDP-credentials"] || provision.outputs["Replicate__RDP-credentials"] || provision.outputs["User-and-Password"] || provision.outputs["All_DBs_User-and-Password"];
if (ip && credentials){
let username = credentials.split(" / ")[0];
let password = credentials.split(" / ")[1];
if (username && password){
let connection;
let connName = `(${scenario.name}_${provision._id}) - ${provision.description}`;
let type;
if ( scenario.labels.indexOf("linux") === -1 ) {
connection = await _createConnection("rdp",connName, ip, username, password, token);
type = "RDP";
} else {
connection = await _createConnection("ssh", connName, ip, username, password, token);
type = "SSH";
}
if (connection) {
let outputs = provision.outputs || {};
const guacClient = guacamoleClientId(connection.identifier,"c", "postgresql");
outputs[`WEB_${type}_ACCESS_WITH_GUACAMOLE`] = `https://qmicloud-dev.qliktech.com:8443/#/client/${guacClient}`;
_addConnectionToUser(provision.user.mail, connection.identifier, token);
resultProvision = await db.provision.update(provision._id, {"guacaConnId": connection.identifier, outputs: outputs});
} else {
console.log("Guacamole# No connection was created for provision: ", provision._id);
}
} else {
console.log("Guacamole# Could not find username or password for provision:", provision._id);
}
} else {
console.log("Guacamole# Could not find ip or credentials for provision:", provision._id);
}
}
}
return resultProvision;
}
async function deleteConnection(provision){
if ( provision.guacaConnId ) {
try {
var token = await _auth();
await axios({
url: `${URL}/connections/${provision.guacaConnId}`,
method: "delete",
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
headers: {
'Guacamole-Token': token
}
});
} catch (err) {
// Handle Error Here
console.log("Guacamole# Could not delete connection ("+provision.guacaConnId+")");
}
}
}
module.exports.setUserConnection = setUserConnection;
module.exports.deleteConnection = deleteConnection;

View File

@@ -1,4 +1,4 @@
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE } from 'qmi-cloud-common/queues';
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
var path = require("path");
@@ -7,6 +7,7 @@ queues[TF_APPLY_QUEUE].process("tf_apply_job", 10, path.resolve(__dirname, 'proc
queues[TF_APPLY_QSEOK_QUEUE].process("tf_apply_qseok_job", 10, path.resolve(__dirname, 'processor-apply-qseok.js'));
queues[TF_DESTROY_QUEUE].process("tf_destroy_job", 10, path.resolve(__dirname, 'processor-destroy.js'));
queues[STOP_CONTAINER_QUEUE].process("tf_abort_apply_job", 10, path.resolve(__dirname, 'processor-stop-container.js'));
queues[SYNAPSE_QUEUE].process("synapse_job", 10, path.resolve(__dirname, 'processor-synapse.js'));
console.log(`Worker queues started!`);

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-worker",
"version": "1.3.1",
"version": "2.1.0",
"scripts": {
"start": "node -r esm index.js",
"start:dev": "nodemon -r esm index.js",

View File

@@ -3,9 +3,13 @@ const db = require('qmi-cloud-common/mongo');
const path = require('path');
const sendEmail = require("qmi-cloud-common/send-email");
const barracuda = require("qmi-cloud-common/barracuda");
const guaca = require("./guacamole");
module.exports = async function(job){
var triggerUser = job.data.user;
var destroyMongo = await db.destroy.update(job.data.id, {
"status": "destroying",
"jobId": job.id,
@@ -27,34 +31,44 @@ module.exports = async function(job){
console.log(`ProcessorDestroy# There is no barracuda to be deleted` );
}
db.event.add({ user: provMongo.user._id, provision: provMongo._id, type: 'provision.destroy-init' });
db.event.add({ user: triggerUser._id, provision: provMongo._id, type: 'provision.destroy-init' });
return tf.destroy(destroyMongo, provMongo, job.data._scenario)
.then(async function(res) {
return tf.destroy(destroyMongo, provMongo).then(async function(res) {
let update, update2;
if ( res.output.indexOf("Error:") !== -1 ) {
update = await db.destroy.update(destroyMongo._id,{"status": "error"});
update2 = await db.provision.update(provMongo._id, {"isDestroyed": false});
} else {
update = await db.destroy.update(destroyMongo._id, {"status": "destroyed"});
let timeRunning = db.utils.getNewTimeRunning(provMongo);
update2 = await db.provision.update(provMongo._id, {"isDestroyed": true, "timeRunning": timeRunning, "pendingNextAction": undefined, "actualDestroyDate": new Date()});
update2 = await db.provision.update(provMongo._id, {"isDestroyed": true, "timeRunning": timeRunning, "pendingNextAction": null, "actualDestroyDate": new Date()});
sendEmail.sendDestroyedSuccess(update2, job.data._scenario);
guaca.deleteConnection(provMongo);
//console.log(`ProcessorDestroy# Deleting shares of this provision` );
//db.sharedProvision.delMany({"provision":update2._id});
}
return { destroy: update, provision: update2 };
}).then(async function(res) {
console.log(`ProcessorDestroy# Provision destroyed!` );
if ( res.provision.scenario === "azqmi-qdi" ) {
let tempApiKey = await db.apiKey.getOne({"description": res.provision._id});
if (tempApiKey && tempApiKey._id){
db.apiKey.del(tempApiKey._id);
if ( res.provision.isDestroyed ) {
console.log(`ProcessorDestroy# Provision (${res.provision._id}) destroyed!` );
db.event.add({ provision: provMongo._id, type: 'provision.destroy-finished' });
if ( res.provision.scenario === "azqmi-qdi" ) {
let tempApiKey = await db.apiKey.getOne({"description": res.provision._id});
if (tempApiKey && tempApiKey._id){
db.apiKey.del(tempApiKey._id);
}
}
} else {
console.log(`ProcessorDestroy# Provision (${res.provision._id}) NOT destroyed - Error!` );
db.event.add({ provision: provMongo._id, type: 'provision.destroy-error' });
}
db.event.add({ user: provMongo.user._id, provision: provMongo._id, type: 'provision.destroy-finished' });
return Promise.resolve({"success": true, job: res});
}).catch(function(err) {
console.log("ProcessorDestroy# err", err);
db.event.add({ user: provMongo.user._id, provision: provMongo._id, type: 'provision.destroy-error' });
db.event.add({ provision: provMongo._id, type: 'provision.destroy-error' });
db.destroy.update(destroyMongo._id, {"status": "error", "isDestroyed": false});
return Promise.reject({"success": false, "err": err});
});

View File

@@ -0,0 +1,67 @@
const db = require('qmi-cloud-common/mongo');
const synapse = require("./docker/synapse");
module.exports = async function(job) {
console.log(`ProcessorAzCLI# will execute ${job.data.tasktype} task`);
if (job.data.tasktype === 'pause') {
var prov = await db.provision.getById(job.data.provId);
if ( !prov ) {
console.log(`ProcessorAzCLI# Error: Not found Provision object in Database (it should exist!), provisionId is: ${job.data.id}` );
return Promise.reject({"success": false, "err": "Not found Provision object in Worker"});
}
// Synapse Pause
db.provision.update(prov._id, {"statusVms": "Stopping"});
return synapse.pause(prov)
.then( function(res) {
db.provision.update(prov._id, {"statusVms": "Stopped"});
return Promise.resolve( { "success": true, "output": res });
} ).catch( function(err) {
console.log("ProcessorAzCLI# Error:", err);
db.provision.update(prov._id, {"statusVms": "Running"});
return Promise.reject({"success": false, "error": err});
} );
} else if (job.data.tasktype === 'resume') {
var prov = await db.provision.getById(job.data.provId);
if ( !prov ) {
console.log(`ProcessorAzCLI# Error: Not found Provision object in Database (it should exist!), provisionId is: ${job.data.id}` );
return Promise.reject({"success": false, "err": "Not found Provision object in Worker"});
}
// Synapse Resume
db.provision.update(prov._id, {"statusVms": "Starting"});
return synapse.resume(prov)
.then( function(res) {
db.provision.update(prov._id, {"statusVms": "Running"});
return Promise.resolve( { "success": true, "output": res });
} ).catch( function(err) {
console.log("ProcessorAzCLI# Error:", err);
db.provision.update(prov._id, {"statusVms": "Stopped"});
return Promise.reject({"success": false, "error": err});
} );
} else if (job.data.tasktype === 'copy-snapshots') {
return synapse.copySnapshots(job.data)
.then( function(res) {
return Promise.resolve( { "success": true, "output": res });
} ).catch( function(err) {
console.log("ProcessorAzCLI# Error:", err);
return Promise.reject({"success": false, "error": err});
} );
} else if (job.data.tasktype === 'check-copy-snapshots') {
return synapse.checkCopySnapshots(job.data)
.then( function(res) {
return Promise.resolve( { "success": true, "output": res });
} ).catch( function(err) {
console.log("ProcessorAzCLI# Error:", err);
return Promise.reject({"success": false, "error": err});
} );
}
}

View File

@@ -18,6 +18,16 @@
"@azure/ms-rest-js" "^2.0.4"
tslib "^1.10.0"
"@azure/arm-dns@^4.0.0":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@azure/arm-dns/-/arm-dns-4.1.1.tgz#50ad92fd2b292da7753a7f39df2d3c5f3789ffb5"
integrity sha512-6AW0gy5v5hugLwtVM8F7Hx1yLmqbs2qmEoTwtbnG7X/6TYQtrWL4CwM7tQ+Ltu0PPYA/swDMH6vtOAUpmd3XFA==
dependencies:
"@azure/core-auth" "^1.1.4"
"@azure/ms-rest-azure-js" "^2.1.0"
"@azure/ms-rest-js" "^2.2.0"
tslib "^1.10.0"
"@azure/core-auth@^1.1.4":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.2.0.tgz#a5a181164e99f8446a3ccf9039345ddc9bb63bb9"
@@ -31,7 +41,7 @@
resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz#45809f89763a480924e21d3c620cd40866771625"
integrity sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==
"@azure/ms-rest-azure-js@^2.0.1":
"@azure/ms-rest-azure-js@^2.0.1", "@azure/ms-rest-azure-js@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.1.0.tgz#8c90b31468aeca3146b06c7144b386fd4827f64c"
integrity sha512-CjZjB8apvXl5h97Ck6SbeeCmU0sk56YPozPtTyGudPp1RGoHXNjFNtoOvwOG76EdpmMpxbK10DqcygI16Lu60Q==
@@ -103,6 +113,19 @@
dependencies:
"@types/node" "*"
"@types/webidl-conversions@*":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7"
integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==
"@types/whatwg-url@^8.2.1":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63"
integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==
dependencies:
"@types/node" "*"
"@types/webidl-conversions" "*"
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -224,6 +247,27 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
available-typed-arrays@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
aws-sdk@^2.942.0:
version "2.1374.0"
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1374.0.tgz#f915dd6a946cab810d1bd46ca14892e985ce69ab"
integrity sha512-Q7TExvBskRM23GfE1iy0GRxpUF+d9LY7ggmdHvL+2gX/QTzxSYaHHRPa8MNzFtzZLz+BDcDIgAcMjYKsrm18Rw==
dependencies:
buffer "4.9.2"
events "1.1.1"
ieee754 "1.1.13"
jmespath "0.16.0"
querystring "0.2.0"
sax "1.2.1"
url "0.10.3"
util "^0.12.4"
uuid "8.0.0"
xml2js "0.5.0"
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@@ -246,6 +290,12 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
"barracuda-api@https://gitlab.com/qlik_gear/barracuda-api-node.git#1.1.0":
version "0.0.10"
resolved "https://gitlab.com/qlik_gear/barracuda-api-node.git#ce8be42102398a45bf896cf973290b46d2e18666"
dependencies:
axios "^0.21.1"
base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
@@ -283,14 +333,6 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
bl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493"
integrity sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==
dependencies:
readable-stream "^2.3.5"
safe-buffer "^5.1.1"
bl@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a"
@@ -300,11 +342,6 @@ bl@^4.0.1:
inherits "^2.0.4"
readable-stream "^3.4.0"
bluebird@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
boxen@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
@@ -342,10 +379,10 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
bson@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.4.tgz#f76870d799f15b854dffb7ee32f0a874797f7e89"
integrity sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==
bson@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/bson/-/bson-5.2.0.tgz#c81d35dd30e2798203e5422a639780ea98dd25ba"
integrity sha512-HevkSpDbpUfsrHWmWiAsNavANKYIErV2ePXllp1bwq5CDreAaFVj6RVlZpJnxK4WWDCJ/5jMUpaY6G526q3Hjg==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
@@ -357,6 +394,15 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffer@4.9.2:
version "4.9.2"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.5.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
@@ -396,6 +442,14 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
dependencies:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
camelcase@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -579,12 +633,12 @@ date-utils@*:
resolved "https://registry.yarnpkg.com/date-utils/-/date-utils-1.2.21.tgz#61fb16cdc1274b3c9acaaffe9fc69df8720a2b64"
integrity sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q=
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
debug@4.x:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.0.0"
ms "2.1.2"
debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
@@ -656,7 +710,7 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
denque@^1.1.0, denque@^1.4.1:
denque@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
@@ -755,6 +809,11 @@ event-target-shim@^5.0.0:
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
events@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==
execa@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
@@ -855,6 +914,13 @@ follow-redirects@^1.10.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
dependencies:
is-callable "^1.1.3"
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -917,6 +983,15 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f"
integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.3"
get-port@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
@@ -954,6 +1029,13 @@ global-dirs@^0.1.0:
dependencies:
ini "^1.3.4"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
got@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@@ -999,6 +1081,18 @@ has-symbols@^1.0.0, has-symbols@^1.0.1:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
dependencies:
has-symbols "^1.0.2"
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -1046,7 +1140,7 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
ieee754@^1.1.4:
ieee754@1.1.13, ieee754@^1.1.4:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
@@ -1096,6 +1190,11 @@ ip-regex@^2.1.0:
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
ip@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da"
integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==
is-accessor-descriptor@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
@@ -1110,6 +1209,14 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-arguments@^1.0.4:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
dependencies:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-binary-path@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
@@ -1122,6 +1229,11 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-callable@^1.1.3:
version "1.2.7"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-callable@^1.1.4, is-callable@^1.1.5:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
@@ -1193,6 +1305,13 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-generator-function@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
dependencies:
has-tostringtag "^1.0.0"
is-glob@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
@@ -1282,6 +1401,17 @@ is-symbol@^1.0.2:
dependencies:
has-symbols "^1.0.1"
is-typed-array@^1.1.10, is-typed-array@^1.1.3:
version "1.1.10"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -1292,10 +1422,10 @@ is-windows@^1.0.2:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
isarray@1.0.0, isarray@~1.0.0:
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
isexe@^2.0.0:
version "2.0.0"
@@ -1319,6 +1449,11 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jmespath@0.16.0:
version "0.16.0"
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076"
integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@@ -1366,10 +1501,10 @@ jws@3.x.x:
jwa "^1.4.1"
safe-buffer "^5.0.1"
kareem@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87"
integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==
kareem@2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.5.1.tgz#7b8203e11819a8e77a34b3517d3ead206764d15d"
integrity sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
@@ -1522,56 +1657,49 @@ moment-timezone@^0.5.31:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
mongodb@3.5.8:
version "3.5.8"
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.5.8.tgz#34550856449b745d145873734bf922c12d6b9caa"
integrity sha512-jz7mR58z66JKL8Px4ZY+FXbgB7d0a0hEGCT7kw8iye46/gsqPrOEpZOswwJ2BQlfzsrCLKdsF9UcaUfGVN2HrQ==
mongodb-connection-string-url@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf"
integrity sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==
dependencies:
bl "^2.2.0"
bson "^1.1.4"
denque "^1.4.1"
require_optional "^1.0.1"
safe-buffer "^5.1.2"
"@types/whatwg-url" "^8.2.1"
whatwg-url "^11.0.0"
mongodb@5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-5.3.0.tgz#9bef3ff35511a66fb7d9aafb7b06112787138db1"
integrity sha512-Wy/sbahguL8c3TXQWXmuBabiLD+iVmz+tOgQf+FwkCjhUIorqbAxRbbz00g4ZoN4sXIPwpAlTANMaGRjGGTikQ==
dependencies:
bson "^5.2.0"
mongodb-connection-string-url "^2.6.0"
socks "^2.7.1"
optionalDependencies:
saslprep "^1.0.0"
saslprep "^1.0.3"
mongoose-legacy-pluralize@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4"
integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==
mongoose@^5.7.4:
version "5.9.17"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.9.17.tgz#9b74659481807cd9ff5b9c120cdb5087cbbd92bd"
integrity sha512-9EDmTiKrOu/41twlPWUA1aOsdxSN6PRIdFwTpLu4MjyNcJ/vuBE+VewKrN1jsD4oXO5rB8bMYtYxVmJQ02SrPg==
mongoose@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-7.1.0.tgz#ad7f23184a0c19eb8efbbad4f1ff2113ede1515d"
integrity sha512-shoo9z/7o96Ojx69wpJn65+EC+Mt3q1SWTducW+F2Y4ieCXo0lZwpCZedgC841MIvJ7V8o6gmzoN1NfcnOTOuw==
dependencies:
bson "^1.1.4"
kareem "2.3.1"
mongodb "3.5.8"
mongoose-legacy-pluralize "1.0.2"
mpath "0.7.0"
mquery "3.2.2"
ms "2.1.2"
regexp-clone "1.0.0"
safe-buffer "5.1.2"
sift "7.0.1"
sliced "1.0.1"
bson "^5.2.0"
kareem "2.5.1"
mongodb "5.3.0"
mpath "0.9.0"
mquery "5.0.0"
ms "2.1.3"
sift "16.0.1"
mpath@0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.7.0.tgz#20e8102e276b71709d6e07e9f8d4d0f641afbfb8"
integrity sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==
mpath@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904"
integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==
mquery@3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.2.tgz#e1383a3951852ce23e37f619a9b350f1fb3664e7"
integrity sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==
mquery@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/mquery/-/mquery-5.0.0.tgz#a95be5dfc610b23862df34a47d3e5d60e110695d"
integrity sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==
dependencies:
bluebird "3.5.1"
debug "3.1.0"
regexp-clone "^1.0.0"
safe-buffer "5.1.2"
sliced "1.0.1"
debug "4.x"
ms@2.0.0:
version "2.0.0"
@@ -1583,6 +1711,11 @@ ms@2.1.2, ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nan@^2.12.1:
version "2.14.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
@@ -1824,27 +1957,41 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qmi-cloud-common@../qmi-cloud-common:
version "1.1.6"
version "2.0.0"
dependencies:
"@azure/arm-compute" "^15.0.0"
"@azure/arm-dns" "^4.0.0"
"@azure/ms-rest-nodeauth" "^3.0.7"
"@hapi/boom" "^9.1.0"
aws-sdk "^2.942.0"
axios "^0.21.1"
barracuda-api "https://gitlab.com/qlik_gear/barracuda-api-node.git#1.1.0"
bull "^3.11.0"
mongoose "^5.7.4"
mongoose "^7.1.0"
nodemailer "^6.4.2"
uuid "^8.3.2"
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
querystring@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
rc@^1.0.1, rc@^1.1.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -1855,7 +2002,7 @@ rc@^1.0.1, rc@^1.1.6:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
readable-stream@^2.0.2, readable-stream@^2.3.5:
readable-stream@^2.0.2:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -1911,11 +2058,6 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
regexp-clone@1.0.0, regexp-clone@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==
registry-auth-token@^3.0.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
@@ -1972,19 +2114,6 @@ repeat-string@^1.6.1:
tunnel-agent "^0.6.0"
uuid "^3.3.2"
require_optional@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e"
integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==
dependencies:
resolve-from "^2.0.0"
semver "^5.1.0"
resolve-from@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -1995,16 +2124,16 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -2017,13 +2146,18 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
saslprep@^1.0.0:
saslprep@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226"
integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==
dependencies:
sparse-bitfield "^3.0.3"
sax@1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -2068,20 +2202,20 @@ shebang-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
sift@7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08"
integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==
sift@16.0.1:
version "16.0.1"
resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.1.tgz#e9c2ccc72191585008cf3e36fc447b2d2633a053"
integrity sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
sliced@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
snapdragon-node@^2.0.1:
version "2.1.1"
@@ -2113,6 +2247,14 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
socks@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55"
integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==
dependencies:
ip "^2.0.0"
smart-buffer "^4.2.0"
source-map-resolve@^0.5.0:
version "0.5.3"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
@@ -2364,6 +2506,13 @@ tough-cookie@~2.5.0:
psl "^1.1.28"
punycode "^2.1.1"
tr46@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
dependencies:
punycode "^2.1.1"
tslib@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@@ -2478,6 +2627,14 @@ url-parse-lax@^1.0.0:
dependencies:
prepend-http "^1.0.1"
url@0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
integrity sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==
dependencies:
punycode "1.3.2"
querystring "0.2.0"
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@@ -2498,11 +2655,32 @@ util.promisify@^1.0.1:
has-symbols "^1.0.1"
object.getownpropertydescriptors "^2.1.0"
util@^0.12.4:
version "0.12.5"
resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
dependencies:
inherits "^2.0.3"
is-arguments "^1.0.4"
is-generator-function "^1.0.7"
is-typed-array "^1.1.3"
which-typed-array "^1.1.2"
uuid@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c"
integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
uuid@^3.1.0, uuid@^3.3.2, uuid@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
verror@1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
@@ -2512,6 +2690,31 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
webidl-conversions@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
whatwg-url@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
dependencies:
tr46 "^3.0.0"
webidl-conversions "^7.0.0"
which-typed-array@^1.1.2:
version "1.1.9"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"
is-typed-array "^1.1.10"
which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@@ -2545,6 +2748,14 @@ xdg-basedir@^3.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
xml2js@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7"
integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xml2js@^0.4.19:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCzYn0nGRdheWZf
X9Gvz5Fc0RH0oeJe1HfC0E7HrDFlogkmErJyvr6vZMwjx7jxSYDbYgNJtUXmalKZ
a3fgF4nRmlv+aPG1pNjeCThoTHTof7hMS70iAIyrbubU6v+SKvbmcWM/RkEkEXXa
7TCOi/ZbN7S+dFQAA9ObNF6dsYHLWBQO4O8ZoydTcxTc1enc+v1Q6/Bqm7//fBY+
VV8tIi4hiIgLTEGnsBCnH34a3Ie8i6JDXEMmD+NiZjeYLrr5YToniDu4drkrpzIp
2P9X2wbdwI25WC6RcWLwSIkjiRY8Z/jDgowjieCDNXHVCYv47+8peeOx/Z//YuKy
Hul2ob25KjK51k6t55NKNj3kTrzXyRIgGOdI6Q+IBl85lfhIiXPRSCobLoMVbZgG
5qTZpEHyp2RN5CPLGlshslrQigunUzuA6sPGR+qnT8ZwGndiV8n5oaWx7fmIoK1k
SYBe5zKehYHh4hprThvzvqqPfJwzAFAOCSJJsch7SuZGlz/2xSx0P1e27vRJ9at/
zSKL3FIFqHpk3I0FKf/FjNZu29RPmyLIyX8uaB/Iud6cg45THaUs+GXeiLY3mt/u
3BRADZF9AXzJZR02Ubtre1gE5PSu0NOnkudoL+9fnCK5PGFlifdtp68R8UiKAnXj
OjMljbq+yqn5tf6DB0y82rEk0qXCoQIDAQABAoICAAhFqkF4zz1f+fVmTPGfZUko
nPwxJj1ll9uPJpWKSRbc44YdcPGIqUYbpFb2uVPAe3riaLC9SsARjBs7ZkzvrkZ7
qJ1b8orZU2Td0PuXoavXuUR6GPqDQvlkL6yG5QqKWlYNyYaxSIS6HLtf77rLFS0S
Aw7l/LpUHYMikEW+WfQUwjazbw3kGlv8n8F/8ye3wqG4156FmH4sFzcq/FdvpD1I
ogQUsVG4dUl3qCWN9jZ0IU3w6GnOFsLCtZ1EyRDXR8rAkLHKIRzfD274QkInaAgm
09ebC5QKbLEURJAJHNI5SzKc3QswgFQcoqdb0tgZHGgcZlXJZ9eWPvT5fEj2Xsd3
i0Q46D6y7r+dAMhhE9hdZA/Ke1fkcBgANLRRdoHHFUey1en7uQsliGfNzm1i/OR4
AW9XdyNiB5F2LqYfnrRXEsG7FHAnCVkN1k5ur8GsBZpfYtzWof9yKXCaVUQODKqW
0gUndI38omx8liZoFruP5VteRJgVYEIkcyAAfeG3OlIaywGmwdTGyViXhWZcihse
3pJMsUI7P72SkSjGKAjVxCf08bwCIT6ls8V79epiUzhMnORAZC4hZxayZrBphS8U
sVzrMGQeVexlXso3EoqjWCaM7XkbvE/8ZOBbFxyJWAkIMuD4vUuW19Fk8IOTzAL4
VKlcq+vfZxWbA7Mq6w7vAoIBAQDeJF82Lv/IUXL3JkgYmSnze7rGg+rwsuUxBXd5
CNPjrlwbCEIVEreQHUPOzih2Rvhrd2iCVpyCO8Kdo2AE5B7wnPePAitnEwZ6hFcD
xMubuiRqlaILfDi8Ph7juL0mmSaz5D+HkzUdCUfvBUFXxvLyeDsNFjgqBGOahL0b
1E1FDct/6Mltc+ATlavWxQxn+UEs8/3ssy+f4gmeLgwgevt+A8cB3ppwSFCOnu4I
iX/vdti5MHjdUBjJGV/qjTOsUWioRXY1ay87H5RiQju3GpTQ8zlkyRv9MMll0lqi
OQwtVvVpRd6O4qHFmgJOfPt+Z81b6cPrA0StbwAG/t5qYH7/AoIBAQDOucsdyj5k
XmlcDo51VyEr95FsJ/SZOPOBh4MpRm4IYUgLsQTacUueTCiY17TUR6CUc+si76gb
YYAcF0KV/17lvPosoWkdZNsqq1L8nrVq3+z2WsTkCs8m10FZ0aajf3N8WTT0v6VX
DsOrZLsD2xxIU4f3Lf4FnL6PI5BDT83Up5paHSIhE+omAp+x7Bzf0KSpsDu2uZTH
r8yJf1ghet+Z923/yUUTHDM8Vpt8hhoEjXWJd3r96OSIoFUwHzLgNFpiPgHkGJUU
xRZV4Au+GeHuz9nLWPRaYVG5CxX/IbPyMzGJHchVFywIySqYK2l+VvVM17M7AuCq
V4SZ3y7Ky15fAoIBAHahp/MwwEqDLMlOOVxhl2S/Y+yWEIbAkuNODxKlIztJJ0kM
bPYCC+O7rTWpJTSdDBegKkDI7kYikflLgYC7LsbCnPZTa0hdga02NZ3+n9mnW8FL
7cECcu4corRsOR9+1ItnToIhnFDIXxEHlnDA/4d7q9V+UzolI+gmETPmeelxx4ak
k8WPB1COMrm8e7afBy5xkt6whrN0rDw8TR+fbeVLMSEPdxyVkefIekg23grNRkoH
19Qg7Uuf8Hg7NihFRYXvqoQ2nH+Pite6lVdgq6625aSsPfVF85gb8WkG3DjuYpr4
xDU8VLZJXAf8ePZ1itcWDRnZofiY+cPCopbet5MCggEAGb7l3wXrE1D2yjI957s8
NF+WyuOHAPYozX71BNTyqzSCZoJbWmE1y7csbyyeJrns89Aj/qveQdq4u8bh0hCF
3xLUDW7kynZfHUdNBI03huHwfxX643O9LNcuGmOT31TmKxxpDfo4O0lpcRUQfYBy
W0eb7VrbAhPtX6JMOzXbKprdDFAIihoS1T0Kanw/dFhlyYRbS3x9XQk17gHgFftZ
kbFRD8QfSCwA7YjTwIRrBRohA0fQF4NDwwhE08Nu8KFUiFu0nJW7K2UITRWkIL7U
douIUlz3wbHRHbyVtrqZ0JYzmyIMaxyBrW5wUZdGgieOUU2j0rufA1f2+brj9vmw
/QKCAQBDuCwPOBuZ6qFu1zBAfDyElTsIve1Uh32q+jbL5w8kkBhsiGaxFcmC3gG6
WkdtKZKSRaf23wl4LgjvAYD5lPX+p3127pHxG+haWFkXweDMw4WrvGamm/X0H8JD
8CX9PNWi2yxVDjNXiJ5tSbsFnhBf2CijHinrZexxlVTId6yuptOWDaq4XeYnMmKt
FRgpnwnKAkxZKmoKIXOeSQOUVJzzxufkX3QG3CJqu60DGEDsLxM00HoP/Vxa81un
XT+DxB6kgSYLC0a7KsHsXm8CbCoikqVEtANzIvahbsKDLNah/OWmOo9O1UphhYN+
HQe3R2QQtfmWZ320cdhukaok4yj3
-----END PRIVATE KEY-----

View File

@@ -2,16 +2,14 @@
const secrets = require("/run/secrets/config.json");
const HOSTNAME_URL = process.env.HOSTNAME_URL || "http://localhost:3000"
const TENANT_ID = secrets.AZURE_TENANT_ID;
const CLIENT_ID = secrets.AZURE_CLIENT_ID;
const CLIENT_SECRET = secrets.AZURE_CLIENT_SECRET;
const CLIENT_ID = secrets.CLIENT_ID;
const CLIENT_SECRET = secrets.CLIENT_SECRET;
const LOGOUT_URL = HOSTNAME_URL;
const REDIRECT_URL = `${HOSTNAME_URL}/auth/openid/return`;
exports.creds = {
// Required
identityMetadata: `https://login.microsoftonline.com/${TENANT_ID}/.well-known/openid-configuration`,
// or equivalently: 'https://login.microsoftonline.com/${TENANT_ID}/.well-known/openid-configuration'
//identityMetadata: IDENTITY_METADATA,
//
// or you can use the common endpoint
// 'https://login.microsoftonline.com/common/.well-known/openid-configuration'
@@ -85,12 +83,13 @@ exports.creds = {
exports.resourceURL = 'https://graph.microsoft.com';
// The url you need to go to destroy the session with AAD
exports.destroySessionUrl = `https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=${LOGOUT_URL}`;
//exports.destroySessionUrl = `https://login.microsoftonline.com/common/oauth2/logout?post_logout_redirect_uri=${LOGOUT_URL}`;
//exports.destroySessionUrl = `https://qlik.okta.com/logout?id_token_hint=${id_token}&post_logout_redirect_uri=${LOGOUT_URL}&state=${state}`
exports.destroySessionUrl = `https://qlik.okta.com/logout?post_logout_redirect_uri=${LOGOUT_URL}`
// If you want to use the mongoDB session store for session middleware, set to true; otherwise we will use the default
// session store provided by express-session.
// Note that the default session store is designed for development purpose only.
exports.useMongoDBSessionStore = true;
exports.useMongoDBSessionStore = false;
// If you want to use mongoDB, provide the uri here for the database.
exports.databaseUri = process.env.MONGO_URI;

View File

@@ -10,6 +10,14 @@ const axios = require('axios');
const path = require('path');
const fs = require('fs');
const sessionStore = new MongoStore({
mongooseConnection: mongoose.connection,
url: process.env.MONGO_URI,
autoRemove: 'interval',
autoRemoveInterval: 10
//clear_interval: config.mongoDBSessionMaxAge
});
// Start QuickStart here
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
@@ -80,22 +88,29 @@ passport.use(new OIDCStrategy({
cookieEncryptionKeys: config.creds.cookieEncryptionKeys,
clockSkew: config.creds.clockSkew,
},
function(iss, sub, profile, jwtClaims, accessToken, refreshToken, params, done) {
async function(iss, sub, profile, jwtClaims, accessToken, refreshToken, params, done) {
if ( !profile.oid ) {
return done(new Error("No oid found"), null);
}
let email = profile.upn;
let isPermitterEmail = (email.toLowerCase().includes("@qlik") || email.toLowerCase().includes("@talend"));
if ( !email || !isPermitterEmail) {
return done(new Error("Not a valid Qlik email"), null);
}
//console.log("accessToken", accessToken);
//console.log("iss", iss);
//console.log("sub", sub);
//console.log("refreshToken", refreshToken);
//console.log("jwtClaims", jwtClaims);
//console.log("params", params);
console.log(`Passport# new login from: ${profile.upn} (${profile.displayName})` );
console.log(`Passport# new login from: ${profile.upn} (${profile.displayName}) - sub: ${sub}` );
//Save user photo
axios({
method: 'get',
method: 'GET',
url: 'https://graph.microsoft.com/v1.0/me/photo/$value',
responseType: 'stream',
headers: { 'Authorization' : 'Bearer '+accessToken }
@@ -104,26 +119,54 @@ passport.use(new OIDCStrategy({
}).catch(function(err){
console.log('Passport# Warning: No picture found');
});
let mail, jobTitle;
try {
let fullProfile = await axios({
method: 'GET',
url: `https://graph.microsoft.com/v1.0/users/${profile.oid}`,
responseType: 'json',
headers: { 'Authorization' : 'Bearer '+accessToken }
});
if ( fullProfile.data ) {
mail = fullProfile.data.mail;
jobTitle = fullProfile.data.jobTitle;
}
} catch (e) {
console.log('Passport# Warning: Could not get user entire profile');
}
// asynchronous verification, for effect...
process.nextTick(function () {
_findByOid(profile.oid, async function(err, user) {
if (err) {
return done(err);
}
if (!user) {
// "Auto-registration"
user = await db.user.add({
"oid": profile.oid,
"upn": profile.upn,
"displayName": profile.displayName,
"lastLogin": new Date()
_findByOid(profile.oid, async function(err, user) {
if (err) {
return done(err);
}
if (!user) {
// "Auto-registration"
user = await db.user.add({
"oid": profile.oid,
"upn": profile.upn,
"displayName": profile.displayName,
"lastLogin": new Date(),
"sub": sub,
"active": true,
"mail": mail,
"jobTitle": jobTitle
});
return done(null, user);
}
db.user.update(user._id, {
"lastLogin": new Date(),
"sub": sub,
"active": true,
"mail": mail,
"jobTitle": jobTitle
});
return done(null, user);
}
db.user.update(user._id, {"lastLogin": new Date()});
return done(null, user);
});
});
});
}
));
@@ -136,17 +179,16 @@ module.exports.init = function(app){
app.use(expressSession({
secret: 'secret',
cookie: {maxAge: config.mongoDBSessionMaxAge * 1000},
store: new MongoStore({
mongooseConnection: mongoose.connection,
autoRemove: 'interval',
autoRemoveInterval: 10
//clear_interval: config.mongoDBSessionMaxAge
}),
store: sessionStore,
resave: true,
saveUninitialized: false
}));
} else {
app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false }));
app.use(expressSession({
secret: 'keyboard cat',
resave: true,
saveUninitialized: false
}));
}
@@ -266,4 +308,6 @@ module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
}
}
return res.status(401).send("Error: Unauthorized");
};
};
module.exports.sessionStore = sessionStore;

268
server/passport-okta.js Normal file
View File

@@ -0,0 +1,268 @@
const passport = require('passport');
const expressSession = require('express-session');
const config = require('./config');
// set up database for express session
const MongoStore = require('connect-mongo')(expressSession);
const mongoose = require('mongoose');
const db = require("qmi-cloud-common/mongo");
const sessionStore = config.useMongoDBSessionStore? new MongoStore({
mongooseConnection: mongoose.connection,
url: process.env.MONGO_URI,
autoRemove: 'interval',
autoRemoveInterval: 10
//clear_interval: config.mongoDBSessionMaxAge
}) : new expressSession.MemoryStore();
var OpenIDConnectStrategy = require('passport-openidconnect');
const OKTA_DOMAIN = "qlik.okta.com";
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
_findByUpn(user.upn, function (err, user) {
done(err, user);
});
});
var _findByUpn = async function(upn, fn) {
var mongouser = await db.user.getOne({"upn": { $regex: new RegExp(upn, 'i') } });
if (mongouser){
return fn(null, mongouser);
} else {
return fn(null, null);
}
};
// set up passport
passport.use('oidc', new OpenIDConnectStrategy({
issuer: "https://qlik.okta.com",
authorizationURL: `https://${OKTA_DOMAIN}/oauth2/v1/authorize`,
tokenURL: `https://${OKTA_DOMAIN}/oauth2/v1/token`,
clientID: config.creds.clientID,
clientSecret: config.creds.clientSecret,
callbackURL: config.creds.redirectUrl,
scope: config.creds.scope,
passReqToCallback: true
}, async (req, issuer, profile, context, idToken, accessToken, refreshToken, done) => {
//console.log("OKTA ISSUER ", issuer)
//console.log("OKTA PROFILE ", profile)
//console.log("OKTA context ", context)
//console.log("OKTA idToken ", idToken)
//console.log("OKTA accessToken ", accessToken)
//console.log("OKTA refreshToken ", refreshToken)
req.session.oktaAccessToken = accessToken;
if ( !profile.id ) {
return done(new Error("No profile id found"), null);
}
console.log(`Passport# new login from: ${profile.displayName} - sub: ${profile.id}` );
// asynchronous verification, for effect...
process.nextTick(function () {
_findByUpn(profile.username, async function(err, user) {
if (err) {
return done(err);
}
if (!user) {
// "Auto-registration"
user = await db.user.add({
//"oid": profile.id,
"upn": profile.username.toLowerCase(),
"displayName": profile.displayName,
"lastLogin": new Date(),
"sub": profile.id,
"active": true,
"mail": profile.emails[0].value,
//"jobTitle": jobTitle
});
return done(null, user);
}
db.user.update(user._id, {
"upn": profile.username.toLowerCase(),
"lastLogin": new Date(),
"sub": profile.id,
"active": true,
"mail": profile.emails[0].value,
//"jobTitle": jobTitle
});
return done(null, user);
});
});
//return done(null, profile);
}));
module.exports.init = function(app, isSecure){
const cookieName = 'qmiconnect.sid';
var cookieConf = {
maxAge: config.mongoDBSessionMaxAge * 1000,
httpOnly: false,
secure: isSecure,
sameSite: isSecure? 'none' : undefined
};
console.log("--- SESSION", "Cookie Conf = ", cookieConf , "Cookie Name = " + cookieName);
// set up session middleware
if (config.useMongoDBSessionStore) {
app.use(expressSession({
name: 'qmiconnect.sid',
secret: 'secret',
cookie: cookieConf,
store: sessionStore,
resave: true,
saveUninitialized: false
}));
} else {
app.use(expressSession({
name: 'qmiconnect.sid',
secret: 'keyboard cat',
cookie: cookieConf,
store: sessionStore,
resave: true,
saveUninitialized: false
}));
}
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.get('/sessioninfo', function(req, res){
if (req.session.oktaAccessToken && req.user ) {
res.json({
"oktaAccessToken": req.session.oktaAccessToken,
"user": req.user
});
} else {
res.status(401).json({});
}
});
app.get('/login',
function(req, res, next) {
passport.authenticate('oidc',
{
response: res, // required
resourceURL: config.resourceURL, // optional. Provide a value if you want to specify the resource.
//customState: 'my_state', // optional. Provide a value if you want to provide custom state value.
failureRedirect: '/error',
session: true,
//assignProperty: "weee",
authInfo: true
}
)(req, res, next);
},
function(req, res) {
res.redirect('/home');
}
);
app.get('/auth/openid/return',
function(req, res, next) {
passport.authenticate('oidc', {
response: res,
failureRedirect: '/error' ,
session: true,
//assignProperty: "weee",
authInfo: true
}
)(req, res, next);
},
function(req, res) {
console.log('Passport# We received a return from OKTA ');
res.redirect('/provisions');
}
);
/*app.use('/auth/openid/return', passport.authenticate('oidc', { failureRedirect: '/error' }), (req, res) => {
console.log('Passport# We received a return from OKTA ');
res.redirect('/provisions');
});*/
app.get('/logout', function(req, res){
req.session.destroy(function(err) {
req.logOut();
res.redirect("/home");
});
});
};
async function isApiKeyAuthenticated(req) {
//console.log("oktaToken", req.headers.oktatoken);
//console.log("okta session token", req.session.oktaAccessToken);
if (req.headers && req.headers.oktatoken !== undefined && req.session && req.headers.oktatoken === req.session.oktaAccessToken) {
console.log("OKTA TOKEN PROVIDED and is GOOD!!!!");
return true;
}
let key = req.query.apiKey || req.get('QMI-ApiKey');
if ( key ) {
var result = await db.apiKey.getOne({"apiKey": key});
if ( result ) {
req.user = result.user;
return true;
} else {
return false;
}
} else {
return false;
}
}
module.exports.ensureAuthenticatedDoLogin = async function(req, res, next) {
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
return next();
}
res.redirect('/login');
};
module.exports.ensureAuthenticated = async function(req, res, next) {
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
return next();
}
res.status(401).send({"error": "Unauthorized"});
};
module.exports.ensureAuthenticatedAndAdmin = async function(req, res, next) {
if ( ( await isApiKeyAuthenticated(req) || req.isAuthenticated()) && (req.user.role === 'admin' || req.user.role === 'superadmin') ) {
return next();
}
res.status(401).send({"error": "Unauthorized"});
};
module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
var userId = (req.params.userId === 'me')? req.user._id : req.params.userId;
if ( req.user._id == userId || req.user.role === 'admin' || req.user.role === 'superadmin' ) {
return next();
} else {
return res.status(401).send("Error: Unauthorized");
}
}
return res.status(401).send("Error: Unauthorized");
};
module.exports.sessionStore = sessionStore;

View File

@@ -1,7 +1,7 @@
const express = require('express');
const router = express.Router();
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport');
const passport = require('../passport-okta');
/**

View File

@@ -1,7 +1,7 @@
const express = require('express')
const router = express.Router()
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport');
const passport = require('../passport-okta');
/**

View File

@@ -1,7 +1,7 @@
const express = require('express')
const router = express.Router()
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport');
const passport = require('../passport-okta');
/**

View File

@@ -1,7 +1,10 @@
const express = require('express');
const router = express.Router();
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport');
const passport = require('../passport-okta');
import { queues, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
/**
* @swagger
@@ -42,6 +45,11 @@ router.post('/updates', passport.ensureAuthenticatedAndAdmin, async (req, res, n
if ( provision ) {
var type = "vms";
if ( logEvent.indexOf("RDS")!== -1 ){
type = "db";
}
let id = provision._id.toString();
console.log(`${logEvent} (${now})# provision (${id}) - scenario is (${provision.scenario} - v${provision.scenarioVersion})`);
@@ -50,8 +58,23 @@ router.post('/updates', passport.ensureAuthenticatedAndAdmin, async (req, res, n
if ( event.instanceState === 'Stopped' ) {
//Check active children provisions
let children = await db.provision.get({ "parent": provision._id, "isDestroyed": false, "isDeleted": false });
if (children.results.length > 0 ) {
children.results.forEach(function(child) {
if ( child.scenario === 'azqmi-synapse') {
queues[SYNAPSE_QUEUE].add("synapse_job", {
provId: child._id,
user: req.user,
tasktype: 'pause'
});
}
});
}
if ( provision.statusVms === 'Stopped' ) {
console.log(`${logEvent} (${now})# provision (${id}) - VMs were already Stopped!`);
console.log(`${logEvent} (${now})# provision (${id}) - were already Stopped!`);
} else {
let timeRunning = db.utils.getNewTimeRunning(provision);
@@ -59,39 +82,32 @@ router.post('/updates', passport.ensureAuthenticatedAndAdmin, async (req, res, n
"statusVms": "Stopped",
"timeRunning": timeRunning,
"stoppedFrom": dateNow,
"pendingNextAction": undefined
"pendingNextAction": null
};
let msg = "";
if ( provision.schedule && !provision.schedule.is24x7 ) {
patch["endDateOnSchedule"] = dateNow;
//This is temporary, only to make sure there is value
if ( !provision["startDateOnSchedule"] ) {
patch["startDateOnSchedule"] = dateNow;
msg = "startDateOS: " + dateNow.toISOString();
} else {
msg = "startDateOS: " + new Date(provision.startDateOnSchedule).toISOString();
}
msg += (" - endDateOS: " + dateNow.toISOString());
}
msg += ` - New totalTimeRunning: ${timeRunning} mins`;
await db.provision.update(id, patch);
console.log(`${logEvent} (${now})# provision (${id}) - VMs changed to Stopped!`);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.stop-schedule', message: msg });
console.log(`${logEvent} (${now})# provision (${id}) - changed to Stopped!`);
db.event.add({ provision: provision._id, type: `${type}.stop`, message: `[Divvy] TotalTimeRunning: ${timeRunning} mins` });
}
} else if ( event.instanceState === 'Running' ) {
if ( provision.statusVms === 'Running' ) {
console.log(`${logEvent} (${now})# provision (${id}) - VMs were already Running!`);
console.log(`${logEvent} (${now})# provision (${id}) - were already Running!`);
} else {
let patch = {
"statusVms": "Running",
"runningFrom": dateNow,
"pendingNextAction": undefined
"pendingNextAction": null
};
// This is temporary, only to make sure there are values soon
@@ -103,9 +119,9 @@ router.post('/updates', passport.ensureAuthenticatedAndAdmin, async (req, res, n
}
await db.provision.update(id, patch);
console.log(`${logEvent} (${now})# provision (${id}) - VMs changed to Running!`);
console.log(`${logEvent} (${now})# provision (${id}) - changed to Running!`);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.start-schedule', message: `TimeRunning so far: ${provision.timeRunning} mins` });
db.event.add({ provision: provision._id, type: `${type}.start`, message: `[Divvy] TotalTimeRunning: ${provision.timeRunning} mins` });
}
}

View File

@@ -1,7 +1,7 @@
const express = require('express');
const router = express.Router();
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport');
const passport = require('../passport-okta');
const sendEmail = require('qmi-cloud-common/send-email');
@@ -15,6 +15,15 @@ const sendEmail = require('qmi-cloud-common/send-email');
* - admin
* produces:
* - application/json
* parameters:
* - name: page
* in: query
* required: false
* type: integer
* - name: size
* in: query
* required: false
* type: integer
* responses:
* 200:
* description: Notifications
@@ -39,8 +48,8 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
* @swagger
* /notifications/testsendemail:
* post:
* description: Get all notifications
* summary: Get all notifications
* description: Test email service
* summary: Test email service
* tags:
* - admin
* produces:

View File

@@ -1,17 +1,19 @@
const express = require('express')
const router = express.Router()
const db = require('qmi-cloud-common/mongo');
const azurecli = require('qmi-cloud-common/azurecli');
const passport = require('../passport');
const passport = require('../passport-okta');
const fs = require('fs-extra');
import { queues, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
/**
* @swagger
* /provisions:
* get:
* description: Get all Provisions
* summary: Get all Provisions)
* summary: Get all Provisions
* tags:
* - admin
* produces:
@@ -63,7 +65,8 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
const result = await db.provision.getPage(filter, page, req.query.populates, req.query.select);
if (result.next){
result.nextUrl = new URL(req.protocol + '://' + req.get('Host') + req.baseUrl);
result.nextUrl = new URL('https://' + req.hostname + req.baseUrl);
if ( req.query.filter ) {
result.nextUrl.searchParams.append("filter", req.query.filter);
}
@@ -85,10 +88,121 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
/**
* @swagger
* /provisions/vmImagesLogs:
* /provisions/copysnapshots:
* post:
* description: Copy snapshots into regions
* summary: Copy snapshots into regions
* tags:
* - admin
* produces:
* - application/json
* parameters:
* - name: snap
* in: query
* required: true
* type: string
* - name: regions
* in: query
* required: true
* type: string
* - name: rg
* in: query
* required: true
* type: string
* responses:
* 200:
* description: OK
* 404:
* description: Not found
*
*/
router.post('/copysnapshots', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
queues[SYNAPSE_QUEUE].add("synapse_job", {
snapName: req.query.snap,
rGroup: req.query.rg,
regions: req.query.regions,
tasktype: "copy-snapshots"
});
return res.json({"msg": "Snapshots are being craeted", "success": true});
} catch (error) {
next(error);
}
});
/**
* @swagger
* /provisions/copysnapshots:
* get:
* description: Get all vmImages
* summary: Get all vmImages
* description: Get Status of copy snapshots into regions
* summary: Get Status of copy snapshots into regions
* tags:
* - admin
* produces:
* - application/json
* parameters:
* - name: snap
* in: query
* required: true
* type: string
* responses:
* 200:
* description: OK
* 404:
* description: Not found
*
*/
router.get('/copysnapshots', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
queues[SYNAPSE_QUEUE].add("synapse_job", {
snapName: req.query.snap,
tasktype: "check-copy-snapshots"
});
return res.json({"msg": "Snapshots are being checked", "success": true});
} catch (error) {
next(error);
}
});
/**
* @swagger
* /provisions/snapshotslogs:
* get:
* description: Get logs for snapshots
* summary: Get logs for snapshots
* tags:
* - admin
* produces:
* - application/json
* responses:
* 200:
* description: OK
* 404:
* description: Not found
*
*/
router.get('/snapshotslogs', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
return res.sendFile("/logs/general/qmi-snapshots.log");
} catch (error) {
next(error);
}
});
/**
* @swagger
* /provisions/optionssLogs:
* get:
* description: Get all optionss
* summary: Get all optionss
* produces:
* - application/json
* parameters:
@@ -112,25 +226,25 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
* 200:
* description: JSON Array
*/
router.get('/vmImagesLogs', passport.ensureAuthenticated, async (req, res, next) => {
router.get('/optionssLogs', passport.ensureAuthenticated, async (req, res, next) => {
try {
let filter = req.query.filter? JSON.parse(req.query.filter) : {};
filter.isDeleted = {"$exists": true};
let select = "_id vmImage"
let select = "_id options"
let populates = "[]";
const result = await db.provision.get(filter, select, req.query.skip, req.query.limit, populates);
var final = [];
result.results.forEach(p=>{
for( let key in p.vmImage ) {
for( let key in p.options ) {
var o = {
"_id": p._id.toString(),
"vmIndex": key,
"vmType": p.vmImage[key].vmType? p.vmImage[key].vmType : null,
"diskSizeGb": p.vmImage[key].diskSizeGb? p.vmImage[key].diskSizeGb : null,
"versionProduct": p.vmImage[key].version? p.vmImage[key].version.name : null,
"versionImage": p.vmImage[key].version? p.vmImage[key].version.image : null
"vmType": p.options[key].vmType? p.options[key].vmType : null,
"diskSizeGb": p.options[key].diskSizeGb? p.options[key].diskSizeGb : null,
"versionProduct": p.options[key].selected? p.options[key].selected.name : null,
"versionImage": p.options[key].selected? p.options[key].selected.value : null
}
final.push(o);
}
@@ -140,7 +254,7 @@ router.get('/vmImagesLogs', passport.ensureAuthenticated, async (req, res, next)
count: result.count
};
if ( result.nextSkip && result.nextLimit ) {
out.nextUrl = new URL(req.protocol + '://' + req.get('Host') + req.baseUrl);
out.nextUrl = new URL('https://' + req.hostname + req.baseUrl);
if ( req.query.filter ) {
out.nextUrl.searchParams.append("filter", req.query.filter);
}
@@ -348,4 +462,5 @@ router.post('/:id/updatetagsvms', passport.ensureAuthenticatedAndAdmin, async (r
module.exports = router;

View File

@@ -1,7 +1,7 @@
const express = require('express')
const router = express.Router()
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport');
const passport = require('../passport-okta');
router.get('/all', passport.ensureAuthenticated, async (req, res, next) => {
@@ -203,7 +203,7 @@ router.delete('/vmtypes/:id', passport.ensureAuthenticatedAndAdmin, async (req,
* type: string
* index:
* type: string
* versions:
* values:
* type: array
* items:
* type: object

View File

@@ -0,0 +1,446 @@
const express = require('express');
const router = express.Router();
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport-okta');
const cloudshare = require('../training/cloudshare');
const qa = require('../training/automations');
/**
* @swagger
* /training/sessions:
* get:
* description: Get all training sessions (admin)
* summary: Get all training sessions (admin)
* tags:
* - admin
* produces:
* - application/json
* responses:
* 200:
* description: TrainingTemplate
*/
router.get('/sessions', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
const result = await db.trainingSession.get(filter);
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/templates:
* get:
* description: Get all training templates
* summary: Get all training templates
* produces:
* - application/json
* responses:
* 200:
* description: TrainingTemplate
*/
router.get('/templates', passport.ensureAuthenticated, async (req, res, next) => {
try {
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
const result = await db.trainingTemplate.get(filter);
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/session/{id}:
* get:
* description: Get training session by ID
* summary: Get training session by ID
* produces:
* - application/json
* parameters:
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: TrainingTemplate
*/
router.get('/session/:id', async (req, res, next) => {
try {
const result = await db.trainingSession.get({"_id":req.params.id}, 'passwd -user description status created updated studentEmailFilter');
if (!result || !result.results || result.results.length === 0 ){
return res.status(404).json({"msg": "Not found"});
}
return res.json(result.results[0]);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/session/{id}:
* post:
* description: Post new student for session by ID
* summary: Post new student for session by ID
* produces:
* - application/json
* parameters:
* - name: id
* in: path
* type: string
* required: true
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* responses:
* 200:
* description: TrainingTemplate
*/
router.post('/session/:id', async (req, res, next) => {
try {
const session = await db.trainingSession.getById(req.params.id);
if (!session){
return res.status(404).json({"msg": "Not found"});
}
let student = await db.trainingStudent.getOne({"email": req.body.email, "session": session._id});
let isNew = false;
if ( !student ) {
let data = req.body;
data.session = session._id;
student = await db.trainingStudent.add(data);
isNew = true;
}
let result1, result2;
if ( session.template.cloudshare && session.cloudshareClass ) {
result1 = await cloudshare.addStudentToClass(session, student.email);
}
if ( session.template.needQcsAutomation && session.qaUrl && session.qaToken ) {
result2 = await qa.runQlikAutomation(session, student.email);
}
if (result1 && result1.error || result2 && result2.error) {
return res.status(500).json({"msg": "Something went wrong when registering studeent", err: result1.error? result1.error : result2.error});
} else {
student = await db.trainingStudent.update(student._id, {"status": "sent", "created": new Date()});
if ( isNew ) {
db.trainingSession.update({ _id: session._id }, { $inc: { studentsCount: 1 } });
}
return res.json(student);
}
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/session/{id}:
* delete:
* description: Delete session by ID
* summary: Delete session by ID
* tags:
* - admin
* produces:
* - application/json
* parameters:
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: TrainingTemplate
*/
router.delete('/session/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.trainingSession.update(req.params.id, {status: "terminated"});
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/templates:
* post:
* description: Create new training template
* summary: Create new training template
* tags:
* - admin
* produces:
* - application/json
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* description:
* type: string
* responses:
* 200:
* description: TrainingTemplate
*/
router.post('/templates', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
let data = req.body;
const result = await db.trainingTemplate.add(data);
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/{userId}/sessions:
* post:
* description: Add new training session
* summary: Add new training session
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* produces:
* - application/json
* responses:
* 200:
* description: TrainingSession
*/
router.post('/:userId/sessions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
let data = req.body;
data.user = userId;
let session = await db.trainingSession.add(data);
session = await db.trainingSession.getById(session._id);
let mainAutomation = await qa.getAutomation(session, 'main');
if (!mainAutomation) {
let result = await qa.createAutomations(session);
mainAutomation = result.main;
}
session = await db.trainingSession.update(session._id, {qaToken: mainAutomation.executionToken, qaUrl: `https://${session.qcsTenantHost}/api/v1/automations/${mainAutomation.id}/actions/execute`})
return res.json(session);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/{userId}/sessions:
* get:
* description: Get all training session by user
* summary: Get all training session by user
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: TrainingSession
*/
router.get('/:userId/sessions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const result = await db.trainingSession.get({user: userId, status: {"$ne": "terminated"}});
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/{userId}/sessions/{id}:
* get:
* description: Get training session by Id for a user
* summary: Get training session by Id for a user
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: TrainingSession
*/
router.get('/:userId/sessions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const id = req.params.id;
const result = await db.trainingSession.getOne({user: userId, _id: id});
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/{userId}/sessions/{id}/automation:
* get:
* description: Test if automation exists in tenant
* summary: Test if automation exists in tenant
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: TrainingSession
*/
router.get('/:userId/sessions/:id/automation', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const id = req.params.id;
let session = await db.trainingSession.getById(id);
let automation = await qa.getAutomation(session);
if (automation) {
session = await db.trainingSession.update(session._id, {qaToken: automation.executionToken, qaUrl: `https://${session.qcsTenantHost}/api/v1/automations/${automation.id}/actions/execute`})
}
return res.json(session);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/session/{id}/students:
* get:
* description: Get studetns for a session
* summary: Get studetns for a session
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: TrainingSession
*/
router.get('/session/:id/students', passport.ensureAuthenticated, async (req, res, next) => {
try {
const id = req.params.id;
const result = await db.trainingStudent.get({session: id});
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/{userId}/sessions/{id}:
* put:
* description: Update training session
* summary: Update training session
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* produces:
* - application/json
* responses:
* 200:
* description: TrainingSession
*/
router.put('/:userId/sessions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const result = await db.trainingSession.update(req.params.id, req.body);
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /training/{userId}/sessions/{id}:
* put:
* description: Update training session
* summary: Update training session
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: TrainingSession
*/
router.delete('/:userId/sessions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const result = await db.trainingSession.update(req.params.id, {status: "terminated"});
return res.json(result);
} catch (error) {
next(error);
}
});
module.exports = router;

View File

@@ -1,11 +1,11 @@
const express = require('express')
const router = express.Router()
const db = require('qmi-cloud-common/mongo');
const config = require('qmi-cloud-common/config');
const passport = require('../passport');
const passport = require('../passport-okta');
const fs = require('fs-extra');
const cli = require('qmi-cloud-common/cli');
const barracuda = require('qmi-cloud-common/barracuda');
const sendEmail = require("qmi-cloud-common/send-email");
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
@@ -13,7 +13,7 @@ async function asyncForEach(array, callback) {
}
}
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE } from 'qmi-cloud-common/queues';
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE, SYNAPSE_QUEUE } from 'qmi-cloud-common/queues';
/**
* @swagger
@@ -21,8 +21,6 @@ import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CO
* get:
* description: Get all users
* summary: Get all users
* tags:
* - admin
* parameters:
* - name: filter
* in: query
@@ -38,7 +36,7 @@ import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CO
* 200:
* description: User
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
router.get('/', passport.ensureAuthenticated, async (req, res, next) => {
try {
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
const result = await db.user.get(filter);
@@ -155,7 +153,7 @@ router.put('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, ne
* type: string
* description:
* type: string
* vmImage:
* options:
* type: object
* properties:
* vm1:
@@ -176,7 +174,7 @@ router.put('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, ne
* 404:
* description: Scenario not found
* 400:
* description: Invalid vmImage
* description: Invalid options
*/
router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
@@ -196,8 +194,8 @@ router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (r
return res.status(400).json({"msg": "Number of simultaneous provisions reached for this scenario: " + scenarioSource.numSimultaneousProvisions});
}
//if (!req.body.vmImage || !req.body.vmImage.vm1 || !req.body.vmImage.vm1.vmType ) {
// return res.status(400).json({"msg": "Invalid vmImage"});
//if (!req.body.options || !req.body.options.vm1 || !req.body.options.vm1.vmType ) {
// return res.status(400).json({"msg": "Invalid options"});
//}
req.body.scenarioVersion = scenarioSource.version;
@@ -205,14 +203,72 @@ router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (r
if ( req.body.scheduleData && req.body.scheduleData.is24x7 !== undefined ) {
const schedule = await db.schedule.add(req.body.scheduleData);
req.body.schedule = schedule._id;
} else {
req.body.schedule = null;
}
req.body.terraformImage = config.DOCKERIMAGE_TERRAFORM;
if ( req.body.scenario.indexOf('azqmi-qdi') !== -1 ) {
req.body.version = config.PROVISION_VERSION;
req.body.terraformImage = scenarioSource.terraformImage || process.env.DOCKERIMAGE_TERRAFORM;
//---Accomodation old QDI Cloud Storage
if (req.body.vmImage) {
req.body.options = {};
if (req.body.vmImage.db) {
if (req.body.scenario === 'azqmi-synapse'){
req.body.options['sku'] = {
selected: {
value: req.body.vmImage.db.version.image,
name: req.body.vmImage.db.version.name
}
};
} else {
req.body.options['db'] = {
selected: {
value: req.body.vmImage.db.version.image,
name: req.body.vmImage.db.version.name
}
};
}
}
if (req.body.vmImage.qdi_sa_id) {
req.body.options['qdi_sa_id'] = {
selected: {
value: req.body.vmImage.qdi_sa_id.version.image,
name: req.body.vmImage.qdi_sa_id.version.name
}
};
}
if (req.body.vmImage.qdi_repl_id) {
req.body.options['qdi_repl_id'] = {
selected: {
value: req.body.vmImage.qdi_repl_id.version.image,
name: req.body.vmImage.qdi_repl_id.version.name
}
};
}
if (req.body.vmImage.qdisacc_name) {
req.body.options['qdisacc_name'] = {
selected: {
value: req.body.vmImage.qdisacc_name.version.image,
name: req.body.vmImage.qdisacc_name.version.name
}
};
}
if (req.body.vmImage.qdisacc_accesskey) {
req.body.options['qdisacc_accesskey'] = {
selected: {
value: req.body.vmImage.qdisacc_accesskey.version.image,
name: req.body.vmImage.qdisacc_accesskey.version.name
}
};
}
}
delete req.body['vmImage'];
//---
const provision = await db.provision.add(req.body);
@@ -302,6 +358,7 @@ router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (r
* description: Provision
*/
router.put('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
const triggerUser = req.user;
let provision = await db.provision.getById(req.params.id);
if (!provision){
return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
@@ -311,12 +368,16 @@ router.put('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async
let schedule;
var msg = "";
if ( req.body.scheduleData ) {
let patch = req.body || {};
if ( req.body.scheduleData ) {
if ( req.body.scheduleData._id ) {
schedule = await db.schedule.update(req.body.scheduleData._id, req.body.scheduleData);
msg += `Updated running schedule (${schedule._id}). `;
} else {
schedule = await db.schedule.add(req.body.scheduleData);
msg += `New running schedule (${schedule._id}). `;
}
var tagsEdit = {
"24x7": schedule.is24x7? " " : false,
@@ -324,25 +385,24 @@ router.put('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async
"ShutdownTime": (!schedule.is24x7 && schedule.utcTagShutdownTime)? schedule.utcTagShutdownTime : false
}
cli.updateVmsTags(provision._id, tagsEdit);
patch.schedule = schedule._id;
delete patch['scheduleData'];
}
var resShared = null;
var resProvision = await db.provision.update(provision._id, patch);
let patch = {};
if ( req.body.user ) {
patch.user = req.body.user;
msg += ` - new user: ${req.body.user}`;
}
if ( schedule ) {
patch.schedule = schedule._id;
msg += ` - new schedule: ${schedule._id}`;
if ( patch.user ) {
msg += `Changed owner (${patch.user}).`;
resShared = await db.sharedProvision.updateMany({"provision": provision._id}, {"user": patch.user});
}
let result = {
provision: await db.provision.update(provision._id, patch),
event: await db.event.updateMany({"provision": provision._id}, {"user": req.body.user})
provision: resProvision,
sharedProvision: resShared
}
db.event.add({ user: provision.user._id, provision: provision._id, type: 'provision.update', message: msg });
db.event.add({ user: triggerUser._id, provision: provision._id, type: 'provision.update', message: msg });
return res.json(result);
@@ -377,7 +437,7 @@ router.put('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async
*/
router.delete('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const triggerUser = req.user;
const provision = await db.provision.getById(req.params.id);
if (!provision){
return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
@@ -393,7 +453,7 @@ router.delete('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, as
fs.moveSync(`/provisions/${provision.scenario}_${req.params.id}`, `/provisions/deleted/${provision.scenario}_${req.params.id}`, { overwrite: true })
}
db.event.add({ user: provision.user._id, provision: provision._id, type: 'provision.delete-history' });
db.event.add({ user: triggerUser._id, provision: provision._id, type: 'provision.delete-history' });
return res.json({"provision": delProv, "destroy": delProv.destroy});
} catch (error) {
@@ -618,6 +678,8 @@ router.delete('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, as
router.post('/:userId/provisions/:id/deallocatevms', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const triggerUser = req.user;
let provision = await db.provision.getById(req.params.id);
if (!provision){
return res.status(404).json({"msg": "Not found provision with id "+req.params.id});
@@ -635,9 +697,20 @@ router.post('/:userId/provisions/:id/deallocatevms', passport.ensureAuthenticate
cli.updateVmsTags(provision._id, tagsEdit);
}
cli.deallocate(provision._id);
if (provision.scenario === 'azqmi-synapse') {
queues[SYNAPSE_QUEUE].add("synapse_job", {
provId: provision._id,
tasktype: 'pause',
user: req.user
});
} else if(provision.scenario === 'awsqmi-rds') {
cli.stopDb(provision._id, triggerUser._id);
} else {
cli.deallocate(provision._id, triggerUser._id);
}
return res.json({"statusVms": "Stopping"});
provision.statusVms = "Stopping";
return res.json(provision);
} catch (error) {
db.provision.update(req.params.id, {"statusVms": "Error_Stopping"});
@@ -671,7 +744,8 @@ router.post('/:userId/provisions/:id/deallocatevms', passport.ensureAuthenticate
*/
router.post('/:userId/provisions/:id/startvms', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const triggerUser = req.user;
let provision = await db.provision.getById(req.params.id);
if (!provision){
return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
@@ -691,9 +765,21 @@ router.post('/:userId/provisions/:id/startvms', passport.ensureAuthenticatedAndI
cli.updateVmsTags(provision._id, tagsEdit);
}
cli.start(provision._id);
if (provision.scenario === 'azqmi-synapse') {
queues[SYNAPSE_QUEUE].add("synapse_job", {
provId: provision._id,
tasktype: 'resume',
user: req.user
});
} else if(provision.scenario === 'awsqmi-rds') {
cli.startDb(provision._id, triggerUser._id);
} else {
cli.start(provision._id, triggerUser._id);
}
return res.json({"statusVms": "Starting"});
provision.statusVms = "Starting";
return res.json(provision);
} catch (error) {
db.provision.update(req.params.id, {"statusVms": "Error_Starting"});
@@ -739,7 +825,7 @@ router.post('/:userId/provisions/:id/extend', passport.ensureAuthenticatedAndIsM
let timeRunning = db.utils.getNewTimeRunning(provision);
let countExtend = db.utils.getNewCountExtend(provision);
provision = await db.provision.update(req.params.id, {"runningFrom":new Date(), "timeRunning": timeRunning, "countExtend": countExtend, "pendingNextAction": undefined});
provision = await db.provision.update(req.params.id, {"runningFrom":new Date(), "timeRunning": timeRunning, "countExtend": countExtend, "pendingNextAction": null});
console.log(`APIUser# Extending running period fo provision (${provision._id}), new total extends: ${countExtend}`);
@@ -855,6 +941,8 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
* in: path
* type: string
* required: true
* - in: body
* name: body
* responses:
* 200:
* description: Provision
@@ -867,22 +955,28 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
if ( req.params.withUserId === userId ) {
return res.status(400).json({"msg": "Can't share with the same user"});
}
let provision = await db.provision.getById(req.params.id);
if (!provision){
var shareWithUser = await db.user.getById(req.params.withUserId);
if (!provision || !shareWithUser){
return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
}
if ( req.params.withUserId === provision.user._id ) {
return res.status(400).json({"msg": "Can't share with the same user"});
}
let found = await db.sharedProvision.getOne({
"user": userId,
"user": provision.user._id,
"provision": provision._id,
"sharedWithUser": req.params.withUserId
});
if ( !found ) {
found = await db.sharedProvision.add({"user": userId, "provision": provision._id, "sharedWithUser": req.params.withUserId})
let canManage = req.body && req.body.type === "manage";
found = await db.sharedProvision.add({"user": provision.user._id, "provision": provision._id, "sharedWithUser": req.params.withUserId, "canManage": canManage})
sendEmail.sendSharedProvision(provision, shareWithUser);
db.event.add({ user: userId, provision: provision._id, type: 'provision.share', 'message': `Start sharing with user (${req.params.withUserId}) with "${canManage? 'manage' : 'view'}" permission` });
}
return res.json(found);
@@ -928,7 +1022,10 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
if ( req.params.withUserId === userId ) {
return res.status(400).json({"msg": "Can't share with the same user"});
}
const result = await db.sharedProvision.delMany({"user": userId, "provision": req.params.id, "sharedWithUser": req.params.withUserId});
const result = await db.sharedProvision.delMany({"provision": req.params.id, "sharedWithUser": req.params.withUserId});
db.event.add({ user: userId, provision: req.params.id, type: 'provision.stop-share', 'message': `Stop sharing with user (${req.params.withUserId})` });
return res.json(result);
} catch (error) {
@@ -969,7 +1066,7 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
return res.status(400).json({"msg": "Can't share with the same user"});
}
const result = await db.sharedProvision.delMany({"user": userId, "provision": req.params.id});
const result = await db.sharedProvision.delMany({"provision": req.params.id, "sharedWithUser": req.params.withUserId});
return res.json(result);
} catch (error) {
@@ -1001,12 +1098,10 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
* description: Not found
*
*/
router.get('/:userId/provisions/:id/share', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
router.get('/:userId/provisions/:id/share', passport.ensureAuthenticated, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const result = await db.sharedProvision.get({"user": userId, "provision": req.params.id});
let result = await db.sharedProvision.get({"provision": req.params.id});
return res.json(result);
} catch (error) {
@@ -1038,8 +1133,8 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
let result = await db.sharedProvision.get({"sharedWithUser": userId});
return res.json(result);
let out = await db.sharedProvision.get({"sharedWithUser": userId});
return res.json(out);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
@@ -1092,7 +1187,7 @@ router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (re
* 200:
* description: JSON Array
*/
router.get('/:userId/events', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
router.get('/:userId/events', passport.ensureAuthenticated, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
@@ -1124,11 +1219,11 @@ router.get('/:userId/events', passport.ensureAuthenticatedAndIsMe, async (req, r
* 200:
* description: JSON Array
*/
router.get('/:userId/provisions/:id/events', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
router.get('/:userId/provisions/:id/events', passport.ensureAuthenticated, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const result = await db.event.get({"user": userId, "provision": req.params.id});
const result = await db.event.get({"provision": req.params.id});
return res.json(result);
} catch (error) {
next(error);

269
server/routes/qsProxy.js Normal file
View File

@@ -0,0 +1,269 @@
const express = require('express')
const router = express.Router()
const jsonwebtoken = require("jsonwebtoken");
const fs = require("fs");
const path = require("path");
const cookie = require("cookie");
const cookieParser = require("cookie-parser");
const axios = require("axios");
const { v4: uuidv4 } = require("uuid");
const ws = require("ws");
const WebSocketServer = ws.WebSocketServer;
const WebSocket = ws.WebSocket;
const passport = require('../passport-okta');
// This is the frontend application uri used for responding to requests.
//const frontendUri = "https://outstanding-desert-gatsby.glitch.me";
const privKey = fs.readFileSync(path.resolve(__dirname, '..', 'certs', 'privateKey.pem'));
const TENANT_DOMAIN = process.env["TENANT_DOMAIN"] || "innovation.us.qlikcloud.com";
const JWT_KEYID = process.env["JWT_KEYID"] || "8889be0d-6cf0-44eb-9fef-24bbc83712c5";
const JWT_ISSUER = process.env["JWT_ISSUER"] || TENANT_DOMAIN;
const JWT_USER_GROUPS = ["AnonJWTGroup"];
console.log("QSProxy# - TENANT_DOMAIN", TENANT_DOMAIN);
const auth = {
generateToken: function (user) {
// kid and issuer have to match with the IDP config and the audience has to be qlik.api/jwt-login-session
const signingOptions = {
keyid: JWT_KEYID,
algorithm: "RS256",
issuer: JWT_ISSUER,
expiresIn: "60m",
notBefore: "0s",
audience: "qlik.api/login/jwt-session",
};
// These are the claims that will be accepted and mapped anything else will be ignored. sub, subtype and name are mandatory.
const uuid = uuidv4();
userEmail = user.mail;
const payload = {
jti: uuid,
sub: `az/${user.sub}`,
subType: "user",
name: user.displayName,
email: userEmail,
email_verified: true,
groups: JWT_USER_GROUPS,
};
const token = jsonwebtoken.sign(payload, privKey, signingOptions);
return token;
},
getQlikSessionCookie: async function (token) {
try {
const resp = await axios(`https://${TENANT_DOMAIN}/login/jwt-session`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
});
if (resp.status === 200) {
console.log("QSProxy# - login/jwt-session", resp.headers['set-cookie'])
return resp.headers['set-cookie']
.map((e) => {
return e.split(";")[0];
})
.join(";");
}
return "";
} catch (e) {
console.log("QSProxy# - ERRRR", e);
return "";
}
},
};
const getStoreData = function (sidParsed) {
const store = passport.sessionStore;
return new Promise((resolve) => {
store.get(sidParsed, (err, session) => {
if (err) {
throw err;
}
return resolve(session);
});
});
};
router.get("/*.js", async (req, res) => {
setCors(res);
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
res.end();
});
// fetch resource from qlik using a redirect instead of proxy
// This endpoint is necessary when your web application uses the capability API.
router.get("/resources/*", async (req, res) => {
setCors(res);
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
res.end();
});
router.get('/assets/*', async (req, res) => {
setCors(res);
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
res.end();
});
// Issues the necessary pre-flight request to make sure the browser
// knows how to work with the web application.
router.options("/*", async (req, res) => {
setCors(res);
res.status(200).end();
});
function setCors(res) {
//res.set("Access-Control-Allow-Origin", "http://localhost:3000");
//res.set("Access-Control-Allow-Methods", "GET, OPTIONS");
//res.set("Access-Control-Allow-Headers", "Content-Type, x-proxy-session-id");
//res.set("Access-Control-Allow-Credentials", "true");
}
async function newSession(req){
console.log("QSProxy# - New cookie request for user", req.user.mail);
const jwtToken = auth.generateToken(req.user);
const qlikSession = await auth.getQlikSessionCookie(jwtToken);
return encodeURIComponent(qlikSession);
}
// Intercepts a request to one of Qlik's REST APIs and proxies the request to
// Qlik Cloud.
router.get("/api/v1/*", passport.ensureAuthenticated, async (req, res) => {
setCors(res);
var session = req.session;
var reqHeaders = {};
try {
if (session && session.id && session.qlikSession) {
console.log("QSProxy# - COOKIE FROM session", session.qlikSession);
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
} else {
const newS = await newSession(req);
session.qlikSession = newS;
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
}
try {
console.log("QSProxy# - qlikSession", reqHeaders.cookie);
const { status, data } = await axios(`https://${TENANT_DOMAIN}${req.path}`, {
headers: reqHeaders,
});
res.status(status).end(data);
} catch (e2) {
console.log("QSProxy# Error: QlikSession expired, requesting a new one.");
let newS = await newSession(req);
session.qlikSession = newS;
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
let { status, data } = await axios(`https://${TENANT_DOMAIN}${req.path}`, {
headers: reqHeaders,
});
res.status(status).end(data);
}
} catch (e) {
res.status(500).end("Error obtaining qlik session");
}
});
router.get('/qlik-embed-iframe/*', async (req, res) => {
setCors(res);
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
res.end();
});
function init (server) {
// Websocket section for intercepting websocket requests from the
// frontend application. When the front end application communicates
// communicates with the backend using websockets, this set of
// functions will be invoked.
const wss = new WebSocketServer({ server });
wss.on("connection", async function connection(ws, req) {
let isOpened = false;
// WebSockets do not have access to session information.
// To get the session you need to parse the 1st-party cookie.
// This will give you access to the Qlik Cloud cookie in order
// to proxy requests.
const cookieString = req.headers.cookie;
let qlikCookie = "";
if (cookieString) {
const cookieParsed = cookie.parse(cookieString);
const appCookie = cookieParsed["connect.sid"];
if (appCookie) {
const sidParsed = cookieParser.signedCookie(appCookie, "secret");
var session = await getStoreData(sidParsed);
qlikCookie = decodeURIComponent(session.qlikSession);
}
}
const appId = req.url.match("/app/(.*)\\?")[1];
if (!qlikCookie){
console.log("QSProxy# - Error in Websocket: NO qlikCookie!");
return;
}
const matchCookie = qlikCookie.match("_csrfToken=(.*);");
if (!matchCookie) {
console.log("QSProxy# - Error in Websocket: cant find _csrfToken= in qlikCookie");
return;
}
const csrfToken = matchCookie[1];
var wsConnUrl = `wss://${TENANT_DOMAIN}/app/${appId}?qlik-csrf-token=${csrfToken}`;
const qlikWebSocket = new WebSocket(
wsConnUrl,
{
headers: {
cookie: qlikCookie,
},
}
);
qlikWebSocket.on("error", console.error);
const openPromise = new Promise((resolve) => {
qlikWebSocket.on("open", function open() {
resolve();
});
});
ws.on("message", async function message(data) {
if (!isOpened) {
await openPromise;
isOpened = true;
}
qlikWebSocket.send(data.toString());
});
qlikWebSocket.on("message", function message(data) {
ws.send(data.toString());
});
});
}
module.exports.router = router;
module.exports.init = init;

Some files were not shown because too many files have changed in this diff Show More