310 Commits
1.1.6 ... share

Author SHA1 Message Date
Manuel Romero
6283dddb22 Share 2021-11-26 11:08:45 +01:00
Manuel Romero
c3b8bd119f more stuff for sharing provisions 2021-11-25 18:02:37 +01:00
Manuel Romero
2e31fe279c more stuff 2021-11-25 14:12:29 +01:00
Manuel Romero
899fd2fda7 share provisions with others 2021-11-25 12:37:42 +01:00
Manuel Romero
dc116c9f7f fix destroy 2021-11-24 12:06:32 +01:00
Manuel Romero
455cc3e75d fix 2021-11-24 10:34:07 +01:00
Manuel Romero
16f574f744 child being destroyed check 2021-11-24 09:53:28 +01:00
Manuel Romero
1486e2fd21 check if destroyed or destroying 2021-11-24 08:35:06 +01:00
Manuel Romero
c0349f440e fix open in Azure 2021-11-19 17:47:55 +01:00
Manuel Romero
1a99589c8b Del temp apikeys for QDI 2021-11-10 10:01:18 +01:00
Manuel Romero
b54a590f51 fix 2021-11-09 16:10:50 +01:00
Manuel Romero
eafd78f91c fix 2021-11-09 16:04:28 +01:00
Manuel Romero
243c65c64b filter all users 2021-11-09 15:36:35 +01:00
Manuel Romero
672ba4194a revert 2021-11-09 15:19:11 +01:00
Manuel Romero
9f539bd96a prep of apikey for QDI 2021-11-09 14:56:10 +01:00
Manuel Romero
55b9f0c032 improve admin prov query 2021-11-09 11:46:53 +01:00
Manuel Romero
00dcb33872 fix alerts 2021-11-08 18:08:55 +01:00
Manuel Romero
429838cfc5 new version 2021-11-08 14:13:10 +01:00
Manuel Romero
3a2d6eb0b6 fix ui 2021-11-08 14:12:06 +01:00
Manuel Romero
4290406c19 fix 2021-11-08 13:35:02 +01:00
Manuel Romero
9057f58342 ui fixes 2021-11-08 13:20:30 +01:00
Manuel Romero
6d03a4f6ed Fixes ui 2021-11-08 13:02:35 +01:00
Manuel Romero
5d914c890f new laf 2021-11-05 18:03:54 +01:00
Manuel Romero
b1286a1b2b new look and feel 2021-11-05 17:59:57 +01:00
Manuel Romero
a5b5f2a8ca fix 2021-11-05 13:53:22 +01:00
Manuel Romero
8bb7856102 destroy also children provisions 2021-11-05 13:08:00 +01:00
Manuel Romero
8280b32872 user_oid 2021-10-28 11:27:55 +02:00
Manuel Romero
39ab3fe6dd Merge branch 'dev' 2021-10-26 17:33:08 +02:00
Manuel Romero
fbe30b740c synapse user 2021-10-26 14:04:16 +02:00
Manuel Romero
ab5ab80765 Test watchtower 2021-10-06 17:48:02 +02:00
Manuel Romero
180a20c8d6 fix 2021-10-05 10:02:39 +02:00
Manuel Romero
91d7017b03 vm1 stuff 2021-10-04 17:13:08 +02:00
Manuel Romero
5b6cb73b09 vm1 stuff 2021-10-04 16:54:48 +02:00
Manuel Romero
2003a039f2 new version available 2021-08-03 16:19:42 +02:00
Manuel Romero
96b1302520 fix 2021-07-13 10:38:03 +02:00
Manuel Romero
891acb714e Go to user 2021-07-13 10:21:33 +02:00
Manuel Romero
7cf4e7b5e7 adding try catch cli 2021-07-12 14:12:09 +02:00
Manuel Romero
0c78992958 change to promise 2021-07-12 13:02:36 +02:00
Manuel Romero
ed44eb7fef change to promise 2021-07-12 12:59:53 +02:00
Manuel Romero
8ed6612686 fix3 2021-07-09 13:33:58 +02:00
Manuel Romero
793b8f8739 fix2 2021-07-09 13:15:30 +02:00
Manuel Romero
a9b3b57414 fix 2021-07-09 12:48:25 +02:00
Manuel Romero
69b92a64bc awscli 2021-07-09 12:32:29 +02:00
Manuel Romero
f84ecd7212 new barracuda api version 2021-07-09 09:44:04 +02:00
Manuel Romero
5b6bb90119 simple barracuda 2021-06-25 12:00:02 +02:00
Manuel Romero
d5637065c2 fixes for barracuda 2021-06-25 11:30:57 +02:00
Manuel Romero
6d322c3cc2 fix 2021-06-10 10:55:58 +02:00
Manuel Romero
e0ba340ca1 info times 2021-06-10 10:39:01 +02:00
Manuel Romero
79ec7af773 fixed readme 2021-06-09 16:13:01 +02:00
Manuel Romero
752a3329be periods per scenario 2021-06-09 15:46:22 +02:00
Manuel Romero
36a246504f scenario model allowedInnactiveHours 2021-06-09 13:43:34 +02:00
Manuel Romero
b2a4b41023 n/a for non vm provisions 2021-06-04 09:39:40 +02:00
Manuel Romero
852c943f99 Logevent 2021-06-01 15:12:17 +02:00
Manuel Romero
96e33ca765 azurewebhook 2021-06-01 11:36:08 +02:00
Manuel Romero
bae80ec847 azurewebhook 2021-06-01 10:49:52 +02:00
Manuel Romero
ce55788a26 Delete BarracudaApp first 2021-05-27 11:54:14 +02:00
Manuel Romero
17e128099e new barracuda api 0.0.8 2021-05-26 17:40:57 +02:00
Manuel Romero
17f391ffbd new barracuda api version 2021-05-26 17:35:17 +02:00
Manuel Romero
23bf5f58f6 Only cli for provision with VMs 2021-05-07 13:43:03 +02:00
Manuel Romero
b36616f28a fix 2021-04-30 16:51:58 +02:00
Manuel Romero
5e266a4461 ui fixes 2021-04-30 16:23:04 +02:00
Manuel Romero
0dd199d5f8 no vmImage 2021-04-30 15:53:19 +02:00
Manuel Romero
0f020e96a7 fix 2021-04-29 13:31:37 +02:00
Manuel Romero
0dcba31636 fix 2021-04-28 14:06:48 +02:00
Manuel Romero
04221eb03a using lock icon 2021-04-28 10:06:35 +02:00
Manuel Romero
c12039d354 info class 2021-04-23 13:51:06 +02:00
Manuel Romero
c5dcc547a9 QDC barracuda 2021-04-23 13:38:12 +02:00
Manuel Romero
5c3e10f9df Some buttons 2021-04-20 16:22:37 +02:00
Manuel Romero
e1848da829 Adding user dashbooard 2021-04-20 14:40:16 +02:00
Manuel Romero
a4ae065d8b ttl to 60 for cnames 2021-04-19 13:00:06 +02:00
Manuel Romero
8e0ffd72fa create barracuda app at provision 2021-04-16 15:23:08 +02:00
Manuel Romero
fafe697af5 create barracuda app at provision 2021-04-16 15:21:58 +02:00
Manuel Romero
2cf319175d fix 2021-04-16 14:39:50 +02:00
Manuel Romero
643cb9775d change region barracuda 2021-04-16 13:21:16 +02:00
Manuel Romero
66085e49c5 getting barracuda detauls in one 2021-04-16 11:33:50 +02:00
Manuel Romero
fd85d65aeb barracuda app status 2021-04-16 11:12:02 +02:00
Manuel Romero
24a3573f73 logs 2021-04-16 10:09:48 +02:00
Manuel Romero
048fc6d89d gitbranch for scenarios 2021-04-16 09:38:56 +02:00
Manuel Romero
bf20f4efd4 gitbranch for scenarios 2021-04-16 09:32:47 +02:00
Manuel Romero
2fd05a2b54 adding git 2021-04-15 14:43:05 +02:00
Manuel Romero
03ce50c255 Some logs 2021-04-15 14:03:53 +02:00
Manuel Romero
171f552571 Some logs 2021-04-15 14:01:12 +02:00
Manuel Romero
1843907b42 Testing delete barracuda at destroy provision 2021-04-15 13:06:32 +02:00
Manuel Romero
d88a6f2359 fix 2021-04-14 13:05:03 +02:00
Manuel Romero
d273bc937e delete barracuda endpoint 2021-04-13 15:58:30 +02:00
Manuel Romero
6777d2f0d8 delete barracuda endpoint 2021-04-13 15:56:40 +02:00
Manuel Romero
3d2506639d Adding creation of DNS record 2021-04-13 15:41:09 +02:00
Manuel Romero
a9806bd94e Changed dockerfile to install git 2021-04-13 14:50:20 +02:00
Manuel Romero
783642f083 barracuda email password 2021-04-13 13:37:16 +02:00
Manuel Romero
2a7fec03d7 Barrcuda API endpoint 2021-04-13 13:32:13 +02:00
Manuel Romero
aed18cccfa No logs tf output 2021-04-08 10:46:06 +02:00
Manuel Romero
0f12ee6649 testing tags are the same 2021-04-07 11:07:02 +02:00
Manuel Romero
fbc727a66e allow userId as 'me' 2021-03-31 15:29:37 +02:00
Manuel Romero
897fa2a631 trycatch error setting tags 2021-03-31 10:33:41 +02:00
Manuel Romero
43a428592e Merge branch 'dev' 2021-03-25 15:53:32 +01:00
Manuel Romero
c3796b478f Sort updated on Scenarios 2021-03-25 15:53:23 +01:00
Manuel Romero
e12f60516b sort first by updated 2021-03-25 15:31:28 +01:00
Manuel Romero
84fe322c96 sort updated 2021-03-25 15:00:57 +01:00
Manuel Romero
84263b08dd Merge branch 'dev' 2021-03-25 12:21:31 +01:00
Manuel Romero
895e069326 Using new node version 15.12 2021-03-25 12:21:07 +01:00
Manuel Romero
da4c940055 fix azurecli module 2021-03-25 12:02:30 +01:00
Manuel Romero
f9e608c06c fix 2021-03-17 15:31:00 +01:00
Manuel Romero
15e8c12508 readme 2021-03-17 12:45:42 +01:00
Manuel Romero
1e543b1e6f Destroy on aborted for user 2021-03-17 12:34:40 +01:00
Manuel Romero
00739082f2 movedestroyed with date 2021-03-17 12:04:10 +01:00
Manuel Romero
aacf23f57b movedestroyed 2021-03-17 11:31:42 +01:00
Manuel Romero
ca037b42ef updatemany 2021-03-17 09:55:56 +01:00
Manuel Romero
96300e1e99 fix axios final 2021-03-16 17:49:35 +01:00
Manuel Romero
721fbe24b8 fix axios 2021-03-16 17:45:34 +01:00
Manuel Romero
b20a6fe858 fix axios 2021-03-16 17:30:53 +01:00
Manuel Romero
658bd6a131 abort from UI 2021-03-16 17:20:33 +01:00
Manuel Romero
be5c9ef85e fix 2021-03-16 14:51:17 +01:00
Manuel Romero
5cc6f7a3c6 Test queue stop container 2021-03-16 13:30:24 +01:00
Manuel Romero
5fdd26306a abort job 2021-03-16 11:24:06 +01:00
Manuel Romero
0e74d8873f disable cache stats 2021-03-10 09:18:33 +01:00
Manuel Romero
3f8e9290de cached stats 2021-03-09 09:32:33 +01:00
Manuel Romero
034dde5e1d fix 2021-03-05 18:06:36 +01:00
Manuel Romero
d0e274cd5f fix 2021-03-05 18:00:11 +01:00
Manuel Romero
b3de18bbda fix 2021-03-05 17:55:47 +01:00
Manuel Romero
e02ac30cde fix 2021-03-05 17:44:00 +01:00
Manuel Romero
49b444cfc8 fix 2021-03-05 17:32:50 +01:00
Manuel Romero
1ab9055fc5 request up to 6 2021-03-05 17:30:00 +01:00
Manuel Romero
fd8e542467 adding page vms 2021-03-05 17:08:10 +01:00
Manuel Romero
7699978f39 Added nextlink 2021-03-05 16:48:32 +01:00
Manuel Romero
902db15c17 using azurerm new lib 2021-03-05 15:14:14 +01:00
Manuel Romero
49bd59df62 vms stats 2021-03-05 13:50:13 +01:00
Manuel Romero
b7e44f436c separate vms endpoints 2021-03-05 12:25:16 +01:00
Manuel Romero
aa26928716 stats vms 2021-03-05 12:22:41 +01:00
Manuel Romero
8509a520cf Adding stats endpoint 2021-03-05 11:36:37 +01:00
Manuel Romero
0037634b70 New topbar logo 2021-03-03 16:56:23 +01:00
Manuel Romero
6dfab50871 fix assign new users on events 2021-03-03 14:54:51 +01:00
Manuel Romero
fcc46cdb1a fix check 2021-03-01 19:41:14 +01:00
Manuel Romero
06c4fae9c2 fix check 2021-03-01 19:35:36 +01:00
Manuel Romero
8b18a65303 ffix 2021-02-28 20:28:28 +01:00
Manuel Romero
e4ad1dde54 Better events logs 2021-02-28 20:26:48 +01:00
Manuel Romero
a2b82501aa Changed onschedule timer algorithm 2021-02-28 18:14:08 +01:00
Manuel Romero
ad0c4cbbd1 schedule log event 2021-02-26 12:35:51 +01:00
Manuel Romero
1a638c16bf fix events 2021-02-26 11:52:24 +01:00
Manuel Romero
7dd9fc79c2 show events 2021-02-25 20:03:00 +01:00
Manuel Romero
d492c5e4a8 more events 2021-02-25 18:18:24 +01:00
Manuel Romero
c2a14e5f79 Event collection 2021-02-25 18:10:25 +01:00
Manuel Romero
c888c068b4 fix nootifi 2021-02-25 17:23:05 +01:00
Manuel Romero
7b0bacc990 notificates from divvy and user api 2021-02-25 16:49:47 +01:00
Manuel Romero
4d66c40c07 onscheduled running calculation 2021-02-25 12:39:20 +01:00
Manuel Romero
8a86130fe6 fix 2021-02-25 09:37:18 +01:00
Manuel Romero
b7db4a5f00 fix 2021-02-24 15:00:51 +01:00
Manuel Romero
f3b9869d4f fix 2021-02-24 13:00:23 +01:00
Manuel Romero
0801f84b10 queues logs 2021-02-09 13:56:20 +01:00
Manuel Romero
9dd091f982 pipeline status 2021-02-09 12:05:26 +01:00
Manuel Romero
190967a889 Using qlik smpt sender 2021-02-09 10:42:33 +01:00
Manuel Romero
510254922b adding axios 2021-02-05 17:43:03 +01:00
Manuel Romero
91da6199b3 Merge branch 'dev' 2021-02-05 17:16:32 +01:00
Manuel Romero
9374a60e2a send message with BAM 2021-02-05 17:16:25 +01:00
Manuel Romero
0e1817947e fix 2021-02-05 17:01:23 +01:00
Manuel Romero
870ad47cbc fix 2021-02-05 16:57:24 +01:00
Manuel Romero
4304c56685 fix 2021-02-05 16:36:09 +01:00
Manuel Romero
b7dfee506f Send with BAM 2021-02-05 16:31:49 +01:00
Manuel Romero
022b414766 fix 2021-02-05 14:44:08 +01:00
Manuel Romero
05143c78c0 fix 2021-02-05 13:15:13 +01:00
Manuel Romero
04ed3a22ef test email 2021-02-05 13:13:48 +01:00
Manuel Romero
d3f0513224 worker 2021-02-05 11:28:49 +01:00
Manuel Romero
d5efa608bc warning error cli 2020-12-04 13:11:03 +01:00
Manuel Romero
040d2187cb try catch start stop vms 2020-12-04 10:46:00 +01:00
Manuel Romero
2d254ae238 first tags then start 2020-12-03 12:18:05 +01:00
Manuel Romero
82861f2130 wait for updating tags azure 2020-12-03 11:43:21 +01:00
Manuel Romero
e0270e6925 wait for updating tags azure 2020-12-03 11:39:35 +01:00
Manuel Romero
dc2180af03 Using avatar-ui for avatarars 2020-12-01 12:40:06 +01:00
Manuel Romero
d240b06c5c support emails 2020-11-30 16:03:50 +01:00
Manuel Romero
2434d2d157 cosmetics 2020-11-26 14:12:16 +01:00
Manuel Romero
c631fb141e Adding user_email to qdi scenario 2020-11-24 09:34:45 +01:00
Manuel Romero
4e27a35e4e Adding scenario titles 2020-11-18 10:40:28 +01:00
Manuel Romero
1b458ab2df New image 2020-11-13 14:13:40 +01:00
Manuel Romero
082e98c01a Adding init password 2020-11-11 14:19:59 +01:00
Manuel Romero
c6d1f84c57 new versions 2020-11-03 13:51:03 +01:00
Manuel Romero
c3b9e8532f fix2 2020-11-03 13:36:03 +01:00
Manuel Romero
4194973884 fix 2020-11-03 12:56:00 +01:00
Manuel Romero
57a47d7794 fix 2020-11-03 12:52:04 +01:00
Manuel Romero
953930bc42 Synapse databases and others 2020-11-03 12:36:50 +01:00
Manuel Romero
35d78b4db0 Do not display error 2020-10-23 14:05:27 +02:00
Manuel Romero
b8a8b8bace error passport image 2020-10-23 12:08:37 +02:00
Manuel Romero
e09d03f1ef Photos 2020-10-23 10:46:07 +02:00
Manuel Romero
dd2f328f45 fix sort by region 2020-10-22 15:51:24 +02:00
Manuel Romero
b3895be44a Set url for sent emails 2020-10-22 15:39:21 +02:00
Manuel Romero
7516f46903 fix filter scenarioos 2020-10-20 21:22:46 +02:00
Manuel Romero
3c7327ed57 Allowed users 2020-10-20 11:34:20 +02:00
Manuel Romero
8e1c027038 No logs 2020-10-19 18:55:51 +02:00
Manuel Romero
f8fe33039c better ui 2020-10-19 18:51:05 +02:00
Manuel Romero
bf6ea3cd51 better ui 2020-10-19 18:43:45 +02:00
Manuel Romero
e3aeeea51f Allowed users for a scenario 2020-10-19 18:37:27 +02:00
Manuel Romero
1fba40289e Default enable if optional server 2020-10-19 11:17:04 +02:00
Manuel Romero
b32c18a349 Set default terraform image 2020-10-16 09:57:48 +02:00
Manuel Romero
9fc6bdd5a3 fix 2020-10-16 09:12:31 +02:00
Manuel Romero
755fd11f1a logs terraform version 2020-10-15 18:31:19 +02:00
Manuel Romero
82984673aa New package versions 2020-10-15 17:42:29 +02:00
Manuel Romero
de600d1525 Taking into account disabled vms for a scenario 2020-10-15 17:36:24 +02:00
Manuel Romero
c1335fdf25 Using terraform version 2020-10-15 17:13:43 +02:00
Manuel Romero
36eced4232 Merge branch 'master' into vmsselect 2020-10-15 14:49:51 +02:00
Manuel Romero
3f11e36b42 better logs 2020-10-15 14:49:31 +02:00
Manuel Romero
db2a3a4d60 better logs 2020-10-15 14:47:31 +02:00
Manuel Romero
d7222c6cf0 better logs 2020-10-15 14:39:18 +02:00
Manuel Romero
fca71efb1e Do not show logs for other subscriptions 2020-10-15 14:18:10 +02:00
Manuel Romero
6943d7eaa4 vmsselect 2020-10-15 14:05:52 +02:00
Manuel Romero
4b343830c9 Some polished UI 2020-10-13 09:57:05 +02:00
Manuel Romero
f50d81651b backend logs url 2020-10-09 16:20:04 +02:00
Manuel Romero
7ac86a435a fix 2020-10-08 14:06:45 +02:00
Manuel Romero
e2dd677c7f Limits of simultaneous provisions 2020-10-08 13:31:10 +02:00
Manuel Romero
1a8a59fd77 backend logs 2020-10-06 17:56:31 +02:00
Manuel Romero
80c085ca38 Adding backend logs section 2020-10-06 17:11:08 +02:00
Manuel Romero
6f079a6361 More logs 2020-10-05 13:12:19 +02:00
Manuel Romero
b2cf816e2f fix 2020-10-05 12:18:04 +02:00
Manuel Romero
6c176726f2 fix 2020-10-05 11:35:23 +02:00
Manuel Romero
47c6a51c39 DivvyCloud logs 2020-10-05 10:23:45 +02:00
Manuel Romero
04f509c76b DivvyCloud logs 2020-10-05 10:12:54 +02:00
Manuel Romero
261a3518ee fixed onScheduleRenewed 2020-10-01 18:01:05 +02:00
Manuel Romero
eb277aa5e8 some fixes and paging requests 2020-09-29 16:45:04 +02:00
Manuel Romero
c4b6ef56e6 totalCount results 2020-09-29 12:30:12 +02:00
Manuel Romero
886d8fa202 New objects for mashup 2020-09-28 12:41:20 +02:00
Manuel Romero
60ce0dcb3e Heights as numbers 2020-09-24 17:17:28 +02:00
Manuel Romero
35748fe333 cost mashup 2020-09-24 15:39:30 +02:00
Manuel Romero
aa826eb5f3 Added external qcs qdt-components 2020-09-24 13:21:25 +02:00
Manuel Romero
e25619b601 Addimg mystats as iframe 2020-09-23 18:06:32 +02:00
Manuel Romero
12b2e612aa qdt components to qcs 2020-09-23 14:52:05 +02:00
Manuel Romero
b6f095d504 set content type json 2020-09-16 13:11:47 +02:00
Manuel Romero
c5a9cbde58 Adding authentication 2020-09-16 12:59:08 +02:00
Manuel Romero
dc8cae1853 costexport folder 2020-09-16 12:46:45 +02:00
Manuel Romero
e22168f39c fix2 2020-09-15 14:14:17 +02:00
Manuel Romero
3319c400b5 fix 2020-09-15 14:03:17 +02:00
Manuel Romero
6725d91663 Added description to provisions table 2020-09-15 14:00:13 +02:00
Manuel Romero
7a78cb6b21 Fix location for secure scenarios 2020-09-04 12:03:06 +02:00
Manuel Romero
0dc76aaf94 fixes 2020-09-02 13:00:20 +02:00
Manuel Romero
58daa3a31e fix 2020-09-02 12:49:28 +02:00
Manuel Romero
beb2330bcd Fixes 2020-09-02 12:12:09 +02:00
Manuel Romero
f38aef13be Fixes 2020-09-02 11:36:59 +02:00
Manuel Romero
7920909308 Set startup time tag if is enable 2020-09-02 10:56:59 +02:00
Manuel Romero
d9a2df002c Disable startuptime 2020-09-01 17:53:40 +02:00
Manuel Romero
5ba87ef2c5 Added labels back to model 2020-09-01 13:49:04 +02:00
Manuel Romero
b83b599553 fix 2020-09-01 13:40:22 +02:00
Manuel Romero
646d49fa2e Fixed some bugs 2020-09-01 12:29:32 +02:00
Manuel Romero
b1535d74a1 UI enhancements 2020-09-01 10:32:43 +02:00
Manuel Romero
8606e31bc5 removed appgw rg name 2020-08-31 17:07:32 +02:00
Manuel Romero
4b87a7584a Getting WAF policy paramateres from DB 2020-08-31 16:08:59 +02:00
Manuel Romero
5234295a87 no subscription in scenario 2020-08-10 18:07:13 +02:00
Manuel Romero
aba7919400 fix2 2020-08-10 16:59:18 +02:00
Manuel Romero
3fdd58db33 fix 2020-08-10 16:37:51 +02:00
Manuel Romero
6651510249 deployOps for scenarios 2020-08-10 15:58:31 +02:00
Manuel Romero
326481521a set loggingLevel for passport to warn 2020-08-05 13:29:16 +02:00
Manuel Romero
b34895a8ae Adding schedule type to logs 2020-08-03 16:42:13 +02:00
Manuel Romero
3663462bb4 Do not change if provision is not finished 2020-07-30 13:00:05 +02:00
Manuel Romero
815df437c7 Adding UTC time 2020-07-30 10:30:47 +02:00
Manuel Romero
cfa47cd47d Fixes 2020-07-30 09:59:10 +02:00
Manuel Romero
fbfd0b5f4c Fix timeRunningOnSchedule 2020-07-30 09:33:45 +02:00
Manuel Romero
ea9a002e01 DivvyCloud event log 2020-07-30 09:14:10 +02:00
Manuel Romero
6f8f7b12ab tag ProvId should be a string 2020-07-30 09:07:05 +02:00
Manuel Romero
bec0ebc623 fix 2020-07-30 09:04:20 +02:00
Manuel Romero
5f863d6ec4 Divvy updates fix 2020-07-30 08:58:17 +02:00
Manuel Romero
d3345aa685 Set tag ProvId 2020-07-30 08:50:29 +02:00
Manuel Romero
aa48199e58 setting provId 2020-07-30 08:48:18 +02:00
Manuel Romero
028d9822bb moment 2020-07-29 16:49:06 +02:00
Manuel Romero
08deb29ae8 adding type to message 2020-07-29 16:32:36 +02:00
Manuel Romero
1a7537ef27 brown 2020-07-29 16:25:25 +02:00
Manuel Romero
e4ef581049 New versions 2020-07-29 15:56:06 +02:00
Manuel Romero
c642c5a5e9 fix sendemail 2020-07-29 15:23:50 +02:00
Manuel Romero
0821049fc1 Some fixes 2020-07-29 15:20:28 +02:00
Manuel Romero
996be45987 yeah 2020-07-29 13:45:45 +02:00
Manuel Romero
44f654c085 yeah 2020-07-29 13:39:29 +02:00
Manuel Romero
98fe966f32 yeah 2020-07-29 13:25:33 +02:00
Manuel Romero
159c9a80c1 minor fixes 2020-07-29 13:06:11 +02:00
Manuel Romero
f407ed5899 fix confirm button colors 2020-07-29 12:28:26 +02:00
Manuel Romero
77ed627312 fix 2020-07-29 12:19:16 +02:00
Manuel Romero
57030be78f fixes 2020-07-29 12:17:15 +02:00
Manuel Romero
c8953e46d5 admin provision info 2020-07-29 12:01:53 +02:00
Manuel Romero
e18fbd7e6e fix 2020-07-29 11:43:26 +02:00
Manuel Romero
551ad78345 fix error 2020-07-29 11:29:32 +02:00
Manuel Romero
dbabd39967 Adding info to emails 2020-07-29 11:16:16 +02:00
Manuel Romero
5cf2f37e7c fix description decodeURI emails 2020-07-29 11:08:19 +02:00
Manuel Romero
fb82a18a5a fixes 2020-07-29 11:04:50 +02:00
Manuel Romero
930a9dab4a isStartupTimeEnable to false if stop from CLI 2020-07-29 11:01:25 +02:00
Manuel Romero
382b9b1576 fixes 2020-07-29 10:56:51 +02:00
Manuel Romero
524b16fd75 fixes 2020-07-29 10:35:33 +02:00
Manuel Romero
07f29da574 stuff 2020-07-29 10:06:19 +02:00
Manuel Romero
ecb6ad4d86 k 2020-07-28 20:44:37 +02:00
Manuel Romero
0c79d43ba1 fix 2020-07-28 20:26:51 +02:00
Manuel Romero
52f8d098b3 Fix cli 2020-07-28 20:17:51 +02:00
Manuel Romero
e4230d58fa keep schedule at stop 2020-07-28 18:36:17 +02:00
Manuel Romero
8df6915b38 fixes 2020-07-28 17:30:44 +02:00
Manuel Romero
2616332b4a polish 2020-07-28 17:05:52 +02:00
Manuel Romero
8218f0469b go 2020-07-28 16:56:24 +02:00
Manuel Romero
8bfd76981f Adding bundle hours 2020-07-28 15:48:27 +02:00
Manuel Romero
2205a9520d New prov schedule fixes 2020-07-28 14:35:36 +02:00
Manuel Romero
3f73c93eb2 stop start messages 2020-07-28 12:58:10 +02:00
Manuel Romero
5dfd363ecd fix 2020-07-28 12:02:56 +02:00
Manuel Romero
c69125c5f2 Adding Divvy time received 2020-07-28 11:37:49 +02:00
Manuel Romero
50d68d0078 Adding timezone 2020-07-28 11:33:03 +02:00
Manuel Romero
73f37b7567 Some minor 2020-07-28 11:17:12 +02:00
Manuel Romero
d7b2ae93bb Changed to Schedule 2020-07-28 10:30:12 +02:00
Manuel Romero
1ab433daec fix 2020-07-27 17:48:24 +02:00
Manuel Romero
4fa0f78b42 last fixes 2020-07-27 17:25:16 +02:00
Manuel Romero
fdea228ed1 fix tags 2020-07-27 16:55:25 +02:00
Manuel Romero
707f0c6830 fix error 2020-07-27 16:38:42 +02:00
Manuel Romero
f71ef166b5 Fix emails to be 7 days 2020-07-27 16:21:50 +02:00
Manuel Romero
89462fd21a renamed tags 2020-07-27 15:37:20 +02:00
Manuel Romero
5f99f553c5 Change owner 2020-07-27 15:20:34 +02:00
Manuel Romero
dddc1314e7 fixes 2020-07-27 15:15:21 +02:00
Manuel Romero
c5f50cb6b1 timeRunning to service. 2020-07-27 14:53:05 +02:00
Manuel Romero
885f2986ef cli 2020-07-27 13:41:50 +02:00
Manuel Romero
e45445f919 Using timezone for times 2020-07-24 15:54:53 +02:00
Manuel Romero
2040c9f914 fixes 2020-07-23 19:28:23 +02:00
Manuel Romero
93884ebeea polishing 2020-07-23 19:12:59 +02:00
Manuel Romero
dc9354a36a Fixes 2020-07-23 18:31:32 +02:00
Manuel Romero
5e1e193688 Scheduler 2020-07-23 18:13:37 +02:00
172 changed files with 417290 additions and 2906 deletions

2
.gitignore vendored
View File

@@ -48,4 +48,4 @@ secrets.json
qmi-cloud-tf-modules/
*.pfx
/photos/*

View File

@@ -1,7 +1,7 @@
# Stage 1:
FROM node:13.8-alpine AS sources
FROM node:15.12.0-alpine AS sources
RUN apk --no-cache add yarn
RUN apk --no-cache add yarn git
WORKDIR /var/www/app
@@ -12,12 +12,13 @@ ADD ./qmi-cloud-common ./qmi-cloud-common
RUN yarn install --production
# Stage 2:
FROM node:13.8-alpine AS production
FROM node:15.12.0-alpine AS production
WORKDIR /var/www/app
COPY --from=sources /var/www/app/node_modules ./node_modules
COPY --from=sources /var/www/app/package.json ./package.json
COPY ./server ./server
COPY ./dist ./dist
COPY ./mystatsmashup ./mystatsmashup
EXPOSE 3000
EXPOSE 3100

View File

@@ -1,5 +1,7 @@
# QMI Cloud
[![pipeline status](https://gitlab.com/qmi/qmi-cloud/badges/master/pipeline.svg)](https://gitlab.com/qmi/qmi-cloud/-/commits/master)
## Pre-requisites
- Docker
- Docker-Compose

View File

@@ -38,12 +38,15 @@
"src/styles.scss"
],
"scripts": [
"node_modules/chart.js/dist/Chart.js",
"node_modules/hammerjs/hammer.min.js",
"node_modules/marked/lib/marked.js",
"node_modules/prismjs/prism.js",
"node_modules/prismjs/plugins/line-highlight/prism-line-highlight.js",
"node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js"
"node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js",
"src/assets/js/qdt-components.js",
"src/assets/js/qlikMashupQCS.js"
]
},
"configurations": {

0
costexport/.keep Normal file
View File

1
costexport/sample.json Normal file
View File

@@ -0,0 +1 @@
{"test": "ok"}

Binary file not shown.

135423
dist/qmi-cloud/assets/js/qdt-components.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,52 @@
var qlikMashup = (function() {
const { qdtCapabilityApp, QdtViz } = QdtComponents;
const initMyQdt = async function(config, returnto) {
const urlLoggedIn = "/api/v1/users/me";//Use GET request to see if you are authenticated
const urlLogin = "/login";
const response = await fetch(`https://${config.host}${urlLoggedIn}`, {
credentials: 'include',
headers: {
'Qlik-Web-Integration-ID': config.webIntegrationId
}
})
if( response.status===401 ) {
const url = new URL(`https://${config.host}${urlLogin}`);
url.searchParams.append('returnto', returnto);
url.searchParams.append('qlik-web-integration-id', config.webIntegrationId);
window.location.href = url;
}
const user = await response.json();
const capabilityApiAppPromise = qdtCapabilityApp(config);
const app = await capabilityApiAppPromise;
return {app: app, user: user};
};
return {
"initMyQdt": initMyQdt,
"myQdtViz": function(app, divId, qlikObjectId, height, type){
QdtViz({
element: document.getElementById(divId),
app,
options: {
type: type,
id: qlikObjectId,
height: height
},
});
}
}
})(qlikMashup||{})

BIN
dist/qmi-cloud/assets/user1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!--
Font Awesome Free 5.12.1 by @fontawesome - https://fontawesome.com
Font Awesome Free 5.13.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 Tue Feb 4 18:05:39 2020
Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020
By Robert Madole
Copyright (c) Font Awesome
</metadata>

Before

Width:  |  Height:  |  Size: 699 KiB

After

Width:  |  Height:  |  Size: 699 KiB

Binary file not shown.

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!--
Font Awesome Free 5.12.1 by @fontawesome - https://fontawesome.com
Font Awesome Free 5.13.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 Tue Feb 4 18:05:39 2020
Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020
By Robert Madole
Copyright (c) Font Awesome
</metadata>

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 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.12.1 by @fontawesome - https://fontawesome.com
Font Awesome Free 5.13.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 Tue Feb 4 18:05:39 2020
Created by FontForge 20190801 at Mon Mar 23 10:45:51 2020
By Robert Madole
Copyright (c) Font Awesome
</metadata>
@@ -23,7 +23,7 @@ Copyright (c) Font Awesome
bbox="-0.983398 -64.9834 640.104 448.427"
underline-thickness="25"
underline-position="-50"
unicode-range="U+0020-F941"
unicode-range="U+0020-F976"
/>
<missing-glyph />
<glyph glyph-name="glass-martini" unicode="&#xf000;"
@@ -2377,6 +2377,10 @@ c0 1.77051 0.74707 4.43359 1.66895 5.94531c0.510742 0.827148 1.51855 2.01953 2.2
c-0.112305 3.6416 -3.08203 7.27051 -6.62988 8.10059zM565.27 119.9c5.92383 -5.26953 10.7432 -15.9814 10.7432 -23.9102c0 -8.49121 -5.38184 -19.6865 -12.0127 -24.9902l-151.23 -121c-9.67188 -7.72754 -27.5693 -14 -39.9492 -14h-0.0507812h-356.77
c-8.83203 0 -16 7.16797 -16 16v96c0 8.83203 7.16797 16 16 16h55.4004l46.5 37.71c17.8789 14.5059 51.0762 26.2842 74.0996 26.29h160v0c17.6309 0 31.9668 -14.3096 32 -31.9404v-0.120117c0 -1.48438 -0.206055 -3.87695 -0.459961 -5.33984
c-2.54004 -15.6992 -17.3496 -26.5996 -33.25 -26.5996h-78.29c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16h118.27h0.176758c12.3496 0 30.1904 6.27148 39.8232 14l92.4004 73.9004c12.4004 10 30.7998 10.6992 42.5996 0z" />
<glyph glyph-name="hand-holding-water" unicode="&#xf4c1;" horiz-adv-x="576"
d="M288 192c-53 0 -96 42.0996 -96 94c0 40 57.0996 120.7 83.2002 155.6c6.39941 8.5 19.2002 8.5 25.5996 0c26.1006 -34.8994 83.2002 -115.6 83.2002 -155.6c0 -51.9004 -43 -94 -96 -94zM565.3 119.9c15.1006 -13.6006 13.9004 -36.8008 -1.2998 -48.9004l-151.2 -121
c-11.3994 -9.09961 -25.5 -14 -40 -14h-356.8c-8.7998 0 -16 7.2002 -16 16v96c0 8.7998 7.2002 16 16 16h55.4004l46.5 37.7002c21 17 47.0996 26.2998 74.0996 26.2998h160c19.5 0 34.9004 -17.4004 31.5996 -37.4004
c-2.59961 -15.6992 -17.3994 -26.5996 -33.2998 -26.5996h-78.2998c-8.7998 0 -16 -7.2002 -16 -16s7.2002 -16 16 -16h118.3c14.6006 0 28.7002 4.90039 40 14l92.4004 73.9004c12.3994 10 30.7998 10.6992 42.5996 0z" />
<glyph glyph-name="hands" unicode="&#xf4c2;" horiz-adv-x="640"
d="M204.8 217.6l57.6006 -76.7998c16.5996 -22.2002 25.5996 -49.0996 25.5996 -76.7998v-112c0 -8.7998 -7.2002 -16 -16 -16h-131.7c-7.2002 0 -13.5 4.7002 -15.2998 11.5996c-2 7.80078 -5.40039 15.2002 -10.4004 21.7002l-104.1 134.3
c-6.7998 8.5 -10.5 19.1006 -10.5 30v218.4c0 17.7002 14.2998 32 32 32s32 -14.2998 32 -32v-148.4l89.7998 -107.8c6 -7.2998 16.9004 -7.7998 23.6006 -1.09961l12.7998 12.7998c5.59961 5.59961 6.2998 14.5 1.5 20.9004l-38.1006 50.7998
@@ -3502,6 +3506,13 @@ d="M12.4102 299.98c-16.5498 7.50977 -16.5498 32.5293 0 40.0391l232.95 105.671c2.
c-6.7998 -3.08984 -14.4893 -3.08984 -21.29 0zM499.59 211.7c16.5498 -7.5 16.5498 -32.5 0 -40l-232.95 -105.59c-6.7998 -3.08008 -14.4893 -3.08008 -21.29 0l-232.939 105.59c-16.5498 7.5 -16.5498 32.5 0 40l58.0996 26.3301l161.63 -73.2705
c7.57031 -3.42969 15.5908 -5.16992 23.8604 -5.16992s16.2998 1.74023 23.8604 5.16992l161.64 73.2705zM499.59 83.9004c16.5498 -7.5 16.5498 -32.5 0 -40l-232.95 -105.591c-6.7998 -3.0791 -14.4893 -3.0791 -21.29 0l-232.939 105.591
c-16.5498 7.5 -16.5498 32.5 0 40l57.8799 26.2295l161.85 -73.3701c7.57031 -3.42969 15.5908 -5.16992 23.8604 -5.16992s16.2998 1.74023 23.8604 5.16992l161.859 73.3701z" />
<glyph glyph-name="lungs" unicode="&#xf604;" horiz-adv-x="640"
d="M636.11 57.8496c2.58984 -9.68945 3.88965 -19.6396 3.88965 -29.6299c0 -61.2295 -62.4805 -105.439 -125.24 -88.6201l-59.5 15.9502c-42.1797 11.3105 -71.2598 47.4697 -71.2598 88.6201v87.4902l85.8398 -57.2305
c1.1123 -0.741211 3.09961 -1.34375 4.43652 -1.34375c2.36328 0 5.34375 1.59668 6.65332 3.56445l8.87988 13.3096c0.742188 1.1123 1.34375 3.09961 1.34375 4.43555c0 2.36328 -1.5957 5.34473 -3.56348 6.6543l-167.59 111.72l-167.59 -111.72
c-1.96777 -1.30957 -3.56445 -4.29004 -3.56445 -6.65332c0 -1.33691 0.602539 -3.32422 1.34473 -4.43652l8.87988 -13.3096c1.30859 -1.96777 4.29004 -3.56445 6.65332 -3.56445c1.33691 0 3.32422 0.601562 4.43652 1.34375l85.8398 57.2305v-87.4902
c0 -41.1504 -29.0801 -77.3203 -71.2598 -88.6201l-59.5 -15.9502c-62.7598 -16.8193 -125.24 27.3906 -125.24 88.6201c0 9.99023 1.2998 19.9404 3.88965 29.6299c21.6699 81.3008 56.04 159.15 102.011 231.021c22.1191 34.5703 36.0693 63.1299 80.0498 63.1299
c38.6895 0 70.0498 -29.4199 70.0498 -65.71v-60.1104l32.8799 21.9199c4.4502 2.9707 7.12012 7.95996 7.12012 13.3105v170.59c0 8.83984 7.16016 16 16 16h16c8.83984 0 16 -7.16016 16 -16v-170.59v-0.00292969c0 -4.72363 3.18945 -10.6855 7.12012 -13.3076
l32.8799 -21.9199v60.1104c0 36.29 31.3604 65.71 70.0498 65.71c43.9805 0 57.9307 -28.5596 80.0498 -63.1299c45.9707 -71.8701 80.3408 -149.72 102.011 -231.021z" />
<glyph glyph-name="microscope" unicode="&#xf610;"
d="M160 128c-17.6699 0 -32 14.3301 -32 32v224c0 17.6699 14.3301 32 32 32v16c0 8.83984 7.16016 16 16 16h64c8.83984 0 16 -7.16016 16 -16v-16c17.6699 0 32 -14.3301 32 -32v-224c0 -17.6699 -14.3301 -32 -32 -32h-12v-16c0 -8.83984 -7.16016 -16 -16 -16h-40
c-8.83984 0 -16 7.16016 -16 16v16h-12zM464 0c26.5098 0 48 -21.4902 48 -48c0 -8.83984 -7.16016 -16 -16 -16h-480c-8.83984 0 -16 7.16016 -16 16c0 26.5098 21.4902 48 48 48h272c70.5801 0 128 57.4199 128 128s-57.4199 128 -128 128v64
@@ -4503,6 +4514,11 @@ c-2.58789 2.58691 -4.6875 7.65625 -4.6875 11.3154s2.09961 8.72852 4.6875 11.3154
c-10.7441 -10.748 -31.4814 -22.2393 -46.29 -25.6494l-120.25 -27.75l-102 -102c-2.58691 -2.58789 -7.65625 -4.6875 -11.3154 -4.6875s-8.72754 2.09961 -11.3154 4.6875l-22.6191 22.6191c-2.58789 2.58789 -4.6875 7.65625 -4.6875 11.3154
s2.09961 8.72852 4.6875 11.3154l102 102l27.7393 120.26c3.4248 14.8057 14.9248 35.5439 25.6699 46.29l109.671 109.67l45.25 -45.25l-55.1006 -55.1006zM273.2 141.31l9.30957 9.31055l-67.8896 67.8896l-9.31055 -9.30957
c-3.57715 -3.59082 -7.41211 -10.5127 -8.55957 -15.4502l-18.2998 -79.2998l79.2998 18.3193c4.94043 1.13379 11.8623 4.95996 15.4502 8.54004z" />
<glyph glyph-name="disease" unicode="&#xf7fa;"
d="M472.29 252.1c48.54 -16.6191 53.8301 -73.8301 8.99023 -96.79l-62 -31.7393c-17.8301 -9.12988 -29.2803 -25.2002 -30.6299 -43l-4.7002 -61.8604c-3.4502 -44.79 -65.1299 -66.7803 -104.45 -37.2197l-54.3203 40.8301
c-15.6201 11.7295 -36.96 16.1201 -57.0693 11.7295l-70 -15.2803c-50.6504 -11.0596 -94.1104 32.5605 -73.46 73.8008l28.4297 57c8.17969 16.3799 6.43945 35.1699 -4.63965 50.2393l-38.54 52.4209c-27.9307 37.9492 7 86.9092 59 82.8398l71.8994 -5.62012
c20.6602 -1.62012 40.9404 5.59961 54.2002 19.3096l46.0898 47.7207c33.4297 34.5098 98.4199 21.1494 110 -22.6201l16 -60.4502c4.60059 -17.3906 18.8604 -31.71 38.1406 -38.3105zM160 192c17.6641 0 32 14.3359 32 32s-14.3359 32 -32 32s-32 -14.3359 -32 -32
s14.3359 -32 32 -32zM288 96c17.6641 0 32 14.3359 32 32s-14.3359 32 -32 32s-32 -14.3359 -32 -32s14.3359 -32 32 -32zM304 224c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16s-16 -7.16797 -16 -16s7.16797 -16 16 -16z" />
<glyph glyph-name="egg" unicode="&#xf7fb;" horiz-adv-x="384"
d="M192 448c106 0 192 -214 192 -320s-86 -192 -192 -192s-192 86 -192 192s86 320 192 320z" />
<glyph glyph-name="hamburger" unicode="&#xf805;"
@@ -4518,6 +4534,15 @@ l38.3994 -6.40039c13.46 -2.25 23.1504 -12.0996 23.1504 -23.54v-49.5898l35.6504 -
<glyph glyph-name="hard-hat" unicode="&#xf807;"
d="M480 160v-64h-448v64c0 80.25 49.2803 148.92 119.19 177.62l40.8096 -81.6201v112c0 8.83203 7.16797 16 16 16h96c8.83203 0 16 -7.16797 16 -16v-112l40.8096 81.6201c69.9102 -28.7002 119.19 -97.3701 119.19 -177.62zM496 64c8.83203 0 16 -7.16797 16 -16v-32
c0 -8.83203 -7.16797 -16 -16 -16h-480c-8.83203 0 -16 7.16797 -16 16v32c0 8.83203 7.16797 16 16 16h480z" />
<glyph glyph-name="hospital-user" unicode="&#xf80d;" horiz-adv-x="640"
d="M480 128c-52.9922 0 -96 43.0078 -96 96s43.0078 96 96 96s96 -43.0078 96 -96s-43.0078 -96 -96 -96zM528 96c61.8242 0 112.002 -50.1758 112.002 -112c0 -0.170898 -0.000976562 -0.449219 -0.00195312 -0.620117c-0.139648 -26.2598 -21.7305 -47.3799 -48 -47.3799
h-224c-26.2695 0 -47.8604 21.1201 -48 47.3799c-0.000976562 0.170898 -0.00195312 0.449219 -0.00195312 0.620117c0 61.8242 50.1758 112 112 112h0.00195312h0.0810547c1.9707 0 5.09277 -0.488281 6.96875 -1.08984
c10.9795 -3.81445 29.3223 -6.91016 40.9453 -6.91016s29.9658 3.0957 40.9453 6.91016c1.87891 0.601562 5.00488 1.08984 6.97754 1.08984h0.0820312zM329.91 85.5498c-23.1367 -23.1309 -41.915 -68.4561 -41.915 -101.172
c0 -0.322266 0.00195312 -0.845703 0.00488281 -1.16797c0.136719 -14.5381 7.44336 -35.6885 16.3096 -47.21h-288.31c-8.83203 0 -16 7.16797 -16 16v368c0 17.6641 14.3359 32 32 32h32v64c0 17.6641 14.3359 32 32 32h160c17.6641 0 32 -14.3359 32 -32v-64h32
c17.6641 0 32 -14.3359 32 -32v-216.62c-6.58008 -4.32227 -16.4766 -12.3096 -22.0898 -17.8301zM144 44v40c0 6.62402 -5.37598 12 -12 12h-40c-6.62402 0 -12 -5.37598 -12 -12v-40c0 -6.62402 5.37598 -12 12 -12h40c6.62402 0 12 5.37598 12 12zM144 172v40
c0 6.62402 -5.37598 12 -12 12h-40c-6.62402 0 -12 -5.37598 -12 -12v-40c0 -6.62402 5.37598 -12 12 -12h40c6.62402 0 12 5.37598 12 12zM192 294v26h26c3.31152 0 6 2.68848 6 6v20c0 3.31152 -2.68848 6 -6 6h-26v26c0 3.31152 -2.68848 6 -6 6h-20
c-3.31152 0 -6 -2.68848 -6 -6v-26h-26c-3.31152 0 -6 -2.68848 -6 -6v-20c0 -3.31152 2.68848 -6 6 -6h26v-26c0 -3.31152 2.68848 -6 6 -6h20c3.31152 0 6 2.68848 6 6zM272 44v40c0 6.62402 -5.37598 12 -12 12h-40c-6.62402 0 -12 -5.37598 -12 -12v-40
c0 -6.62402 5.37598 -12 12 -12h40c6.62402 0 12 5.37598 12 12zM272 172v40c0 6.62402 -5.37598 12 -12 12h-40c-6.62402 0 -12 -5.37598 -12 -12v-40c0 -6.62402 5.37598 -12 12 -12h40c6.62402 0 12 5.37598 12 12z" />
<glyph glyph-name="hotdog" unicode="&#xf80f;"
d="M488.56 424.56c12.9297 -12.9326 23.4238 -38.2715 23.4238 -56.5596s-10.4941 -43.627 -23.4238 -56.5596l-352 -352c-13.0205 -13.4824 -38.7998 -24.4238 -57.543 -24.4238c-44.1592 0 -80 35.8408 -80 80c0 18.7432 10.9414 44.5225 24.4238 57.543l352 352
c12.9326 12.9297 38.2715 23.4238 56.5596 23.4238s43.627 -10.4941 56.5596 -23.4238zM438.63 329.37c2.58691 2.58691 4.68652 7.65625 4.68652 11.3145c0 8.83301 -7.16797 16.002 -16.001 16.002c-3.65918 0 -8.72852 -2.09961 -11.3154 -4.68652
@@ -4690,11 +4715,224 @@ s-248 111 -248 248s111 248 248 248zM256 64c70.6562 0 128 57.3438 128 128s-57.343
d="M416 240c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM624 128c8.83203 0 16 -7.16797 16 -16v-32c0 -8.83203 -7.16797 -16 -16 -16h-336c0 -52.9922 -43.0078 -96 -96 -96s-96 43.0078 -96 96h-32
c-35.3281 0 -64 28.6719 -64 64v256c0 35.3281 28.6719 64 64 64h352c88.3203 0 160 -71.6797 160 -160v-160h48zM192 16c26.4688 0.0273438 47.9727 21.5312 48 48c0 26.4961 -21.5039 48 -48 48s-48 -21.5039 -48 -48s21.5039 -48 48 -48zM256 256v64
c0 17.6641 -14.3359 32 -32 32h-128c-17.6641 0 -32 -14.3359 -32 -32v-64c0 -17.6641 14.3359 -32 32 -32h128c17.6641 0 32 14.3359 32 32zM448 128v192c0 17.6641 -14.3359 32 -32 32h-64c-17.6641 0 -32 -14.3359 -32 -32v-192h128z" />
<glyph glyph-name="faucet" unicode="&#xf905;"
d="M352 192c88.3203 0 160 -71.6797 160 -160c0 -17.6641 -14.3359 -32 -32 -32h-64c-17.6641 0 -32 14.3359 -32 32s-14.3359 32 -32 32h-12.79c-20.5898 -37.7305 -64.21 -64 -115.21 -64s-94.6201 26.2695 -115.21 64h-92.79c-8.83203 0 -16 7.16797 -16 16v96
c0 8.83203 7.16797 16 16 16h118.61c15.71 13.4004 35.46 23 57.3896 28v47.5596l32 3.38086l32 -3.38086v-47.5596c21.9297 -4.92969 41.6797 -14.5596 57.3896 -28h38.6104zM81.5898 288.09c-9.41992 -1 -17.5898 6.81055 -17.5898 16.7998v30.2207
c0 9.98926 8.16992 17.7998 17.5898 16.8096l110.41 -11.6602v27.7402c0 8.83203 7.16797 16 16 16h32c8.83203 0 16 -7.16797 16 -16v-27.7402l110.41 11.6602c9.41992 0.990234 17.5898 -6.80957 17.5898 -16.8096v-30.2207
c0 -9.98926 -8.16992 -17.7998 -17.5898 -16.7998l-142.41 15z" />
<glyph glyph-name="trailer" unicode="&#xf941;" horiz-adv-x="640"
d="M624 128c8.83203 0 16 -7.16797 16 -16v-32c0 -8.83203 -7.16797 -16 -16 -16h-337.61c-7.83008 54.21 -54 96 -110.39 96s-102.56 -41.79 -110.39 -96h-49.6104c-8.83203 0 -16 7.16797 -16 16v288c0 8.83203 7.16797 16 16 16h512c8.83203 0 16 -7.16797 16 -16v-240
h80zM96 204.32v107.68c0 4.41602 -3.58398 8 -8 8h-16c-4.41602 0 -8 -3.58398 -8 -8v-128.39c8.20996 6.67578 22.5469 15.9541 32 20.71zM192 222.86v89.1396c0 4.41602 -3.58398 8 -8 8h-16c-4.41602 0 -8 -3.58398 -8 -8v-89.1396
c5.30957 0.489258 10.5703 1.13965 16 1.13965s10.6904 -0.650391 16 -1.13965zM288 183.61v128.39c0 4.41602 -3.58398 8 -8 8h-16c-4.41602 0 -8 -3.58398 -8 -8v-107.68c9.45312 -4.75586 23.79 -14.0342 32 -20.71zM384 128v184c0 4.41602 -3.58398 8 -8 8h-16
c-4.41602 0 -8 -3.58398 -8 -8v-184h32zM480 128v184c0 4.41602 -3.58398 8 -8 8h-16c-4.41602 0 -8 -3.58398 -8 -8v-184h32zM176 128c44.1602 0 80 -35.8398 80 -80s-35.8398 -80 -80 -80s-80 35.8398 -80 80s35.8398 80 80 80zM176 16c17.6641 0 32 14.3359 32 32
s-14.3359 32 -32 32s-32 -14.3359 -32 -32s14.3359 -32 32 -32z" />
<glyph glyph-name="box-tissue" unicode="&#xf95b;"
d="M383.88 160.18h-256l-64 288h141.4c27.9277 -0.00195312 57.7646 -21.5059 66.5996 -48c8.83105 -26.4932 38.6641 -47.9971 66.5898 -48h109.41zM-0.120117 -31.8203v64h512v-64c0 -17.6641 -14.3359 -32 -32 -32h-448c-17.6641 0 -32 14.3359 -32 32zM479.88 224.18
c17.6582 -0.00488281 31.9902 -14.3408 31.9902 -32v0v-128h-512v128c0 17.6641 14.3359 32 32 32h49l14.2197 -64h-15.21c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16h352c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16h-14.2695l21.3301 64h40.9395z" />
<glyph glyph-name="hand-holding-medical" unicode="&#xf95c;" horiz-adv-x="576"
d="M159.88 272.18c-8.83203 0 -16 7.16797 -16 16v64c0 8.83203 7.16797 16 16 16h64v64c0 8.83203 7.16797 16 16 16h64c8.83203 0 16 -7.16797 16 -16v-64h64c8.83203 0 16 -7.16797 16 -16v-64c0 -8.83203 -7.16797 -16 -16 -16h-64v-64c0 -8.83203 -7.16797 -16 -16 -16
h-64c-8.83203 0 -16 7.16797 -16 16v64h-64zM568.07 111.87c4.28906 -5.83496 7.77051 -16.4492 7.77051 -23.6914c0 -11.1436 -7.27637 -25.5596 -16.2412 -32.1787l-135.029 -99.5703c-15.2061 -11.1436 -42.8477 -20.2246 -61.7002 -20.2695h-347
c-8.77246 0.0595703 -15.9404 7.22754 -16 16v96c0.0595703 8.77246 7.22754 15.9404 16 16h55.3604l46.5 37.7402c17.8828 14.4893 51.0781 26.25 74.0957 26.25h0.0234375h160h0.00488281c17.6973 0 32.0596 -14.3633 32.0596 -32.0605
c0 -1.47852 -0.198242 -3.86133 -0.444336 -5.32031c-2.62012 -15.7393 -17.3701 -26.6094 -33.3701 -26.6094h-78.2393c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16h120.609l119.67 88.1797c5.8418 4.3252 16.4814 7.83496 23.749 7.83496
c11.1621 0 25.5791 -7.30469 32.1816 -16.3047z" />
<glyph glyph-name="hand-sparkles" unicode="&#xf95d;" horiz-adv-x="640"
d="M106.66 277.36l-20.7402 -49.6201c-1.01074 -2.04297 -3.68066 -3.7002 -5.95996 -3.7002s-4.94922 1.65723 -5.95996 3.7002l-20.6602 49.6602h-0.0703125l-49.5898 20.5996c-1.92383 1.09863 -3.57227 3.78711 -3.67969 6v0c0.106445 2.21973 1.76367 4.9082 3.7002 6
l49.6299 20.6904h0.0498047l20.7002 49.6299c1.01465 2.03516 3.68555 3.6875 5.95996 3.6875s4.94434 -1.65234 5.95996 -3.6875l20.6602 -49.6406h0.0703125l49.5693 -20.6699c1.92871 -1.0957 3.57715 -3.78418 3.68066 -6v0
c-0.108398 -2.21289 -1.75684 -4.90039 -3.68066 -6l-49.5498 -20.6494h-0.0898438zM471.38 -19.4102l37.4902 -15.6299l0.0703125 -0.169922c-7.59082 -17.0596 -24 -28.79 -43.2402 -28.79h-197.61c-13.4805 0.0224609 -30.8584 8.88867 -38.79 19.79l-125.6 172.61
c-4.22852 5.81055 -7.66016 16.3584 -7.66016 23.5449c0 22.0879 17.9268 40.0146 40.0146 40.0146c11.2441 0 25.7393 -7.37891 32.3555 -16.4697l23.5898 -32.4902v241c0 17.6641 14.3359 32 32 32s32 -14.3359 32 -32v-152c0 -4.41602 3.58398 -8 8 -8h16
c4.41602 0 8 3.58398 8 8v184c0 17.6641 14.3359 32 32 32s32 -14.3359 32 -32v-184c0 -4.41602 3.58398 -8 8 -8h16c4.41602 0 8 3.58398 8 8v152c0 17.6641 14.3359 32 32 32s32 -14.3359 32 -32v-152c0 -4.41602 3.58398 -8 8 -8h16c4.41602 0 8 3.58398 8 8v72
c0 17.6641 14.3359 32 32 32s32 -14.3359 32 -32v-176.03c-0.0195312 -1.30957 -0.269531 -2.66992 -0.269531 -4c-7.77051 -3.70996 -14.5 -9.59961 -18.3506 -17.3398l-0.469727 -0.950195l-0.410156 -1l-15.6299 -37.4795l-37.4902 -15.6299l-1 -0.430664l-1 -0.489258
c-11.7803 -5.90527 -21.3408 -21.3926 -21.3408 -34.5703s9.56055 -28.665 21.3408 -34.5703l1 -0.5zM349.79 108.48c1.22266 0.609375 2.21582 2.21289 2.21582 3.5791c0 1.36719 -0.993164 2.9707 -2.21582 3.58008l-29.79 12.4199l-12.4297 29.7803
c-0.611328 1.21777 -2.21289 2.20605 -3.5752 2.20605s-2.96387 -0.988281 -3.5752 -2.20605l-12.4199 -29.7803l-29.79 -12.4199c-1.22266 -0.609375 -2.21582 -2.21289 -2.21582 -3.58008c0 -1.36621 0.993164 -2.96973 2.21582 -3.5791l29.79 -12.4102l12.4297 -29.7803
c0.611328 -1.21777 2.21289 -2.20605 3.5752 -2.20605s2.96387 0.988281 3.5752 2.20605l12.4199 29.7803zM640 16.0898l-0.0703125 -0.0703125v0c-0.117188 -2.19727 -1.76562 -4.86328 -3.67969 -5.94922l-49.5498 -20.6602h-0.0898438v0l-20.6904 -49.6201
c-1.01074 -2.04297 -3.68066 -3.7002 -5.95996 -3.7002s-4.94922 1.65723 -5.95996 3.7002l-20.6602 49.5898h-0.0703125l-49.5693 20.6699c-1.91406 1.08691 -3.5625 3.75293 -3.68066 5.9502v0c0.101562 2.2168 1.75 4.90527 3.68066 6l49.6299 20.7402h0.0498047
l20.7002 49.6299c1.01465 2.03516 3.68555 3.6875 5.95996 3.6875s4.94434 -1.65234 5.95996 -3.6875l20.6797 -49.6104h0.0703125l49.5703 -20.6699c1.92969 -1.09473 3.57812 -3.7832 3.67969 -6z" />
<glyph glyph-name="hands-wash" unicode="&#xf95e;" horiz-adv-x="576"
d="M496 224c-26.4961 0 -48 21.5039 -48 48s21.5039 48 48 48s48 -21.5039 48 -48s-21.5039 -48 -48 -48zM311.47 269.55l-16.0801 -4.96973l20.9004 66.1699c3.5 11.0703 14.1797 18.8604 25.71 17.5098c11.8564 -1.25195 21.4785 -11.9453 21.4785 -23.8672
c0 -2.05566 -0.505859 -5.31348 -1.12891 -7.27246l-15.3496 -48.6104c-5.0752 1.88184 -13.5869 3.44531 -19 3.49023h-0.0322266c-4.6543 0 -12.0449 -1.09766 -16.498 -2.4502zM93.6504 61.6699c-33.4609 19.3945 -61.0801 66.5195 -61.6504 105.19v112.729
c0.179688 13.3203 11.6699 23.9102 24.9004 23.8604c13.1709 -0.0771484 23.8604 -10.8281 23.8604 -24c0 -0.0410156 0 -0.108398 -0.000976562 -0.150391l2.06055 -50.0498l60 189.85c3.5 11.0703 14.1797 18.9004 25.71 17.46
c11.8398 -1.26465 21.4492 -11.9561 21.4492 -23.8633c0 -2.04785 -0.500977 -5.29395 -1.11914 -7.24609l-38.5605 -122c-0.205078 -0.649414 -0.371094 -1.72949 -0.371094 -2.41016c0 -4.41699 3.58398 -8.00195 8.00098 -8.00195
c3.2373 0 6.65527 2.50586 7.62988 5.5918l47.9307 151.71c3.50977 11.0605 14.1797 18.8506 25.71 17.5098c11.8398 -1.26465 21.4502 -11.9561 21.4502 -23.8633c0 -2.04785 -0.501953 -5.29395 -1.12012 -7.24609l-43.3701 -137.79
c-0.206055 -0.650391 -0.373047 -1.73242 -0.373047 -2.41504c0 -4.41797 3.58496 -8.00293 8.00293 -8.00293c3.23535 0 6.65332 2.50293 7.62988 5.58789l33.4502 106.42c3.5 11.0703 14.1895 18.8604 25.7197 17.5195
c11.8408 -1.26465 21.4502 -11.9561 21.4502 -23.8633c0 -2.04785 -0.501953 -5.29395 -1.12012 -7.24609l-34.1602 -108.12l-73.7002 -22.7598c-59.0469 -19.5098 -107.01 -85.8135 -107.06 -148v-25.6904c-0.80957 -0.169922 -1.5498 -0.519531 -2.34961 -0.709961z
M519.1 112c11.6104 0 22.25 -7.83984 24.4404 -19.2402c0.262695 -1.30078 0.476562 -3.43262 0.476562 -4.75977c0 -13.248 -10.752 -24 -24 -24h-0.0166016h-160c-4.41602 0 -8 -3.58398 -8 -8s3.58398 -8 8 -8h127.1c11.6104 0 22.25 -7.83984 24.4404 -19.2402
c0.262695 -1.30078 0.476562 -3.43262 0.476562 -4.75977c0 -13.248 -10.752 -24 -24 -24h-0.0166016h-128c-4.41602 0 -8 -3.58398 -8 -8s3.58398 -8 8 -8h95.0996c11.6104 0 22.25 -7.83984 24.4404 -19.2402c0.262695 -1.30078 0.476562 -3.43262 0.476562 -4.75977
c0 -13.248 -10.752 -24 -24 -24h-0.0166016h-208c-18.4902 0.0703125 -46.2656 8.00879 -62 17.7197c3.32715 8.06641 6.02734 21.6953 6.02734 30.4209c0 36.0527 -28.6846 71.0908 -64.0273 78.209v25.6504v0.00683594c0 49.501 38.165 102.223 85.1904 117.684
l107.72 33.25c1.91211 0.59082 5.08887 1.07031 7.08984 1.07031c13.2539 0 24.0107 -10.7568 24.0107 -24.0107c0 -9.77637 -7.58008 -20.0537 -16.9209 -22.9404l-47.0898 -17.0596h199.1c11.6104 0 22.25 -7.83984 24.4404 -19.2402
c0.262695 -1.30078 0.476562 -3.43262 0.476562 -4.75977c0 -13.248 -10.752 -24 -24 -24h-0.0166016h-128c-4.41602 0 -8 -3.58398 -8 -8s3.58398 -8 8 -8h159.1zM416 384c-17.6641 0 -32 14.3359 -32 32s14.3359 32 32 32s32 -14.3359 32 -32s-14.3359 -32 -32 -32z
M112 32c26.4961 0 48 -21.5039 48 -48s-21.5039 -48 -48 -48s-48 21.5039 -48 48s21.5039 48 48 48z" />
<glyph glyph-name="handshake-alt-slash" unicode="&#xf95f;" horiz-adv-x="640"
d="M358.59 252.4l26.1104 23.8896c2.86914 2.62598 5.19824 7.91504 5.19824 11.8047c0 8.83398 -7.16992 16.0039 -16.0039 16.0039c-3.43164 0 -8.27246 -1.88086 -10.8047 -4.19824l-27 -24.7002l-32.6895 -29.9199l330.43 -255.38
c3.41016 -2.65234 6.17773 -8.31055 6.17773 -12.6309c0 -3.0293 -1.50879 -7.42773 -3.36816 -9.81934l-19.6396 -25.2705c-2.65234 -3.41211 -8.31152 -6.18262 -12.6338 -6.18262c-3.03125 0 -7.43359 1.51172 -9.82617 3.37305l-588.35 454.72
c-3.41016 2.65234 -6.17773 8.31055 -6.17773 12.6309c0 3.02832 1.50781 7.42773 3.36719 9.81934l19.6201 25.2695c2.65234 3.41602 8.31348 6.1875 12.6377 6.1875c3.03418 0 7.43848 -1.5127 9.83203 -3.37695l116.891 -90.3301l20.3398 20.2998
c5.16211 5.17969 15.2871 9.39551 22.5996 9.41016h83.79l-75.5996 -69.2402l25.6895 -19.8496l88.1201 80.6797c5.0625 4.63965 14.7432 8.40723 21.6104 8.41016h85.8896c7.31641 -0.0126953 17.4453 -4.22852 22.6104 -9.41016l54.5898 -54.5898h112v0
c8.78223 0 15.9502 -7.12793 16 -15.9102v-191.8c-0.0273438 -8.80469 -7.19531 -15.9727 -16 -16h-97.5898c-2.26465 12.7275 -12.2148 29.7109 -22.21 37.9102zM16 320h7.55957l382.44 -295.59l-8.7998 -10.8203c-6.15723 -7.57617 -19.0762 -13.7246 -28.8389 -13.7246
c-7.29004 0 -17.7959 3.73438 -23.4512 8.33496l-17.9102 15.5l-0.200195 -0.200195c-10.6025 -13.0381 -32.8477 -23.6201 -49.6533 -23.6201c-12.5381 0 -30.6133 6.41602 -40.3467 14.3203l-90.5 81.8896h-130.3c-8.83203 0 -16 7.16797 -16 16v191.91
c0.0273438 8.80469 7.19531 15.9727 16 16z" />
<glyph glyph-name="handshake-slash" unicode="&#xf960;" horiz-adv-x="640"
d="M0 319.79h23.8301l72.1699 -55.79v-168c0 -17.6641 -14.3359 -32 -32 -32h-64v255.79zM48 127.9c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16s16 7.16797 16 16s-7.16797 16 -16 16zM128 96.0898v143.19l278 -214.87l-8.7998 -10.8203
c-6.15723 -7.57617 -19.0762 -13.7246 -28.8389 -13.7246c-7.29004 0 -17.7959 3.73438 -23.4512 8.33496l-17.9102 15.5l-0.200195 -0.200195c-10.6025 -13.0381 -32.8477 -23.6201 -49.6533 -23.6201c-12.5381 0 -30.6133 6.41602 -40.3467 14.3203l-90.5 81.8896
h-18.2998zM544 319.79h96v-255.89h-64c-17.6641 0 -32 14.3359 -32 32v223.89zM592 95.9004c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16s-16 -7.16797 -16 -16s7.16797 -16 16 -16zM303.33 245.33l330.5 -255.43c3.41309 -2.65234 6.18359 -8.3125 6.18359 -12.6357
c0 -3.02734 -1.50684 -7.42383 -3.36328 -9.81445l-19.6504 -25.2705c-2.65234 -3.41504 -8.31348 -6.1875 -12.6377 -6.1875c-3.03418 0 -7.43848 1.51367 -9.83203 3.37793l-588.34 454.72c-3.41016 2.65234 -6.17773 8.31055 -6.17773 12.6309
c0 3.02832 1.50781 7.42773 3.36719 9.81934l19.6201 25.2695c2.65234 3.41602 8.31348 6.1875 12.6377 6.1875c3.03418 0 7.43848 -1.5127 9.83203 -3.37695l116.891 -90.3398l20.3398 20.3096c5.16211 5.17969 15.2871 9.39551 22.5996 9.41016h83.79l-75.5996 -69.2402
l25.6396 -19.8096l88.0703 80.6396c5.05566 4.64258 14.7305 8.41016 21.5947 8.41016h0.00488281h85.9004h0.0351562c7.31055 0 17.4199 -4.21582 22.5645 -9.41016l54.6104 -54.5898v-193.5c-2.02246 2.29102 -5.56641 5.74023 -7.91016 7.7002l-145.59 118.2
l26.0898 23.8896c2.73828 2.61035 4.95996 7.79883 4.95996 11.5811c0 8.83203 -7.16797 16 -16 16c-3.32422 0 -8.05078 -1.7793 -10.5498 -3.9707z" />
<glyph glyph-name="head-side-cough" unicode="&#xf961;" horiz-adv-x="640"
d="M616 144c-13.248 0 -24 10.752 -24 24s10.752 24 24 24s24 -10.752 24 -24s-10.752 -24 -24 -24zM552 32c13.248 0 24 -10.752 24 -24s-10.752 -24 -24 -24s-24 10.752 -24 24s10.752 24 24 24zM488 88c13.248 0 24 -10.752 24 -24s-10.752 -24 -24 -24
s-24 10.752 -24 24s10.752 24 24 24zM616 -16c13.248 0 24 -10.752 24 -24s-10.752 -24 -24 -24s-24 10.752 -24 24s10.752 24 24 24zM616 88c13.248 0 24 -10.752 24 -24s-10.752 -24 -24 -24s-24 10.752 -24 24s10.752 24 24 24zM552 128c13.248 0 24 -10.752 24 -24
s-10.752 -24 -24 -24s-24 10.752 -24 24s10.752 24 24 24zM477.22 173c1.52344 -3.42676 2.75977 -9.25 2.75977 -13c0 -17.6523 -14.3271 -31.9883 -31.9795 -32h-32v-32h-96c-17.6641 0 -32 -14.3359 -32 -32s14.3359 -32 32 -32h96c0 -35.3281 -28.6719 -64 -64 -64h-64
v-32h-224v177.12c-39.25 35.2598 -64 86.1299 -64 142.88c0 106 86 192 192 192h42.0996c59.5439 -0.0390625 135.704 -39.5752 170 -88.25c24.6201 -35 52.1201 -139.63 73.1201 -186.75zM288 224c17.626 0.0380859 31.9619 14.374 32 32c0 17.6641 -14.3359 32 -32 32
s-32 -14.3359 -32 -32s14.3359 -32 32 -32z" />
<glyph glyph-name="head-side-cough-slash" unicode="&#xf962;" horiz-adv-x="640"
d="M454.11 128.79l179.72 -138.89c3.41016 -2.65234 6.17773 -8.31055 6.17773 -12.6309c0 -3.0293 -1.50879 -7.42773 -3.36816 -9.81934l-19.6396 -25.2705c-2.65234 -3.41504 -8.31348 -6.1875 -12.6377 -6.1875c-3.03418 0 -7.43848 1.51367 -9.83203 3.37793
l-588.351 454.72c-3.41016 2.65234 -6.17773 8.31055 -6.17773 12.6309c0 3.02832 1.50879 7.42773 3.36816 9.81934l19.6299 25.2695c2.65234 3.41309 8.31152 6.18262 12.6338 6.18262c3.03125 0 7.43359 -1.51074 9.82617 -3.37207l38.7197 -29.9199
c26.8672 18.3818 75.0928 33.2998 107.646 33.2998h0.173828h42.0996c59.5439 -0.0390625 135.704 -39.5752 170 -88.25c24.6201 -35 52.1201 -139.63 73.1201 -186.75c8.51074 -19.21 -3.5498 -40.4004 -23.1094 -44.21zM313.39 237.55
c3.85059 5.28027 6.61035 11.4502 6.58008 18.4502c-0.0322266 17.6309 -14.3691 31.9668 -32 32c-9.92969 0 -18.4795 -4.86035 -24.3594 -12zM616 144c-13.248 0 -24 10.752 -24 24s10.752 24 24 24s24 -10.752 24 -24s-10.752 -24 -24 -24zM552 80
c-13.248 0 -24 10.752 -24 24s10.752 24 24 24s24 -10.752 24 -24s-10.752 -24 -24 -24zM288 64c0 -17.6641 14.3359 -32 32 -32h96c0 -35.3281 -28.6719 -64 -64 -64h-64v-32h-224v177.12c-39.25 35.2598 -64 86.1299 -64 142.88
c0.0126953 25.2188 9.2998 63.9307 20.7305 86.4102l318.81 -246.41h-19.54c-17.6641 0 -32 -14.3359 -32 -32zM616 88c13.248 0 24 -10.752 24 -24s-10.752 -24 -24 -24s-24 10.752 -24 24s10.752 24 24 24z" />
<glyph glyph-name="head-side-mask" unicode="&#xf963;"
d="M0.150391 263.58c0.364258 7.85059 1.94043 20.4707 3.51953 28.1699l220.33 -160.26v-195.49h-160v177.12c-41 36.8203 -66.1699 90.6699 -63.8496 150.46zM509.22 173c1.40625 -3.27148 2.54688 -8.81543 2.54688 -12.376
c0 -0.171875 -0.00292969 -0.452148 -0.00683594 -0.624023h-272.55l-225.96 164.35c29.2305 73.0801 103.75 123.65 186.75 123.65h66.1104c59.541 -0.0390625 135.697 -39.5752 169.989 -88.25c24.6201 -35 52.1201 -139.63 73.1201 -186.75zM320 224
c17.626 0.0380859 31.9619 14.374 32 32c0 17.6641 -14.3359 32 -32 32s-32 -14.3359 -32 -32s14.3359 -32 32 -32zM336 80c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16h149.34l-10.6699 -32h-138.67c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16h128
l-1.41016 -4.24023c-8.05176 -24.1533 -35.25 -43.7578 -60.71 -43.7598h-145.88v192h256l-16 -48h-160z" />
<glyph glyph-name="head-side-virus" unicode="&#xf964;"
d="M272 208c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM208 272c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM509.2 173c1.52344 -3.42676 2.75977 -9.25 2.75977 -13
c0 -17.6416 -14.3184 -31.9775 -31.96 -32h-32v-64c0 -35.3281 -28.6719 -64 -64 -64h-64v-64h-256v177.19c-35.3281 31.4834 -64 95.4414 -64 142.763v0.046875c0 106 86 192 192 192h74.0898h0.0214844c59.5684 0 135.736 -39.5361 170.019 -88.25
c24.6396 -35.0195 52.1396 -139.63 73.0703 -186.75zM368 208c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16h-12.1201c-28.5098 0 -42.79 34.4697 -22.6299 54.6299l8.58008 8.57031c2.58691 2.58691 4.68652 7.65625 4.68652 11.3145
c0 8.83301 -7.16895 16.002 -16.002 16.002c-3.6582 0 -8.72754 -2.09961 -11.3145 -4.68652l-8.57031 -8.58008c-20.1602 -20.1602 -54.6299 -5.87988 -54.6299 22.6299v12.1201c0 8.83203 -7.16797 16 -16 16s-16 -7.16797 -16 -16v-12.1201
c0 -28.5098 -34.4697 -42.79 -54.6299 -22.6299l-8.57031 8.58008c-2.58691 2.58691 -7.65625 4.68652 -11.3145 4.68652c-8.83301 0 -16.002 -7.16895 -16.002 -16.002c0 -3.6582 2.09961 -8.72754 4.68652 -11.3145l8.58008 -8.57031
c20.1602 -20.1602 5.87988 -54.6299 -22.6299 -54.6299h-12.1201c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16h12.1201c28.5098 0 42.79 -34.4697 22.6299 -54.6299l-8.58008 -8.57031c-2.58691 -2.58691 -4.68652 -7.65625 -4.68652 -11.3145
c0 -8.83301 7.16895 -16.002 16.002 -16.002c3.6582 0 8.72754 2.09961 11.3145 4.68652l8.57031 8.58008c20.1602 20.1602 54.6299 5.87988 54.6299 -22.6299v-12.1201c0 -8.83203 7.16797 -16 16 -16s16 7.16797 16 16v12.1201c0 28.5098 34.4697 42.79 54.6299 22.6299
l8.57031 -8.58008c2.58691 -2.58691 7.65625 -4.68652 11.3145 -4.68652c8.83301 0 16.002 7.16895 16.002 16.002c0 3.6582 -2.09961 8.72754 -4.68652 11.3145l-8.58008 8.57031c-20.1602 20.1602 -5.87988 54.6299 22.6299 54.6299h12.1201z" />
<glyph glyph-name="house-user" unicode="&#xf965;" horiz-adv-x="576"
d="M570.69 211.73c2.54004 -2.81152 4.91895 -8.15137 5.30957 -11.9209c-0.319336 -3.25977 -2.15234 -8.04883 -4.08984 -10.6895l-21.4102 -23.8105c-2.7959 -2.53809 -8.11426 -4.91699 -11.8701 -5.30957c-3.2666 0.334961 -8.06934 2.18066 -10.7197 4.12012
l-15.9102 14v-210.12c0 -17.6641 -14.3359 -32 -32 -32h-383.91c-17.6641 0 -32 14.3359 -32 32v210.11l-15.8994 -14c-2.63965 -1.94336 -7.42871 -3.78418 -10.6904 -4.11035c-3.78906 0.381836 -9.16504 2.75586 -12 5.2998l-21.4102 23.79
c-2.08398 2.59082 -3.91602 7.38965 -4.08984 10.71c0.200195 3.83789 2.55664 9.16895 5.25977 11.9004l256 226c6.28027 5.68945 18.21 10.2998 26.7402 10.2998s20.5 -4.61035 26.7803 -10.2998l101.22 -89.3701v51.6699c0 8.83203 7.16797 16 16 16h64
c8.83203 0 16 -7.16797 16 -16v-136.44zM288 272c-35.3281 0 -64 -28.6719 -64 -64s28.6719 -64 64 -64s64 28.6719 64 64s-28.6719 64 -64 64zM400 0c8.83203 0 16 7.16797 16 16c0 52.9922 -43.0078 96 -96 96h-64c-52.9922 0 -96 -43.0078 -96 -96
c0 -8.83203 7.16797 -16 16 -16h224z" />
<glyph glyph-name="laptop-house" unicode="&#xf966;" horiz-adv-x="640"
d="M272 160v-128h-176c-17.6641 0 -32 14.3359 -32 32v164.12l-21.6602 -19.1201c-2.27344 -1.77637 -6.45801 -3.33594 -9.33984 -3.48047c-3.45117 0.183594 -8.22754 2.3252 -10.6602 4.78027l-18.79 21.3105c-1.8125 2.27637 -3.40332 6.4834 -3.5498 9.38965
c0.194336 3.42871 2.33594 8.16797 4.78027 10.5801l211.8 187.5c5.54004 4.91992 16.0703 8.91992 23.4697 8.91992c7.40039 0 17.9502 -4 23.4502 -8.91992l88.5 -78.3799v39.2998c0 8.83203 7.16797 16 16 16h32c8.83203 0 16 -7.16797 16 -16v-96l59.25 -52.3896
c2.42773 -2.42871 4.55566 -7.18164 4.75 -10.6104c-0.15332 -2.93457 -1.77051 -7.17773 -3.61035 -9.46973l-6.64941 -7.53027h-136.94c-17.7998 0 -33.6895 -8.24023 -44.7998 -21.1201v37.1201c0 8.83203 -7.16797 16 -16 16h-64c-8.83203 0 -16 -7.16797 -16 -16v-64
c0 -8.83203 7.16797 -16 16 -16h64zM629.33 0c5.88965 0 10.6699 -4.78027 10.6699 -10.6699v-10.6602c-0.0820312 -23.4336 -19.167 -42.5498 -42.5996 -42.6699h-298.801c-23.4326 0.120117 -42.5176 19.2363 -42.5996 42.6699v10.6602
c0 5.88965 4.78027 10.6699 10.6699 10.6699v0h37.3301v160c0 17.6699 12.8896 32 28.7998 32h230.4c15.9102 0 28.7998 -14.3301 28.7998 -32v-160h37.3301zM544 0v144h-192v-144h192z" />
<glyph glyph-name="lungs-virus" unicode="&#xf967;" horiz-adv-x="640"
d="M344 297.32c-6.11035 3.6875 -16.8623 6.68066 -24 6.68066s-17.8896 -2.99316 -24 -6.68066v134.68c0 8.83203 7.16797 16 16 16h16c8.83203 0 16 -7.16797 16 -16v-134.68zM195.54 3.54004c7.55664 -7.76367 22.4814 -14.0645 33.3154 -14.0645
c2.33594 0 6.09668 0.342773 8.39453 0.764648c-11.2559 -14.4775 -34.7754 -30.0459 -52.5 -34.75l-59.5 -15.8701c-62.75 -16.8799 -125.25 27.3799 -125.25 88.6299v0.241211c0 8.25 1.73828 21.4121 3.87988 29.3789c18.2109 68.1455 63.9072 171.634 102 231
c22.1201 34.6299 36.1201 63.1299 80.1201 63.1299c38.6201 0 70 -29.3799 70 -65.75v-27.6797c-6.68359 4.46582 -18.6309 8.08984 -26.6689 8.08984c-26.4961 0 -48 -21.5039 -48 -48c0 -11.043 6.36523 -26.3154 14.209 -34.0898l8.58008 -8.57031h-12.1201
c-26.4961 0 -48 -21.5039 -48 -48s21.5039 -48 48 -48h12.1201l-8.58008 -8.58008c-7.74609 -7.76562 -14.0332 -22.9707 -14.0332 -33.9395c0 -10.9697 6.28711 -26.1748 14.0332 -33.9404zM421.83 26.1699c-2.58691 -2.58789 -7.65625 -4.6875 -11.3154 -4.6875
c-3.6582 0 -8.72754 2.09961 -11.3145 4.6875l-8.57031 8.57031c-20.1602 20.1602 -54.6299 5.87988 -54.6299 -22.6201v-12.1201c0 -8.83203 -7.16797 -16 -16 -16s-16 7.16797 -16 16v12.1201c0 28.5 -34.4697 42.7803 -54.6299 22.6201l-8.57031 -8.57031
c-2.60156 -2.67969 -7.74512 -4.85547 -11.4805 -4.85547c-8.83203 0 -16 7.16797 -16 16c0 3.7334 2.17285 8.87402 4.85059 11.4756l8.58008 8.58008c20.1602 20.1602 5.87988 54.6299 -22.6299 54.6299h-12.1201c-8.83203 0 -16 7.16797 -16 16s7.16797 16 16 16h12.1201
c28.5098 0 42.79 34.4697 22.6299 54.6201l-8.58008 8.58008c-2.58691 2.58691 -4.68652 7.65625 -4.68652 11.3145c0 8.83301 7.16895 16.002 16.002 16.002c3.6582 0 8.72754 -2.09961 11.3145 -4.68652l8.57031 -8.58008
c20.1602 -20.1602 54.6299 -5.87988 54.6299 22.6299v12.1201c0 8.83203 7.16797 16 16 16s16 -7.16797 16 -16v-12.1201c0 -28.5098 34.4697 -42.79 54.6299 -22.6299l8.57031 8.58008c2.58691 2.58691 7.65625 4.68652 11.3145 4.68652
c8.83301 0 16.002 -7.16895 16.002 -16.002c0 -3.6582 -2.09961 -8.72754 -4.68652 -11.3145l-8.58008 -8.58008c-20.1602 -20.1504 -5.87988 -54.6201 22.6299 -54.6201h12.1201c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16h-12.1201
c-28.5098 0 -42.79 -34.4697 -22.6299 -54.6299l8.58008 -8.58008c2.58496 -2.58691 4.68262 -7.65332 4.68262 -11.3096c0 -3.65723 -2.09766 -8.72363 -4.68262 -11.3105zM288 144c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16s-16 -7.16797 -16 -16
s7.16797 -16 16 -16zM352 80c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16s-16 -7.16797 -16 -16s7.16797 -16 16 -16zM636.12 57.8701c2.1416 -7.9668 3.87988 -21.1289 3.87988 -29.3789v-0.241211c0 -61.25 -62.5 -105.51 -125.25 -88.6299l-59.5 15.8701
c-17.7246 4.7041 -41.2441 20.2725 -52.5 34.75c2.32812 -0.421875 6.1377 -0.764648 8.50391 -0.764648c26.3311 0 47.7002 21.3701 47.7002 47.7002c0 11.1445 -6.49316 26.4863 -14.4941 34.2441l-8.58008 8.58008h12.1201c26.4961 0 48 21.5039 48 48
s-21.5039 48 -48 48h-12.1201l8.58008 8.53027c7.84375 7.77441 14.209 23.0469 14.209 34.0898c0 26.4961 -21.5039 48 -48 48c-8.03809 0 -19.9854 -3.62402 -26.6689 -8.08984v27.7197c0 36.3701 31.3799 65.75 70 65.75c44 0 58 -28.5 80.1201 -63.1299
c38.0928 -59.3662 83.7891 -162.854 102 -231z" />
<glyph glyph-name="people-arrows" unicode="&#xf968;" horiz-adv-x="576"
d="M96 320c-35.3281 0 -64 28.6719 -64 64s28.6719 64 64 64s64 -28.6719 64 -64s-28.6719 -64 -64 -64zM96 143.92v-0.118164c0 -10.4102 6.11035 -24.6934 13.6396 -31.8818l50.3604 -47.5303v-96.3896c0 -17.6641 -14.3359 -32 -32 -32h-64
c-17.6641 0 -32 14.3359 -32 32v128c-17.6641 0 -32 14.3359 -32 32v96c0 35.3281 28.6719 64 64 64h64c23.9707 -0.0224609 50.5732 -18.1357 59.3799 -40.4297c-1.83984 -1.26074 -3.95996 -2.02051 -5.61035 -3.57031l-72.1299 -68.0801
c-7.5293 -7.19336 -13.6396 -21.4814 -13.6396 -31.8945v-0.105469zM480 320c-35.3281 0 -64 28.6719 -64 64s28.6719 64 64 64s64 -28.6719 64 -64s-28.6719 -64 -64 -64zM512 288c35.3281 0 64 -28.6719 64 -64v-96c0 -17.6641 -14.3359 -32 -32 -32v-128
c0 -17.6641 -14.3359 -32 -32 -32h-64c-17.6641 0 -32 14.3359 -32 32v96.3799l50.3604 47.5498c7.52539 7.22949 13.6338 21.5654 13.6338 32c0 10.4355 -6.1084 24.7715 -13.6338 32l-72.1201 68.0605c-1.62012 1.58984 -3.78027 2.31934 -5.62012 3.58984
c8.80957 22.291 35.4111 40.3984 59.3799 40.4199h64zM444.4 152.66c1.98633 -2.00195 3.59863 -5.91504 3.59863 -8.73535s-1.6123 -6.7334 -3.59863 -8.73438l-72.1201 -68.0703c-1.91895 -1.83008 -5.62891 -3.31445 -8.28027 -3.31445c-6.62402 0 -12 5.37598 -12 12
v0.0546875v36.1396h-128v-36.1396v-0.0546875c0 -6.62402 -5.37598 -12 -12 -12c-2.65137 0 -6.36133 1.48438 -8.28027 3.31445l-72.1201 68.0703c-1.98633 2.00098 -3.59863 5.91406 -3.59863 8.73438s1.6123 6.7334 3.59863 8.73535l72.1201 68.0703
c1.91895 1.8291 5.62891 3.31348 8.28027 3.31348c6.62402 0 12 -5.37598 12 -12v-0.0439453v-36h128v36v0.0341797c0 6.62402 5.37598 12 12 12c2.65137 0 6.36133 -1.48438 8.28027 -3.31445z" />
<glyph glyph-name="plane-slash" unicode="&#xf969;" horiz-adv-x="640"
d="M32.4805 300.12c-0.21875 0.947266 -0.396484 2.50586 -0.396484 3.47852c0 2.40137 1.01465 6.0127 2.26562 8.06152l324.841 -251.061l-66.6006 -116.54c-2.54297 -4.44824 -8.76562 -8.05957 -13.8896 -8.05957h-65.5
c-8.81543 0.0166016 -15.9697 7.18457 -15.9697 16c0 1.24121 0.277344 3.2168 0.619141 4.41016l49 171.59h-102.85l-43.2002 -57.5898c-2.64746 -3.53613 -8.38184 -6.4082 -12.7998 -6.41016h-40c-8.8291 0.00292969 -15.9951 7.1709 -15.9951 16
c0 1.08398 0.212891 2.81836 0.475586 3.87012l31.5195 108.13zM633.82 -10.0898c3.41602 -2.65234 6.18848 -8.31445 6.18848 -12.6387c0 -3.03027 -1.50879 -7.42969 -3.36914 -9.82129l-19.6396 -25.2598c-2.65234 -3.41699 -8.31445 -6.18945 -12.6387 -6.18945
c-3.03027 0 -7.42969 1.50977 -9.82129 3.36914l-588.36 454.72c-3.41211 2.65234 -6.18262 8.3125 -6.18262 12.6338c0 3.03223 1.51172 7.43359 3.37305 9.82617l19.6299 25.2598c2.65234 3.41309 8.31152 6.18262 12.6338 6.18262
c3.03125 0 7.43359 -1.51074 9.82617 -3.37207l189.3 -146.3l-36.9395 129.29c-0.338867 1.1875 -0.614258 3.1543 -0.614258 4.38965c0 8.8291 7.16504 15.9971 15.9941 16h65.5098c5.12988 0 11.3496 -3.61035 13.9004 -8.05957l105.09 -183.94h114.3
c35.3398 0 96 -28.6602 96 -64s-60.6602 -64 -96 -64h-56.8604z" />
<glyph glyph-name="pump-medical" unicode="&#xf96a;" horiz-adv-x="384"
d="M235.51 288.18c32.2471 -0.00195312 60.7979 -26.0664 63.7305 -58.1797l20.3701 -224c0.145508 -1.59766 0.262695 -4.19629 0.262695 -5.7998c0 -35.3242 -28.6689 -63.9961 -63.9932 -64h-192h-0.00292969c-35.3281 0 -64 28.6719 -64 64
c0 1.60352 0.117188 4.20215 0.262695 5.7998l20.3701 224c2.93262 32.1133 31.4834 58.1777 63.7305 58.1797h151.27zM239.88 114.85v26.6602c0 7.36426 -5.97656 13.3398 -13.3398 13.3398v0h-40v40c0 7.3584 -5.97168 13.3301 -13.3301 13.3301v0h-26.6699
c-7.3584 0 -13.3301 -5.97168 -13.3301 -13.3301v-40h-40c-7.3584 0 -13.3301 -5.97168 -13.3301 -13.3301v-0.00976562v-26.6602c0 -7.35742 5.97168 -13.334 13.3301 -13.3398h40v-40c0 -7.3584 5.97168 -13.3301 13.3301 -13.3301v0h26.6699
c7.3584 0 13.3301 5.97168 13.3301 13.3301v40h40c7.3584 0.00585938 13.334 5.98242 13.3398 13.3398zM379.19 354.12c2.58691 -2.58691 4.6875 -7.65625 4.6875 -11.3154c0 -3.6582 -2.10059 -8.72754 -4.6875 -11.3145l-22.6201 -22.6201
c-2.58691 -2.58789 -7.65625 -4.6875 -11.3154 -4.6875s-8.72754 2.09961 -11.3145 4.6875l-43.3105 43.3096h-66.75v-32h-128v96c0 17.6641 14.3359 32 32 32h64c17.6641 0 32 -14.3359 32 -32h66.75c14.6279 -0.00195312 34.8955 -8.39746 45.2402 -18.7393z" />
<glyph glyph-name="pump-soap" unicode="&#xf96b;" horiz-adv-x="384"
d="M235.63 288c32.2637 0 60.8311 -26.0781 63.75 -58.21l20.3604 -224c0.144531 -1.59473 0.262695 -4.18848 0.262695 -5.79004c0 -35.3281 -28.6729 -64 -64 -64h-0.00292969h-192c-35.3242 0.00390625 -63.9922 28.6758 -63.9922 64
c0 1.60156 0.117188 4.19531 0.261719 5.79004l20.3604 224c2.91895 32.1318 31.4736 58.21 63.7373 58.21h0.00292969h151.26zM160 32c33.1201 0 60 26.3301 60 58.7305c0 25 -35.6699 75.4697 -52 97.2695c-1.65625 2.21387 -5.24316 4.00977 -8.00781 4.00977
c-2.75586 0 -6.33594 -1.78711 -7.99219 -3.99023c-16.2998 -21.7998 -52 -72.2695 -52 -97.2695c0 -32.4199 26.8799 -58.75 60 -58.75zM379.31 353.94c2.58789 -2.58691 4.68848 -7.65625 4.68848 -11.3154s-2.10059 -8.72852 -4.68848 -11.3154l-22.6191 -22.6191
c-2.58691 -2.58789 -7.65625 -4.68848 -11.3154 -4.68848s-8.72852 2.10059 -11.3154 4.68848l-43.3096 43.3096h-66.75v-32h-128v96c0 17.6641 14.3359 32 32 32h64c17.6641 0 32 -14.3359 32 -32h66.75v0c14.6309 0 34.9033 -8.39551 45.25 -18.7402z" />
<glyph glyph-name="shield-virus" unicode="&#xf96c;"
d="M224 256c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16zM466.5 364.32c16.2842 -6.80176 29.5 -26.6445 29.5 -44.292v-0.0283203c0 -221.3 -135.91 -344.61 -221.59 -380.32
c-4.89062 -2.03223 -13.1592 -3.68164 -18.4551 -3.68164c-5.29688 0 -13.5645 1.64941 -18.4551 3.68164c-107 44.6006 -221.5 181.82 -221.5 380.32v0.0478516c0 17.6787 13.2559 37.5176 29.5898 44.2822l192 80c4.92676 1.85938 13.1973 3.50391 18.46 3.66992
c5.26074 -0.169922 13.5264 -1.81836 18.4502 -3.67969zM384 192c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16h-12.1201c-28.5098 0 -42.79 34.4697 -22.6299 54.6299l8.58008 8.57031c2.58691 2.58691 4.68652 7.65625 4.68652 11.3145
c0 8.83301 -7.16895 16.002 -16.002 16.002c-3.6582 0 -8.72754 -2.09961 -11.3145 -4.68652l-8.57031 -8.58008c-20.1602 -20.1602 -54.6299 -5.87988 -54.6299 22.6299v12.1201c0 8.83203 -7.16797 16 -16 16s-16 -7.16797 -16 -16v-12.1201
c0 -28.5098 -34.4697 -42.79 -54.6299 -22.6299l-8.57031 8.58008c-2.58691 2.58691 -7.65625 4.68652 -11.3145 4.68652c-8.83301 0 -16.002 -7.16895 -16.002 -16.002c0 -3.6582 2.09961 -8.72754 4.68652 -11.3145l8.58008 -8.57031
c20.1602 -20.1602 5.87988 -54.6299 -22.6299 -54.6299h-12.1201c-8.83203 0 -16 -7.16797 -16 -16s7.16797 -16 16 -16h12.1201c28.5098 0 42.79 -34.4697 22.6299 -54.6299l-8.58008 -8.57031c-2.58691 -2.58691 -4.68652 -7.65625 -4.68652 -11.3145
c0 -8.83301 7.16895 -16.002 16.002 -16.002c3.6582 0 8.72754 2.09961 11.3145 4.68652l8.57031 8.58008c20.1602 20.1602 54.6299 5.87988 54.6299 -22.6299v-12.1201c0 -8.83203 7.16797 -16 16 -16s16 7.16797 16 16v12.1201c0 28.5098 34.4697 42.79 54.6299 22.6299
l8.57031 -8.58008c2.58691 -2.58691 7.65625 -4.68652 11.3145 -4.68652c8.83301 0 16.002 7.16895 16.002 16.002c0 3.6582 -2.09961 8.72754 -4.68652 11.3145l-8.58008 8.57031c-20.1602 20.1602 -5.87988 54.6299 22.6299 54.6299h12.1201zM288 192
c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16s-16 7.16797 -16 16s7.16797 16 16 16z" />
<glyph glyph-name="soap" unicode="&#xf96e;"
d="M416 256c52.9922 0 96 -43.0078 96 -96v-128c0 -52.9922 -43.0078 -96 -96 -96h-320c-52.9922 0 -96 43.0078 -96 96v128c0 52.9922 43.0078 96 96 96h128c0.0517578 -20.3193 11.2119 -48.9912 24.9102 -64h-88.9102c-52.9922 0 -96 -43.0078 -96 -96
s43.0078 -96 96 -96h192h0.206055c52.8809 0 95.7998 42.918 95.7998 95.7998c0 36.9893 -28.2002 77.3047 -62.9463 89.9902c17.0488 15.6279 30.9092 47.082 30.9404 70.21zM320 192c-35.3281 0 -64 28.6719 -64 64s28.6719 64 64 64s64 -28.6719 64 -64
s-28.6719 -64 -64 -64zM208 352c-26.4961 0 -48 21.5039 -48 48s21.5039 48 48 48s48 -21.5039 48 -48s-21.5039 -48 -48 -48zM384 384c-17.6641 0 -32 14.3359 -32 32s14.3359 32 32 32s32 -14.3359 32 -32s-14.3359 -32 -32 -32zM160 160h192
c35.3281 0 64 -28.6719 64 -64s-28.6719 -64 -64 -64h-192c-35.3281 0 -64 28.6719 -64 64s28.6719 64 64 64z" />
<glyph glyph-name="stopwatch-20" unicode="&#xf96f;" horiz-adv-x="448"
d="M398.5 257.09c18.4922 -28.3281 33.501 -78.7754 33.501 -112.605c0 -0.133789 -0.000976562 -0.350586 -0.000976562 -0.484375c0 -116 -94.8701 -209.77 -211.28 -208c-113.96 1.78027 -208.08 100.5 -204.63 214.43c2.92773 95.2598 81.7354 184.443 175.91 199.07
v34.5h-32c-8.80469 0.0273438 -15.9727 7.19531 -16 16v32c0.0273438 8.80469 7.19531 15.9727 16 16h128c8.83203 0 16 -7.16797 16 -16v-32c0 -8.83203 -7.16797 -16 -16 -16h-32v-34.5c30.8857 -4.76953 75.5469 -24.7461 99.6904 -44.5898l24.6797 24.6797
c2.58691 2.58789 7.65625 4.6875 11.3145 4.6875c3.65918 0 8.72852 -2.09961 11.3154 -4.6875l22.6797 -22.6797c2.58789 -2.58691 4.6875 -7.65625 4.6875 -11.3154c0 -3.6582 -2.09961 -8.72754 -4.6875 -11.3145l-26.5898 -26.5801zM204.37 70.4502l-49.1299 0.0400391
c1.7998 15.6299 14.8496 36.2002 26.4102 51.2002c21.9092 30.0996 34.3496 45.7295 34.3496 81.3096c0 35.1504 -12.5703 61 -55.5703 61c-47.9492 0 -56.4297 -32.9404 -56.4297 -60.2402v-4.06934c0.0703125 -4.45605 3.74316 -8.07129 8.19922 -8.07129
c0.0361328 0 0.0947266 0 0.130859 0.000976562h24.9004c0.0361328 -0.000976562 0.0947266 -0.000976562 0.130859 -0.000976562c4.45508 0 8.12891 3.61523 8.19824 8.07129v5.22949c0 15.2803 3.30078 22.6797 12.6904 22.6797c10.4199 0 12.21 -7.34961 12.21 -24.2695
c0 -25.0205 -6.67969 -33.1504 -27.0996 -62.3398c-23.7803 -33.96 -35.6699 -56.1504 -38.4502 -91.3701c-0.0224609 -0.320312 -0.0410156 -0.84082 -0.0410156 -1.16113c0 -9.08594 7.37402 -16.46 16.46 -16.46c0.0527344 0 0.137695 0 0.19043 0.000976562h82.8506
c0.0332031 -0.000976562 0.0878906 -0.000976562 0.121094 -0.000976562c4.45508 0 8.12891 3.61621 8.19922 8.07129v22.3096c-0.0703125 4.45508 -3.74414 8.07129 -8.19922 8.07129c-0.0332031 0 -0.0878906 -0.000976562 -0.121094 -0.000976562zM344 95.6797v107.021
c0 38.6602 -19 61.2998 -55.7998 61.2998c-36.6201 0 -56.2002 -22.4902 -56.2002 -63.2197v-105.33c0 -33.9307 11.1904 -63.4502 54.7695 -63.4502c44.9307 0 57.2305 28.5195 57.2305 63.6797zM287.87 226.27c10.0098 0 13.0195 -8.05957 13 -19.3291v-115.94
c0 -13.2695 -3.36035 -21.2695 -13 -21.2695s-13.2305 7.47949 -13.2305 20.5898v115.949c0 12.5 3.82031 20 13.2305 20z" />
<glyph glyph-name="store-alt-slash" unicode="&#xf970;" horiz-adv-x="640"
d="M17.8896 324.38l88.4707 -68.3799h-74.2607c-25.5898 0 -40.79 28.5 -26.5898 49.7998zM576 34.5801l57.8301 -44.6797c3.41016 -2.65234 6.17773 -8.31055 6.17773 -12.6309c0 -3.0293 -1.50879 -7.42773 -3.36816 -9.81934l-19.6396 -25.2598
c-2.65234 -3.41895 -8.31543 -6.19434 -12.6426 -6.19434c-3.03223 0 -7.43457 1.51172 -9.82715 3.37402l-588.351 454.72c-3.41016 2.65234 -6.17773 8.31055 -6.17773 12.6309c0 3.02832 1.50879 7.42773 3.36816 9.81934l19.6299 25.2695
c2.65234 3.41309 8.31152 6.18262 12.6338 6.18262c3.03125 0 7.43359 -1.51074 9.82617 -3.37207l34.6904 -26.8203l10.6592 16c5.22266 7.83887 17.1045 14.2002 26.5234 14.2002h0.0869141h405.18h0.0117188c9.45117 0 21.4082 -6.36133 26.6885 -14.2002l85.29 -128
c14.1104 -21.2998 -1.08984 -49.7998 -26.5898 -49.7998h-318.48l41.4004 -32h53.0801v-41l128 -99v140h64v-189.42zM320 64v26.8799l64 -49.4697v-73.4102c0 -17.6641 -14.3359 -32 -32 -32h-256c-17.6641 0 -32 14.3359 -32 32v256h64v-160h192z" />
<glyph glyph-name="store-slash" unicode="&#xf971;" horiz-adv-x="640"
d="M121.51 64h226.91l157.33 -128h-414.52c-16.8105 0 -30.4004 14.2998 -30.4004 32v196.8c4.23047 -1.29297 11.2109 -2.90625 15.5801 -3.59961c4.69629 -0.660156 12.3574 -1.19727 17.0996 -1.2002c7.85352 0.12793 20.3975 1.83008 28 3.7998v-99.7998z
M93.5098 192.09h-0.21875c-3.54883 0 -9.28418 0.385742 -12.8008 0.860352c-58.9404 8.46973 -87.0098 81.6094 -56.4902 135l133.51 -108.62c-16.71 -16.5205 -38.8994 -27.2402 -64 -27.2402zM602.13 -10.0898c3.24316 -2.74219 5.875 -8.41406 5.875 -12.6611
c0 -2.99414 -1.43555 -7.38379 -3.20508 -9.79883l-18.6602 -25.2598c-2.42383 -3.41309 -7.78906 -6.18359 -11.9756 -6.18359c-2.91602 0 -7.10645 1.50684 -9.35449 3.36328l-558.939 454.72c-3.24316 2.74219 -5.875 8.41406 -5.875 12.6611
c0 2.99414 1.43555 7.38379 3.20508 9.79883l18.6602 25.2598c2.42188 3.41211 7.78516 6.18164 11.9697 6.18164c2.91797 0 7.11133 -1.51074 9.36035 -3.37109l33.6895 -27.4004l9.38965 15.7803c4.74609 8.18066 16.2734 14.9014 25.7305 15h383.81
c9.46289 -0.09375 20.9941 -6.81445 25.7402 -15l61.6602 -103.6c31.9404 -53.6006 3.59961 -127.99 -56.0596 -136.4c-3.57129 -0.5 -9.39453 -0.907227 -13 -0.910156c-28.0303 0 -52.9199 13 -70.1104 33.1104c-17.1104 -20.1104 -42 -33.1104 -70.1104 -33.1104
c-7.18164 0.106445 -18.5654 1.96094 -25.4102 4.14062l137.82 -112.11v79.6797c7.59863 -2.00586 20.1426 -3.70898 28 -3.7998c4.79492 0.00585938 12.541 0.542969 17.29 1.2002c4.38281 0.625 11.3584 2.2373 15.5703 3.59961v-130.21z" />
<glyph glyph-name="toilet-paper-slash" unicode="&#xf972;" horiz-adv-x="640"
d="M64 256c0 10.8096 0.530273 21.3398 1.41992 31.6699l316 -244.25c-4.17969 -32.2002 -12.8701 -57.7197 -22.1797 -85.5498c-3.98926 -12.0723 -17.5459 -21.8701 -30.2607 -21.8701h-0.119141h-280.86c-8.78906 0.0429688 -15.9209 7.21094 -15.9209 16
c0 1.41504 0.358398 3.65527 0.800781 5c21.3701 64.1201 31.1201 85.75 31.1201 126.87v172.13zM633.82 -10.0898c3.41602 -2.65234 6.18848 -8.31445 6.18848 -12.6387c0 -3.03027 -1.50879 -7.42969 -3.36914 -9.82129l-19.6396 -25.2598
c-2.65234 -3.41699 -8.31445 -6.18945 -12.6387 -6.18945c-3.03027 0 -7.42969 1.50977 -9.82129 3.36914l-588.36 454.72c-3.41211 2.65234 -6.18262 8.3125 -6.18262 12.6338c0 3.03223 1.51172 7.43359 3.37305 9.82617l19.6299 25.2598
c2.65234 3.41309 8.31152 6.18262 12.6338 6.18262c3.03125 0 7.43359 -1.51074 9.82617 -3.37207l53.2803 -41.1504c16.6299 27.7002 37.9297 44.5303 61.2598 44.5303h284.5c-36.8701 -38.5 -60.5 -108.38 -60.5 -192v-73l50.4297 -39
c-11.4297 31.5996 -18.4297 70 -18.4297 112c0 106 43 192 96 192s96 -86 96 -192c0 -92.3203 -32.7197 -168.91 -76.1797 -187.28zM512 192c17.6201 0 32 28.6299 32 64s-14.3701 64 -32 64s-32 -28.6201 -32 -64s14.3701 -64 32 -64z" />
<glyph glyph-name="virus" unicode="&#xf974;"
d="M483.55 220.45c0.147461 0.00292969 0.356445 0.00488281 0.503906 0.00488281c15.7041 0 28.4492 -12.7461 28.4492 -28.4502s-12.7451 -28.4502 -28.4492 -28.4502c-0.147461 0 -0.386719 0.00292969 -0.53418 0.00488281h-21.5391
c-50.6807 0 -76.0703 -61.2793 -40.2305 -97.1191l15.25 -15.2402c4.15039 -4.50879 7.51855 -13.1406 7.51855 -19.2686c0 -15.7051 -12.7451 -28.4502 -28.4502 -28.4502c-6.12793 0 -14.7598 3.36816 -19.2686 7.51855l-15.2402 15.2305
c-35.8398 35.8398 -97.1094 10.4492 -97.1094 -40.2305v-21.5195c0 -15.7051 -12.7461 -28.4502 -28.4502 -28.4502s-28.4502 12.7451 -28.4502 28.4502v21.5391c0 50.6807 -61.2695 76.0703 -97.1094 40.2305l-15.2402 -15.25
c-4.50879 -4.15039 -13.1406 -7.51855 -19.2686 -7.51855c-15.7051 0 -28.4502 12.7451 -28.4502 28.4502c0 6.12793 3.36816 14.7598 7.51855 19.2686l15.2305 15.2402c35.8398 35.8398 10.4492 97.1191 -40.2305 97.1191h-21.5498
c-0.147461 -0.00195312 -0.386719 -0.00488281 -0.533203 -0.00488281c-15.7051 0 -28.4502 12.7461 -28.4502 28.4502s12.7451 28.4502 28.4502 28.4502c0.146484 0 0.385742 -0.00195312 0.533203 -0.00488281h21.5693c50.6807 0 76.0703 61.2695 40.2305 97.1094
l-15.25 15.25c-4.21094 4.52148 -7.62793 13.2051 -7.62793 19.3828c0 15.6992 12.7412 28.4404 28.4404 28.4404c6.17969 0 14.8662 -3.41992 19.3877 -7.63281l15.2402 -15.2305c35.8398 -35.8291 97.1094 -10.4492 97.1094 40.2305v21.5596
c0 15.7051 12.7461 28.4502 28.4502 28.4502s28.4502 -12.7451 28.4502 -28.4502v-21.5498c0 -50.6797 61.2695 -76.0596 97.1094 -40.2295l15.2402 15.2197c4.52148 4.21289 13.208 7.63281 19.3877 7.63281c15.6992 0 28.4404 -12.7412 28.4404 -28.4404
c0 -6.17773 -3.41699 -14.8613 -7.62793 -19.3828l-15.2305 -15.25c-35.8398 -35.8398 -10.4492 -97.1094 40.2305 -97.1094h21.5498zM224 176c26.4961 0 48 21.5039 48 48s-21.5039 48 -48 48s-48 -21.5039 -48 -48s21.5039 -48 48 -48zM304 120c13.248 0 24 10.752 24 24
s-10.752 24 -24 24s-24 -10.752 -24 -24s10.752 -24 24 -24z" />
<glyph glyph-name="virus-slash" unicode="&#xf975;" horiz-adv-x="640"
d="M114 220.44c8.37207 0.0664062 20.9922 3.61914 28.1699 7.92969l244.5 -189c-21.2197 -7.45996 -38.2197 -26.7598 -38.2197 -53.3701v-21.5195c0 -15.7051 -12.7461 -28.4502 -28.4502 -28.4502s-28.4502 12.7451 -28.4502 28.4502v21.5391
c0 50.6807 -61.2695 76.0703 -97.1094 40.2305l-15.25 -15.25c-4.66113 -5.03711 -14.0127 -9.125 -20.875 -9.125c-15.6992 0 -28.4404 12.7412 -28.4404 28.4404c0 6.8623 4.08789 16.2139 9.125 20.875l15.2305 15.25c35.8291 35.8398 10.4492 97.1191 -40.2305 97.1191
h-21.5596c-15.6992 0 -28.4404 12.7412 -28.4404 28.4404s12.7412 28.4404 28.4404 28.4404h21.5596zM633.82 -10.0898c3.41602 -2.65234 6.18848 -8.31445 6.18848 -12.6387c0 -3.03027 -1.50879 -7.42969 -3.36914 -9.82129l-19.6396 -25.2598
c-2.65234 -3.41699 -8.31445 -6.18945 -12.6387 -6.18945c-3.03027 0 -7.42969 1.50977 -9.82129 3.36914l-588.36 454.72c-3.41211 2.65234 -6.18262 8.3125 -6.18262 12.6338c0 3.03223 1.51172 7.43359 3.37305 9.82617l19.6299 25.2598
c2.65234 3.41309 8.31152 6.18262 12.6338 6.18262c3.03125 0 7.43359 -1.51074 9.82617 -3.37207l93.2598 -72.0801c0.129883 0.139648 0.150391 0.320312 0.280273 0.459961c4.5957 4.5918 13.5986 8.31934 20.0947 8.31934c6.49707 0 15.5 -3.72754 20.0957 -8.31934
l15.25 -15.2305c35.8398 -35.8398 97.1094 -10.46 97.1094 40.2305v21.5498c0 15.7051 12.7461 28.4502 28.4502 28.4502s28.4502 -12.7451 28.4502 -28.4502v-21.54c0 -50.6895 61.2695 -76.0693 97.1094 -40.2295l15.25 15.2197
c4.51367 4.17578 13.167 7.56543 19.3154 7.56543c15.6992 0 28.4404 -12.7412 28.4404 -28.4404c0 -6.14844 -3.38965 -14.8018 -7.56543 -19.3154l-15.2305 -15.29c-35.8291 -35.8398 -10.4492 -97.1191 40.2305 -97.1191h21.5596
c15.6992 0 28.4404 -12.7412 28.4404 -28.4404s-12.7412 -28.4404 -28.4404 -28.4404v0h-21.5498c-30.4795 0 -51.2197 -22.1299 -55.3896 -47.5195zM335.43 220.52c0.0898438 1.19043 0.570312 2.26074 0.570312 3.48047c0 26.4961 -21.5039 48 -48 48
c-4.39844 -0.0683594 -11.3154 -1.36328 -15.4404 -2.88965z" />
<glyph glyph-name="viruses" unicode="&#xf976;" horiz-adv-x="640"
d="M624 96c8.83203 0 16 -7.16797 16 -16s-7.16797 -16 -16 -16h-12.1201c-28.5098 0 -42.79 -34.4697 -22.6299 -54.6299l8.58008 -8.57031c2.58691 -2.58691 4.68652 -7.65625 4.68652 -11.3145c0 -8.83301 -7.16895 -16.002 -16.002 -16.002
c-3.6582 0 -8.72754 2.09961 -11.3145 4.68652l-8.57031 8.58008c-20.1602 20.1602 -54.6299 5.87988 -54.6299 -22.6299v-12.1201c0 -8.83203 -7.16797 -16 -16 -16s-16 7.16797 -16 16v12.1201c0 28.5098 -34.4697 42.79 -54.6299 22.6299l-8.57031 -8.58008
c-2.58691 -2.58691 -7.65625 -4.68652 -11.3145 -4.68652c-8.83301 0 -16.002 7.16895 -16.002 16.002c0 3.6582 2.09961 8.72754 4.68652 11.3145l8.58008 8.57031c20.1602 20.1602 5.87988 54.6299 -22.6299 54.6299h-12.1201c-8.83203 0 -16 7.16797 -16 16
s7.16797 16 16 16h12.1201c28.5098 0 42.79 34.4697 22.6299 54.6299l-8.58008 8.57031c-2.58691 2.58691 -4.68652 7.65625 -4.68652 11.3145c0 8.83301 7.16895 16.002 16.002 16.002c3.6582 0 8.72754 -2.09961 11.3145 -4.68652l8.57031 -8.58008
c20.1602 -20.1602 54.6299 -5.87988 54.6299 22.6299v12.1201c0 8.83203 7.16797 16 16 16s16 -7.16797 16 -16v-12.1201c0 -28.5098 34.4697 -42.79 54.6299 -22.6299l8.57031 8.58008c2.58691 2.58691 7.65625 4.68652 11.3145 4.68652
c8.83301 0 16.002 -7.16895 16.002 -16.002c0 -3.6582 -2.09961 -8.72754 -4.68652 -11.3145l-8.58008 -8.57031c-20.1602 -20.1602 -5.87988 -54.6299 22.6299 -54.6299h12.1201zM480 64c17.6641 0 32 14.3359 32 32s-14.3359 32 -32 32s-32 -14.3359 -32 -32
s14.3359 -32 32 -32zM346.51 234.67c-38.0195 0 -57.0498 -45.96 -30.1699 -72.8398l11.4297 -11.4297c3.44922 -3.44922 6.24902 -10.208 6.24902 -15.085c0 -11.7764 -9.55762 -21.334 -21.334 -21.334c-4.87695 0 -11.6357 2.7998 -15.085 6.24902l-11.4297 11.4297
c-26.8398 26.8799 -72.8398 7.83008 -72.8398 -30.1699v-16.1602c0 -11.7744 -9.55566 -21.3301 -21.3301 -21.3301s-21.3301 9.55566 -21.3301 21.3301v16.1602c0 38.0195 -45.96 57.0498 -72.8398 30.1699l-11.4297 -11.4297
c-3.44922 -3.44922 -10.208 -6.24902 -15.085 -6.24902c-11.7764 0 -21.334 9.55762 -21.334 21.334c0 4.87695 2.7998 11.6357 6.24902 15.085l11.4297 11.4297c26.8799 26.8398 7.83008 72.8398 -30.1699 72.8398h-16.1602c-11.7744 0 -21.3301 9.55566 -21.3301 21.3301
s9.55566 21.3301 21.3301 21.3301h16.1602c38.0195 0 57.0498 45.96 30.1699 72.8398l-11.4297 11.4404c-3.41895 3.44336 -6.19434 10.1758 -6.19434 15.0283c0 11.7744 9.55566 21.3301 21.3301 21.3301c4.85449 0 11.5898 -2.77734 15.0342 -6.19922l11.4297 -11.4297
c26.8398 -26.8799 72.8398 -7.83008 72.8398 30.1699v16.1602c0 11.7744 9.55566 21.3301 21.3301 21.3301s21.3301 -9.55566 21.3301 -21.3301v-16.1602c0 -38.0195 45.96 -57.0498 72.8398 -30.1699l11.4297 11.4297c3.44434 3.42188 10.1797 6.19922 15.0342 6.19922
c11.7744 0 21.3301 -9.55566 21.3301 -21.3301c0 -4.85254 -2.77539 -11.585 -6.19434 -15.0283l-11.4297 -11.4404c-26.8799 -26.8398 -7.83008 -72.8398 30.1699 -72.8398h16.1602c11.7744 0 21.3301 -9.55566 21.3301 -21.3301s-9.55566 -21.3301 -21.3301 -21.3301
h-16.1602zM160 256c17.6641 0 32 14.3359 32 32s-14.3359 32 -32 32s-32 -14.3359 -32 -32s14.3359 -32 32 -32zM240 224c8.83203 0 16 7.16797 16 16s-7.16797 16 -16 16s-16 -7.16797 -16 -16s7.16797 -16 16 -16z" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 829 KiB

After

Width:  |  Height:  |  Size: 876 KiB

View File

@@ -6,8 +6,8 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/favicon.ico">
<link rel="stylesheet" href="styles.23a05ae088b0f881d0eb.css"></head>
<link rel="stylesheet" href="styles.921aafa95031aeb74181.css"></head>
<body>
<app-root></app-root>
<script src="runtime.e227d1a0e31cbccbf8ec.js" defer></script><script src="polyfills-es5.f1f388528ea207060cbf.js" nomodule defer></script><script src="polyfills.335424f161535f57733f.js" defer></script><script src="scripts.5520a99f673924c17e00.js" defer></script><script src="main.412f22a84714f529779a.js" defer></script></body>
<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>
</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

File diff suppressed because one or more lines are too long

View File

@@ -56,6 +56,7 @@ services:
target: config.json
environment:
- HOSTNAME_URL=http://localhost:3000
- BACKEND_LOGS_URL=http://localhost:8888
- REDIS_URL=redis://redis
- MONGO_URI=mongodb://root:example@mongo/qmicloud?authSource=admin
- CERT_PFX_FILENAME=
@@ -75,6 +76,8 @@ services:
# -------------------
- ../qmi-cloud-provisions:/provisions
- ./logs:/logs
- ./costexport:/var/www/app/costexport
- ./photos:/var/www/app/photos
#- ./certs:/var/www/app/server/certs
depends_on:
- mongo
@@ -93,6 +96,7 @@ services:
container_name: qmi-cloud-worker
restart: on-failure
environment:
- HOSTNAME_URL=http://localhost:3000
- REDIS_URL=redis://redis
- MONGO_URI=mongodb://root:example@mongo/qmicloud?authSource=admin
- PROJECT_PATH=%PWD%
@@ -104,7 +108,7 @@ services:
command: "sh -c 'npm run start:dev'"
volumes:
# -- Dev only volumes
- ./worker:/app/worker
- ./qmi-cloud-worker:/app/worker
# -------------------
- ./logs:/logs
- /var/run/docker.sock:/home/docker.sock
@@ -127,5 +131,16 @@ services:
ME_CONFIG_MONGODB_ADMINPASSWORD: example
ME_CONFIG_BASICAUTH_USERNAME: qlik
ME_CONFIG_BASICAUTH_PASSWORD: Qlik1234
networks:
- backend
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 8888:8080
#environment:
# DOZZLE_BASE: "/dozzle"
networks:
- backend

View File

@@ -22,7 +22,8 @@ echo "--- Building image: qlikgear/qmi-cloud-cli:$TAG_CLI"
docker build -f ./qmi-cloud-cli/Dockerfile -t qlikgear/qmi-cloud-cli:$TAG_CLI ./
echo "--- Pushing image: qlikgear/qmi-cloud-cli:$TAG_CLI"
docker push qlikgear/qmi-cloud-cli:$TAG_CLI
docker build -f ./qmi-cloud-cli/Dockerfile -t qlikgear/qmi-cloud-cli:$STABLE_TAG ./
docker image tag qlikgear/qmi-cloud-cli:$TAG_CLI qlikgear/qmi-cloud-cli:$STABLE_TAG
#docker build -f ./qmi-cloud-cli/Dockerfile -t qlikgear/qmi-cloud-cli:$STABLE_TAG ./
docker push qlikgear/qmi-cloud-cli:$STABLE_TAG
@@ -30,12 +31,14 @@ echo "--- Building image: qlikgear/qmi-cloud-worker:$TAG_WORKER"
docker build -f ./qmi-cloud-worker/Dockerfile -t qlikgear/qmi-cloud-worker:$TAG_WORKER ./
echo "--- Pushing image: qlikgear/qmi-cloud-worker:$TAG_WORKER"
docker push qlikgear/qmi-cloud-worker:$TAG_WORKER
docker build -f ./qmi-cloud-worker/Dockerfile -t qlikgear/qmi-cloud-worker:$STABLE_TAG ./
docker image tag qlikgear/qmi-cloud-worker:$TAG_WORKER qlikgear/qmi-cloud-worker:$STABLE_TAG
#docker build -f ./qmi-cloud-worker/Dockerfile -t qlikgear/qmi-cloud-worker:$STABLE_TAG ./
docker push qlikgear/qmi-cloud-worker:$STABLE_TAG
echo "--- Building image: qlikgear/qmi-cloud-app:$TAG_APP"
docker build -f ./Dockerfile -t qlikgear/qmi-cloud-app:$TAG_APP ./
echo "--- Pushing image: qlikgear/qmi-cloud-app:$TAG_APP"
docker push qlikgear/qmi-cloud-app:$TAG_APP
docker build -f ./Dockerfile -t qlikgear/qmi-cloud-app:$STABLE_TAG ./
docker image tag qlikgear/qmi-cloud-app:$TAG_APP qlikgear/qmi-cloud-app:$STABLE_TAG
#docker build -f ./Dockerfile -t qlikgear/qmi-cloud-app:$STABLE_TAG ./
docker push qlikgear/qmi-cloud-app:$STABLE_TAG

89
mystatsmashup/index.html Normal file
View File

@@ -0,0 +1,89 @@
<!doctype html>
<!-- <html qva-bootstrap="false" lang="en"> -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Simple html with qdt-components</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta http-equiv="cleartype" content="on">
<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" />
<!-- <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>
<style>
body {
margin: 0;
padding: 0;
}
div.qvobject {
height: 100%;
width: 100%;
}
.row {
padding: 10px 0px;
}
</style>
</head>
<body onload="init()">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div id="currentselections" style="width: 100%;height: 40px;"></div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div id="filterstatus" style="width: 100%;height: 40px;"></div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div id="totalprovkpi" class="qvobject"></div>
</div>
<div class="col-md-4">
<div id="costhismonth" class="qvobject"></div>
</div>
<div class="col-md-4">
<div id="statusprov" class="qvobject"></div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="qdt1" class="qvobject"></div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<div id="qdt1a" class="qvobject"></div>
</div>
<div class="col-md-5">
<div id="qdt3" class="qvobject"></div>
</div>
<div class="col-md-5">
<div id="qdt4" class="qvobject"></div>
</div>
</div>
</div>
</body>
</html>

119
mystatsmashup/index.js Normal file
View File

@@ -0,0 +1,119 @@
// https://help.qlik.com/en-US/sense-developer/June2020/Subsystems/Mashups/Content/Sense_Mashups/mashups-authentication-cloud.htm
// https://qlik.dev/tutorials/build-a-simple-web-app
const config = {
host: "gear-presales.eu.qlikcloud.com",
secure: true,
port: 443,
prefix: "",
webIntegrationId: 'n4kMLH62hvXXC84q2vdfW15WUvrUw-HU',
appId: "2d03e11f-f3d8-4ba7-a123-be5a282fb9f1"
}
const urlLoggedIn = "/api/v1/audits";//Use GET request to see if you are authenticated
const urlLogin = "/login";
const init = async () => {
const response = await fetch(`https://${config.host}${urlLoggedIn}`, {
credentials: 'include',
headers: {
'Qlik-Web-Integration-ID': config.webIntegrationId
}
})
if(response.status===401){
const url = new URL(`https://${config.host}/login`);
url.searchParams.append('returnto', 'http://localhost:3000/mystats');
url.searchParams.append('qlik-web-integration-id', config.webIntegrationId);
window.location.href = url;
}
const { qdtCapabilityApp, QdtViz } = QdtComponents;
const capabilityApiAppPromise = qdtCapabilityApp(config);
const app = await capabilityApiAppPromise;
QdtViz({
element: document.getElementById('currentselections'),
app,
options: {
id: 'CurrentSelections',
height: "40px"
},
});
QdtViz({
element: document.getElementById('qdt1'),
app,
options: {
type: 'table',
id: 'mCjaU',
height: "300px"
},
});
QdtViz({
element: document.getElementById('qdt1a'),
app,
options: {
type: "piechart",
id: 'XrnNdj',
height: "300px"
},
});
QdtViz({
element: document.getElementById('qdt3'),
app,
options: {
type: "linechart",
id: 'QfYrJj',
height: "300px"
},
});
QdtViz({
element: document.getElementById('qdt4'),
app,
options: {
type: "barchart",
id: 'JvSJCW',
height: "300px"
},
});
QdtViz({
element: document.getElementById('totalprovkpi'),
app,
options: {
id: 'zWDmhfq',
height: "100px"
},
});
QdtViz({
element: document.getElementById('filterstatus'),
app,
options: {
id: 'uznaVCC',
height: "40px"
},
});
QdtViz({
element: document.getElementById('costhismonth'),
app,
options: {
type: "kpi",
id: "GkRz",
height: "100px"
},
});
QdtViz({
element: document.getElementById('statusprov'),
app,
options: {
id: 'kXyQPa',
height: "150px"
},
});
}

135423
mystatsmashup/qdt-components.js Normal file

File diff suppressed because one or more lines are too long

470
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-app",
"version": "1.1.3",
"version": "1.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -440,6 +440,206 @@
"integrity": "sha512-IZG1kvw48JyFRy7bfMHqBixWrEHZmXmkP5DWsi5Tw6KusaczkMghI20BevCkodPcajXWHAUHNKyp1tlE3OnH0w==",
"dev": true
},
"@angular/localize": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-9.0.7.tgz",
"integrity": "sha512-nlmcxEVZmz4ty1MXE1OFnHMYndedQUunZY4MB3lm7c8MJrRnI5x7zAOvkwI+sUF0Hy3rfjkO7ddOzGBfDtjPPA==",
"requires": {
"@babel/core": "7.8.3",
"glob": "7.1.2",
"yargs": "13.1.0"
},
"dependencies": {
"@babel/core": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz",
"integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==",
"requires": {
"@babel/code-frame": "^7.8.3",
"@babel/generator": "^7.8.3",
"@babel/helpers": "^7.8.3",
"@babel/parser": "^7.8.3",
"@babel/template": "^7.8.3",
"@babel/traverse": "^7.8.3",
"@babel/types": "^7.8.3",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.1",
"json5": "^2.1.0",
"lodash": "^4.17.13",
"resolve": "^1.3.2",
"semver": "^5.4.1",
"source-map": "^0.5.0"
}
},
"@babel/generator": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz",
"integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==",
"requires": {
"@babel/types": "^7.13.0",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
},
"dependencies": {
"@babel/types": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}
}
},
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
},
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"requires": {
"locate-path": "^3.0.0"
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
"requires": {
"minimist": "^1.2.5"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
},
"yargs": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.1.0.tgz",
"integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==",
"requires": {
"cliui": "^4.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"os-locale": "^3.1.0",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^13.0.0"
}
},
"yargs-parser": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"@angular/platform-browser": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.0.7.tgz",
@@ -455,6 +655,120 @@
"resolved": "https://registry.npmjs.org/@angular/router/-/router-9.0.7.tgz",
"integrity": "sha512-uKru9F/Zju//gg6INl54abnlpLdEUUO/GpCfMk4zqu8LCZGNFta6OY7VT+9DK9Vdrh/XUD70oE9WoelcRwwTYA=="
},
"@azure/abort-controller": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.4.tgz",
"integrity": "sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==",
"requires": {
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
}
}
},
"@azure/arm-compute": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/@azure/arm-compute/-/arm-compute-15.0.0.tgz",
"integrity": "sha512-ElV2MuYZ+B2+ygMx2iiM/u3C7WDRruZjkXzfk5p2O+UbWxjG6j/686OH3T+VSDqmzg+47AnIOTLu2v0u0H8FOw==",
"requires": {
"@azure/ms-rest-azure-js": "^2.0.1",
"@azure/ms-rest-js": "^2.0.4",
"tslib": "^1.10.0"
}
},
"@azure/arm-dns": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@azure/arm-dns/-/arm-dns-4.0.0.tgz",
"integrity": "sha512-VhI8NRd6hyHKxMSTqUWpozQ//D4S1CuxFMRDao/Bzs0ETUIUem4DNOJAk5Zn5+IWfDDpJnRlLqLCspfMD5/V8A==",
"requires": {
"@azure/ms-rest-azure-js": "^2.0.1",
"@azure/ms-rest-js": "^2.0.4",
"tslib": "^1.10.0"
}
},
"@azure/core-auth": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.3.0.tgz",
"integrity": "sha512-kSDSZBL6c0CYdhb+7KuutnKGf2geeT+bCJAgccB0DD7wmNJSsQPcF7TcuoZX83B7VK4tLz/u+8sOO/CnCsYp8A==",
"requires": {
"@azure/abort-controller": "^1.0.0",
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
}
}
},
"@azure/ms-rest-azure-env": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz",
"integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw=="
},
"@azure/ms-rest-azure-js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.1.0.tgz",
"integrity": "sha512-CjZjB8apvXl5h97Ck6SbeeCmU0sk56YPozPtTyGudPp1RGoHXNjFNtoOvwOG76EdpmMpxbK10DqcygI16Lu60Q==",
"requires": {
"@azure/core-auth": "^1.1.4",
"@azure/ms-rest-js": "^2.2.0",
"tslib": "^1.10.0"
}
},
"@azure/ms-rest-js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.3.0.tgz",
"integrity": "sha512-8NOnHgovi61NpcUld53zRkY/IcQJBBO48VeMntNTUtaPo8yYYTnu1hWRvp6b6vpBnur7HGmuj692J9li5Kx6/Q==",
"requires": {
"@azure/core-auth": "^1.1.4",
"abort-controller": "^3.0.0",
"form-data": "^2.5.0",
"node-fetch": "^2.6.0",
"tough-cookie": "^3.0.1",
"tslib": "^1.10.0",
"tunnel": "0.0.6",
"uuid": "^3.3.2",
"xml2js": "^0.4.19"
},
"dependencies": {
"form-data": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
}
},
"tough-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
"integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
"requires": {
"ip-regex": "^2.1.0",
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
}
}
},
"@azure/ms-rest-nodeauth": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.0.9.tgz",
"integrity": "sha512-+GdDHUJlWtIDanRZemFooLy68NsBDhN/Oni9DSFeoXIFNGlSe1IOes8/IRkQdrNXyUvBanuzzR7I5WYYzYQsmA==",
"requires": {
"@azure/ms-rest-azure-env": "^2.0.0",
"@azure/ms-rest-js": "^2.0.4",
"adal-node": "^0.1.28"
}
},
"@babel/code-frame": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
@@ -1337,6 +1651,11 @@
"resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz",
"integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="
},
"@ng-bootstrap/ng-bootstrap": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-6.2.0.tgz",
"integrity": "sha512-wqwhnJFyEwvzWQJjXrEt+7oBTSvu2qPbdYvUFYhDVzOJLWB5M7YEhDAkMrfHQJ0pZNBMGr580FqYue+XiURY0Q=="
},
"@ngtools/webpack": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.0.7.tgz",
@@ -1488,8 +1807,7 @@
"@types/node": {
"version": "8.9.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
"integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==",
"dev": true
"integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ=="
},
"@types/prop-types": {
"version": "15.7.3",
@@ -1753,6 +2071,14 @@
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@@ -1794,13 +2120,6 @@
"uuid": "^3.1.0",
"xmldom": ">= 0.1.x",
"xpath.js": "~1.1.0"
},
"dependencies": {
"@types/node": {
"version": "8.10.61",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz",
"integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q=="
}
}
},
"adm-zip": {
@@ -2370,36 +2689,12 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz",
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
},
"azure-arm-compute": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/azure-arm-compute/-/azure-arm-compute-10.0.0.tgz",
"integrity": "sha512-ehafNtcMKI6c00FT+xhPWPtzhYgHCZ675TUsaJ1FJ2bSpznih5EUrpir/4w519T4zbFBigszhnRK6eBkl78I9g==",
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"ms-rest": "^2.5.0",
"ms-rest-azure": "^2.5.5"
},
"dependencies": {
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
"integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
"requires": {
"lodash": "^4.14.0"
}
},
"ms-rest-azure": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/ms-rest-azure/-/ms-rest-azure-2.6.0.tgz",
"integrity": "sha512-J6386a9krZ4VtU7CRt+Ypgo9RGf8+d3gjMBkH7zbkM4zzkhbbMOYiPRaZ+bHZcfihkKLlktTgA6rjshTjF329A==",
"requires": {
"adal-node": "^0.1.28",
"async": "2.6.0",
"moment": "^2.22.2",
"ms-rest": "^2.3.2",
"request": "^2.88.0",
"uuid": "^3.2.1"
}
}
"follow-redirects": "^1.10.0"
}
},
"babel-code-frame": {
@@ -2488,6 +2783,13 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"barracuda-api": {
"version": "git+https://gitlab.com/qlik_gear/barracuda-api-node.git#a4a15a6766f3c66196ed1854cc301575c218323f",
"from": "git+https://gitlab.com/qlik_gear/barracuda-api-node.git#0.0.3",
"requires": {
"axios": "^0.21.1"
}
},
"base": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
@@ -4938,11 +5240,6 @@
"nan": "^2.14.0"
}
},
"duplexer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@@ -5311,6 +5608,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"eventemitter3": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
@@ -6052,6 +6354,11 @@
"integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==",
"dev": true
},
"gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
},
"geojson-vt": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
@@ -9059,44 +9366,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"ms-rest": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/ms-rest/-/ms-rest-2.5.4.tgz",
"integrity": "sha512-VeqCbawxRM6nhw0RKNfj7TWL7SL8PB6MypqwgylXCi+u412uvYoyY/kSmO8n06wyd8nIcnTbYToCmSKFMI1mCg==",
"requires": {
"duplexer": "^0.1.1",
"is-buffer": "^1.1.6",
"is-stream": "^1.1.0",
"moment": "^2.21.0",
"request": "^2.88.0",
"through": "^2.3.8",
"tunnel": "0.0.5",
"uuid": "^3.2.1"
}
},
"ms-rest-azure": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ms-rest-azure/-/ms-rest-azure-3.0.0.tgz",
"integrity": "sha512-cttN01/TtMDB4v3rt/WQ/slgffB6jcUYxcPzcL0VNSB+WFPE1j4y5ICNHMuD1RaNNekCYMI4Pv51BDQ/BXNq7Q==",
"requires": {
"adal-node": "^0.1.28",
"async": "2.6.0",
"moment": "^2.22.2",
"ms-rest": "^2.3.2",
"request": "^2.88.0",
"uuid": "^3.2.1"
},
"dependencies": {
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
"integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
"requires": {
"lodash": "^4.14.0"
}
}
}
},
"multicast-dns": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
@@ -9214,6 +9483,11 @@
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-fetch-npm": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz",
@@ -11353,11 +11627,14 @@
"qmi-cloud-common": {
"version": "file:qmi-cloud-common",
"requires": {
"@azure/arm-compute": "^15.0.0",
"@azure/arm-dns": "^4.0.0",
"@azure/ms-rest-nodeauth": "^3.0.7",
"@hapi/boom": "^9.1.0",
"azure-arm-compute": "^10.0.0",
"axios": "^0.21.1",
"barracuda-api": "git+https://gitlab.com/qlik_gear/barracuda-api-node.git#0.0.3",
"bull": "^3.11.0",
"mongoose": "^5.7.4",
"ms-rest-azure": "^3.0.0",
"nodemailer": "^6.4.2"
}
},
@@ -13594,7 +13871,8 @@
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"through2": {
"version": "2.0.5",
@@ -13802,9 +14080,9 @@
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
},
"tunnel": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.5.tgz",
"integrity": "sha512-gj5sdqherx4VZKMcBA4vewER7zdK25Td+z1npBqpbDys4eJrLx+SlYjJvq1bDXs2irkuJM5pf8ktaEQVipkrbA=="
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"tunnel-agent": {
"version": "0.6.0",
@@ -13990,9 +14268,9 @@
}
},
"underscore": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
"integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg=="
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz",
"integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g=="
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
@@ -15066,7 +15344,6 @@
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
"dev": true,
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
@@ -15075,13 +15352,12 @@
"xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"dev": true
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
},
"xmldom": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.3.0.tgz",
"integrity": "sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g=="
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.5.0.tgz",
"integrity": "sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA=="
},
"xmlhttprequest-ssl": {
"version": "1.5.5",

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-app",
"version": "1.1.6",
"version": "1.3.1",
"scripts": {
"start": "node -r esm server/server.js",
"start:dev": "nodemon -r esm server/server.js",
@@ -27,6 +27,7 @@
"adal-angular4": "^4.0.12",
"angular-bootstrap-md": "9.0.0",
"animate.css": "^3.7.2",
"axios": "^0.21.1",
"body-parser": "^1.19.0",
"bootstrap": "^4.3.1",
"bull-arena": "^2.6.4",
@@ -43,6 +44,7 @@
"jsonwebtoken": "^8.5.1",
"leonardo-ui": "^1.7.1",
"moment": "^2.24.0",
"moment-timezone": "^0.5.31",
"mongoose": "^5.7.4",
"ngx-markdown": "^9.0.0",
"nodemon": "^1.19.1",
@@ -75,4 +77,4 @@
"tslint": "~5.11.0",
"typescript": "~3.7.5"
}
}
}

0
photos/.keep Normal file
View File

View File

@@ -1,7 +1,7 @@
# Stage 1: NOTE: context is actually ../
FROM node:13.8-alpine AS sources
FROM node:15.12.0-alpine AS sources
RUN apk --no-cache add yarn
RUN apk --no-cache add yarn git
WORKDIR /app
@@ -11,7 +11,7 @@ ADD ./qmi-cloud-common ../qmi-cloud-common
RUN yarn install --production
# Stage 2:
FROM node:13.8-alpine AS production
FROM node:15.12.0-alpine AS production
WORKDIR /app
COPY --from=sources /app ./

View File

@@ -1,22 +1,36 @@
# Examples
## Check Destroy
```
docker run --net=host \
-e MONGO_URI="mongodb://root:example@localhost:27017/qmicloud?authSource=admin" \
-e API_KEY="c229219ccdd72d11e8ea253fd3876d247e5f489c9c84922cabdfb0cc194d8ff398a8d8d6528d8241efc99add2207e0ec75122a1b2c5598cc340cbe6b7c3c0dbf" \
qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -p 4 -r test
qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -r test
```
## Init DB
```
docker run --net=host \
-e MONGO_URI="mongodb://root:example@localhost:27017/qmicloud?authSource=admin" \
qlikgear/qmi-cloud-cli:latest -s initdb
```
## Check Destroy
```
docker run --net=host -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -p 20 -r test
docker run --net=host -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkdestroy -t warning -r test
```
## Check Stop
```
docker run --net=host -e SMTP_EMAIL_SENDER=http://172.20.16.4:9200/sendemail -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkstop -t warning -r test
```
## Check Errors
```
docker run --net=host -e MONGO_URI=$MONGO_URI qlikgear/qmi-cloud-cli:latest -s checkerror -r test
```
## Move Destroyed
```
docker run --net=host -e MONGO_URI=$MONGO_URI -v $PWD/qmi-cloud-provisions:/provisions qlikgear/qmi-cloud-cli:latest -s movedestroyed -r "2020-31-12T00:00:00.000Z"
```
docker run --net=host -e MONGO_URI=$MONGO_URI -e API_KEY=$API_KEY qlikgear/qmi-cloud-cli:latest -s checkstop -t warning -p 4 -r test
```

View File

@@ -1,7 +1,7 @@
var myArgs = process.argv.slice(2);
if ( myArgs.length < 3 ) {
if ( myArgs.length < 2 ) {
console.log("Missing args", myArgs);
process.exit(0);
}
@@ -12,10 +12,11 @@ const moment = require('moment');
const fetch = require('node-fetch');
const IS_REAL = myArgs[2] !== 'test';
const STOPPED_PERIOD = myArgs[1]; //Days
const STOPPED_LIMIT_HOURS_WARNING = 24*(STOPPED_PERIOD-2); //Hours
const STOPPED_LIMIT_HOURS_STOP = 24*STOPPED_PERIOD; //Hours
const IS_REAL = myArgs[1] !== 'test';
const WARNING_DAYS = 2;
//---
const API_KEY = process.env.API_KEY;
const SERVER_URL = "http://localhost:3000";
@@ -56,18 +57,18 @@ async function asyncForEach(array, callback) {
}
async function init(type) {
var limit, cb;
var cb;
var filter = {
"isDestroyed":false,
"isDeleted": false,
"statusVms": "Stopped"
"statusVms": "Stopped",
"vmImage": {"$exists": true},
"vmImage.vm1": { "$exists": true }
};
if ( type === "warning" ) {
limit = STOPPED_LIMIT_HOURS_WARNING;
filter.pendingNextAction = {$ne: "destroy"};
cb = doSendEmailDestroyWarning;
} else if ( type === "exec" ) {
limit = STOPPED_LIMIT_HOURS_STOP;
filter.pendingNextAction = "destroy";
cb = doDestroy;
} else {
@@ -76,46 +77,56 @@ async function init(type) {
}
let provisions = await db.provision.get(filter);
let scenarios = await db.scenario.get();
await asyncForEach(provisions.results, async function(p) {
var _scenario = scenarios.results.filter(s=>{
return s.name === p.scenario
});
if ( _scenario.length ){
p._scenario = _scenario[0];
var typeSchedule = "24x7";
if ( p.schedule && !p.schedule.is24x7 ) {
typeSchedule = 'OnSchedule';
}
timeRunning(p);
if (!IS_REAL) {
console.log(`${p._id} - limit: ${limit} hs - actual duration: ${p.duration.hours} hs`);
let stoppedPeriod = p._scenarioDoc.allowedInnactiveDays || 20;
let stoppedPeriodExternal = Math.ceil(stoppedPeriod/2);
var limit;
if ( type === "warning" ) {
limit = p.isExternalAccess? 24*(stoppedPeriodExternal-WARNING_DAYS) : 24*(stoppedPeriod-WARNING_DAYS);
} else if ( type === "exec" ) {
limit = p.isExternalAccess? (24*stoppedPeriodExternal) : (24*stoppedPeriod);
}
if ( !IS_REAL ) {
console.log(`${p._id} (${typeSchedule}) - limit: ${limit} hs - actual duration: ${p.duration.hours} hs`);
}
if ( p.duration && p.duration.hours >= limit) {
await cb(p);
await cb(p, limit, typeSchedule);
}
});
}
const doSendEmailDestroyWarning = async function(p) {
const doSendEmailDestroyWarning = async function(p, limit, typeSchedule) {
if ( p.pendingNextAction === 'destroy') {
console.log(`Warning email Destroy already sent. Wait for pending action to complete.`);
} else {
let msg = `Send warning DESTROY email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenario.title}' (${p._id}) being 'Inactive' more than ${STOPPED_LIMIT_HOURS_WARNING} hours (exactly ${p.duration.complete})`;
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' });
await db.provision.update(p._id, {"pendingNextAction": "destroy"});
await sendEmail.sendWillDestroyIn24(p, p._scenario);
await db.notification.add({ provision: p._id.toString(), type: 'warningDestroy', message: msg });
await db.notification.add({ provision: p._id.toString(), type: 'warningDestroy', message: msg });
await sendEmail.sendWillDestroyIn24(p, p._scenarioDoc, Math.floor(limit/24), WARNING_DAYS);
}
}
};
const doDestroy = async function(p) {
const doDestroy = async function(p, limit, typeSchedule) {
try {
let msg = `Provision destroyed - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenario.title}' (${p._id}) being 'Inactive' more than ${STOPPED_LIMIT_HOURS_STOP} hours (exactly ${p.duration.complete})`
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 ) {
if ( IS_REAL ) {
db.event.add({ user: p.user._id, provision: p._id, type: 'vms.exec-destroy' });
await postDestroy(p);
await db.notification.add({ provision: p._id.toString(), type: 'destroy', message: msg });
await db.notification.add({ provision: p._id.toString(), type: 'destroy', message: msg });
}
} catch (error) {
console.log("doDestroy Error", error);

View File

@@ -0,0 +1,59 @@
var myArgs = process.argv.slice(2);
if ( myArgs.length < 1 ) {
console.log("Missing args", myArgs);
process.exit(0);
}
const IS_REAL = myArgs[0] !== 'test';
// ---
const db = require('qmi-cloud-common/mongo');
const sendEmail = require("qmi-cloud-common/send-email");
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
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' });
await db.notification.add({ provision: p._id.toString(), type: 'warningError', message: msg });
await sendEmail.sendProvisionError(p, p._scenarioDoc);
}
};
async function init() {
let provisions = await db.provision.get({
"isDestroyed":false,
"isDeleted": false,
"status": "error"
});
await asyncForEach(provisions.results, async function(p) {
await doSendEmailErrorProvision(p);
});
}
function check() {
init().then(function(){
db.mongoose.connection.close()
process.exit(0);
}).catch(function(e){
db.mongoose.connection.close()
console.log("Error", e);
process.exit(0);
});
}
// --------------------------------
check();

View File

@@ -0,0 +1,61 @@
var myArgs = process.argv.slice(2);
if ( myArgs.length < 1 ) {
console.log("Missing args", myArgs);
process.exit(0);
}
console.log("myArgs", myArgs);
const ISODATE = new Date(myArgs[0]).toISOString();
const db = require('qmi-cloud-common/mongo');
const fs = require('fs-extra');
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
async function init() {
let provisions = await db.provision.get({
"isDestroyed":true,
"isDeleted": false,
"created": { "$lt" : ISODATE }
});
await asyncForEach(provisions.results, async function(p) {
await doMoveDestroyed(p);
});
}
const doMoveDestroyed = async function(provision) {
var scenarioFolder = `${provision.scenario}_${provision._id}`;
console.log(`Moving scenario: /provisions/${scenarioFolder}`);
if (fs.existsSync(`/provisions/${scenarioFolder}`)) {
fs.moveSync(`/provisions/${scenarioFolder}`, `/provisions/deleted/${scenarioFolder}`, { overwrite: true })
console.log(`OK.`);
} else {
console.log(`NOK: It does not exist.`);
}
};
function check() {
init().then(function(){
db.mongoose.connection.close()
process.exit(0);
}).catch(function(e){
db.mongoose.connection.close()
console.log("Error", e);
process.exit(0);
});
}
// --------------------------------
check();

View File

@@ -1,27 +1,24 @@
var myArgs = process.argv.slice(2);
if ( myArgs.length < 3 ) {
if ( myArgs.length < 2 ) {
console.log("Missing args", myArgs);
process.exit(0);
}
const IS_REAL = myArgs[2] !== 'test';
const RUNNING_PERIOD = myArgs[1]; //Days
const RUNNING_LIMIT_HOURS_WARNING = 24*(RUNNING_PERIOD-1);
const RUNNING_LIMIT_HOURS_STOP = 24*RUNNING_PERIOD;
const IS_REAL = myArgs[1] !== 'test';
const WARNING_DAYS = 1;
// ---
const db = require('qmi-cloud-common/mongo');
const sendEmail = require("qmi-cloud-common/send-email");
const moment = require('moment');
const azurecli = require('qmi-cloud-common/azurecli');
const cli = require('qmi-cloud-common/cli');
function timeRunning(p) {
function timeRunningIs24x7(p) {
let runningFromTime = p.runningFrom? new Date(p.runningFrom).getTime() : new Date(p.created).getTime();
let totalRunningFromTime;
if (p.statusVms !== 'Stopped' && p.statusVms !== 'Starting' && !p.isDestroyed) {
totalRunningFromTime = Math.abs(new Date().getTime() - runningFromTime);
}
let totalRunningFromTime = Math.abs(new Date().getTime() - runningFromTime);
let duration = moment.duration(totalRunningFromTime);
p.duration = {
hours: Math.floor(duration.asHours()),
@@ -29,6 +26,30 @@ function timeRunning(p) {
};
}
function timeRunningOnSchedule(p) {
let runningFromTime = p.runningFrom? new Date(p.runningFrom).getTime() : new Date(p.created).getTime();
let totalRunningTime = p.timeRunning*1000*60;
let now = new Date();
totalRunningTime = totalRunningTime + Math.abs(now.getTime() - runningFromTime);
let duration = moment.duration(totalRunningTime);
p.duration = {
hours: Math.floor(duration.asHours()),
complete: Math.floor(duration.asDays()) +"d "+duration.hours()+"h "+duration.minutes()
};
}
function timeRunningOnSchedule2(p) {
let startTimestamp = p.startDateOnSchedule? new Date(p.startDateOnSchedule).getTime() : 0;
let endTimestamp = p.endDateOnSchedule? new Date(p.endDateOnSchedule).getTime() : 0;
let totalTimeOnschedule = Math.abs(endTimestamp - startTimestamp);
let duration = moment.duration(totalTimeOnschedule);
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);
@@ -36,18 +57,18 @@ async function asyncForEach(array, callback) {
}
async function init(type) {
var limit, cb;
var cb;
var filter = {
"isDestroyed":false,
"isDestroyed": false,
"isDeleted": false,
"statusVms": "Running"
"statusVms": "Running",
"vmImage": { "$exists": true },
"vmImage.vm1": { "$exists": true }
};
if ( type === "warning" ) {
limit = RUNNING_LIMIT_HOURS_WARNING;
filter.pendingNextAction = {$ne: "stopVms"};
filter.pendingNextAction = { $ne: "stopVms" };
cb = doSendEmailWarning;
} else if ( type === "exec" ) {
limit = RUNNING_LIMIT_HOURS_STOP;
filter.pendingNextAction = "stopVms";
cb = doStop;
} else {
@@ -56,46 +77,69 @@ async function init(type) {
}
let provisions = await db.provision.get(filter);
let scenarios = await db.scenario.get();
await asyncForEach(provisions.results, async function(p) {
var _scenario = scenarios.results.filter(s=>{
return s.name === p.scenario
});
if ( _scenario.length ){
p._scenario = _scenario[0];
//Only if 24x7
let typeSchedule = "24x7";
let periodDays = p._scenarioDoc.allowed24x7RunningDays;
if ( !p.schedule || p.schedule.is24x7 ) {
// 24x7
timeRunningIs24x7(p);
periodDays = p._scenarioDoc.allowed24x7RunningDays;
} else if ( p.schedule && !p.schedule.is24x7 ) {
// OnSchedule
timeRunningOnSchedule2(p);
typeSchedule = 'OnSchedule';
periodDays = p._scenarioDoc.allowedOnScheduleRunningDays;
}
let limit;
if ( type === "warning" ) {
limit = 24*(periodDays-WARNING_DAYS);
} else if ( type === "exec" ) {
limit = 24*periodDays;
}
timeRunning(p);
if (!IS_REAL) {
console.log(`${p._id} - limit: ${limit} hs - actual duration: ${p.duration.hours} hs`);
console.log(`${p._id} - (${typeSchedule}) - limit: ${limit} hs - actual duration: ${p.duration.hours} hs`);
}
if ( p.duration && p.duration.hours >= limit) {
await cb(p);
await cb(p, limit, typeSchedule);
}
});
}
const doSendEmailWarning = async function(p) {
const doSendEmailWarning = async function(p, limit, typeSchedule) {
if ( p.pendingNextAction === 'stopVms') {
console.log(`Warning email Stop already sent. Wait for pending action to complete.`);
} else {
let msg = `Send warning STOP email - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenario.title}' (${p._id}) being 'Running' more than ${RUNNING_LIMIT_HOURS_WARNING} hours (exactly ${p.duration.complete})`;
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' });
await db.provision.update(p._id, {"pendingNextAction": "stopVms"});
await sendEmail.sendWillStopIn24(p, p._scenario);
await db.notification.add({ provision: p._id.toString(), type: 'warningStop', message: msg });
await db.notification.add({ provision: p._id.toString(), type: 'warningStop', message: msg });
await sendEmail.sendWillStopIn24(p, p._scenarioDoc, Math.floor(limit/24), WARNING_DAYS);
}
}
};
const doStop = async function(p) {
const doStop = async function(p, limit, typeSchedule) {
try {
let msg = `VMs stopped - ${p.user.displayName} (${p.user.upn}) about provision '${p._scenario.title}' (${p._id}) being 'Running' more than ${RUNNING_LIMIT_HOURS_STOP} hours (exactly ${p.duration.complete})`
let msg = `VMs stopped - ${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 ) {
await azurecli.deallocate(p, true);
if ( IS_REAL ) {
if (p.schedule){
//Disable Divvy
await db.schedule.update(p.schedule._id, {"isStartupTimeEnable": false});
await cli.updateVmsTags(p._id, {
"24x7": false,
"StartupTime": false,
"ShutdownTime": false
});
}
//Stop VMs indefinitely
db.event.add({ user: p.user._id, provision: p._id, type: 'vms.exec-stop' });
await cli.deallocate(p._id, true);
await db.notification.add({ provision: p._id.toString(), type: 'stop', message: msg });
}

View File

@@ -1,13 +1,15 @@
{
"name": "qmi-cloud-cli",
"version": "1.1.3",
"version": "1.3.0",
"scripts": {
},
"private": true,
"dependencies": {
"qmi-cloud-common": "../qmi-cloud-common",
"node-fetch": "^2.6.0",
"moment": "^2.24.0"
"moment": "^2.24.0",
"axios": "^0.21.1",
"fs-extra": "^8.1.0"
}
}

View File

@@ -5,18 +5,33 @@ helpFunction()
{
echo ""
echo "Usage: $0 -s script"
echo -e "\t-s Type of script ( checkstop | checkdestroy | initdb )"
echo -e "\t-s Type of script ( checkstop | checkdestroy | checkerror | movedestroyed | initdb )"
exit 1 # Exit script after printing help
}
helpFunctionCheck()
{
echo ""
echo "Usage: $0 -s script -t type -p period -r realortest"
echo -e "\t-s Type of script ( checkstop | checkdestroy | initdb )"
echo -e "\t-t Type of script (warning | exec)"
echo -e "\t-p Period (number of days)"
echo -e "\t-r Type of script (test | real)"
echo "Usage: $0 -s script [-t type -r realortest]"
echo -e "\t-s Type of script ( checkstop | checkdestroy | checkerror )"
echo -e "\t-t Type of script ( warning | exec )"
echo -e "\t-r Indicate whether is for test or real execution ( test | real )"
exit 1 # Exit script after printing help
}
helpFunctionCheck2()
{
echo ""
echo "Usage: $0 -s checkerror [-r realortest]"
echo -e "\t-r Indicate whether is for test or real execution ( test | real )"
exit 1 # Exit script after printing help
}
helpFunctionCheck3()
{
echo ""
echo "Usage: $0 -s movedestroyed [-r isodate]"
echo -e "\t-r An ISODATE (ie: '2020-31-12T00:00:00.000Z') to movedestroyed backwards"
exit 1 # Exit script after printing help
}
@@ -25,7 +40,6 @@ do
case "$opt" in
s ) script="$OPTARG" ;;
t ) type="$OPTARG" ;;
p ) period="$OPTARG" ;;
r ) realortest="$OPTARG" ;;
? ) helpFunction ;; # Print helpFunction in case parameter is non-existent
esac
@@ -33,25 +47,25 @@ done
if [ -z "$script" ]
then
echo "Some or all of the parameters are empty";
echo "Script not recognised";
helpFunction
fi
if [ "$script" = "checkstop" ] || [ "$script" = "checkdestroy" ]
then
if [ -z "$type" ] || [ -z "$period" ] || [ -z "$realortest" ]
if [ -z "$type" ] || [ -z "$realortest" ]
then
echo "Some or all of the parameters are empty";
helpFunctionCheck
else
if [ "$script" = "checkstop" ]
then
$BASEDIR/checkstop.sh $type $period $realortest
$BASEDIR/checkstop.sh $type $realortest
fi
if [ "$script" = "checkdestroy" ]
then
$BASEDIR/checkdestroy.sh $type $period $realortest
$BASEDIR/checkdestroy.sh $type $realortest
fi
fi
elif [ "$script" = "initdb" ]
@@ -64,5 +78,24 @@ then
echo "\$MONGO_URI=$MONGO_URI"
node $BASEDIR/../jobs/db-init.js
fi
elif [ "$script" = "checkerror" ]
then
if [ -z "$realortest" ]
then
echo "Some or all of the parameters are empty";
helpFunctionCheck2
else
node $BASEDIR/../jobs/error5.js $realortest
fi
elif [ "$script" = "movedestroyed" ]
then
if [ -z "$realortest" ]
then
echo "Some or all of the parameters are empty";
helpFunctionCheck3
else
node $BASEDIR/../jobs/move_destroyed.js $realortest
fi
fi

View File

@@ -3,8 +3,7 @@ d=`date`
echo "------ $d"
echo "------ SCRIPT: $0"
echo "------ TYPE: $1"
echo "------ PERIOD: $2"
echo "------ TEST/REAL: $3"
echo "------ TEST/REAL: $2"
if [ -z "$MONGO_URI" ]
then
@@ -22,4 +21,4 @@ else
echo "\$API_KEY=$API_KEY"
fi
node $BASEDIR/../jobs/destroy5.js $1 $2 $3
node $BASEDIR/../jobs/destroy5.js $1 $2

View File

@@ -0,0 +1,14 @@
BASEDIR=$(dirname "$0")
d=`date`
echo "------ $d"
echo "------ SCRIPT: $0"
if [ -z "$MONGO_URI" ]
then
echo "\$MONGO_URI is empty"
exit
else
echo "\$MONGO_URI=$MONGO_URI"
fi
node $BASEDIR/../jobs/error5.js $1

View File

@@ -3,8 +3,7 @@ d=`date`
echo "------ $d"
echo "------ SCRIPT: $0"
echo "------ TYPE: $1"
echo "------ PERIOD: $2"
echo "------ TEST/REAL: $3"
echo "------ TEST/REAL: $2"
if [ -z "$MONGO_URI" ]
then
@@ -22,4 +21,4 @@ else
echo "\$API_KEY=$API_KEY"
fi
node $BASEDIR/../jobs/stop5.js $1 $2 $3
node $BASEDIR/../jobs/stop5.js $1 $2

View File

@@ -2,6 +2,70 @@
# yarn lockfile v1
"@azure/abort-controller@^1.0.0":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd"
integrity sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==
dependencies:
tslib "^2.0.0"
"@azure/arm-compute@^15.0.0":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@azure/arm-compute/-/arm-compute-15.0.0.tgz#5d0d0c1db16adbb6db3d33ca376b120c68e6ae23"
integrity sha512-ElV2MuYZ+B2+ygMx2iiM/u3C7WDRruZjkXzfk5p2O+UbWxjG6j/686OH3T+VSDqmzg+47AnIOTLu2v0u0H8FOw==
dependencies:
"@azure/ms-rest-azure-js" "^2.0.1"
"@azure/ms-rest-js" "^2.0.4"
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"
integrity sha512-KUl+Nwn/Sm6Lw5d3U90m1jZfNSL087SPcqHLxwn2T6PupNKmcgsEbDjHB25gDvHO4h7pBsTlrdJAY7dz+Qk8GA==
dependencies:
"@azure/abort-controller" "^1.0.0"
tslib "^2.0.0"
"@azure/ms-rest-azure-env@^2.0.0":
version "2.0.0"
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":
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==
dependencies:
"@azure/core-auth" "^1.1.4"
"@azure/ms-rest-js" "^2.2.0"
tslib "^1.10.0"
"@azure/ms-rest-js@^2.0.4", "@azure/ms-rest-js@^2.2.0":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-2.2.3.tgz#8f0085f7768c69f17b3cdb20ce95728b452dc304"
integrity sha512-sXOhOu/37Tr8428f32Jwuwga975Xw64pYg1UeUwOBMhkNgtn5vUuNRa3fhmem+I6f8EKoi6hOsYDFlaHeZ52jA==
dependencies:
"@azure/core-auth" "^1.1.4"
"@types/node-fetch" "^2.3.7"
"@types/tunnel" "0.0.1"
abort-controller "^3.0.0"
form-data "^2.5.0"
node-fetch "^2.6.0"
tough-cookie "^3.0.1"
tslib "^1.10.0"
tunnel "0.0.6"
uuid "^3.3.2"
xml2js "^0.4.19"
"@azure/ms-rest-nodeauth@^3.0.7":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.0.7.tgz#73c399b0aef45c75104324b6617aa4e0a6c27875"
integrity sha512-7Q1MyMB+eqUQy8JO+virSIzAjqR2UbKXE/YQZe+53gC8yakm8WOQ5OzGfPP+eyHqeRs6bQESyw2IC5feLWlT2A==
dependencies:
"@azure/ms-rest-azure-env" "^2.0.0"
"@azure/ms-rest-js" "^2.0.4"
adal-node "^0.1.28"
"@hapi/boom@^9.1.0":
version "9.1.0"
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.0.tgz#0d9517657a56ff1e0b42d0aca9da1b37706fec56"
@@ -14,11 +78,38 @@
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010"
integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==
"@types/node-fetch@^2.3.7":
version "2.5.8"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb"
integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "14.14.35"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313"
integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==
"@types/node@^8.0.47":
version "8.10.61"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.61.tgz#d299136ce54bcaf1abaa4a487f9e4bedf6b0d393"
integrity sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==
"@types/tunnel@0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c"
integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==
dependencies:
"@types/node" "*"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
adal-node@^0.1.28:
version "0.1.28"
resolved "https://registry.yarnpkg.com/adal-node/-/adal-node-0.1.28.tgz#468c4bb3ebbd96b1270669f4b9cba4e0065ea485"
@@ -56,13 +147,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
async@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
integrity sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==
dependencies:
lodash "^4.14.0"
async@>=0.6.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
@@ -83,13 +167,12 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
azure-arm-compute@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/azure-arm-compute/-/azure-arm-compute-10.0.0.tgz#ce9ba2e4d6dd6b1174c34da2219700a8080f389b"
integrity sha512-ehafNtcMKI6c00FT+xhPWPtzhYgHCZ675TUsaJ1FJ2bSpznih5EUrpir/4w519T4zbFBigszhnRK6eBkl78I9g==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
ms-rest "^2.5.0"
ms-rest-azure "^2.5.5"
follow-redirects "^1.10.0"
bcrypt-pbkdf@^1.0.0:
version "1.0.2"
@@ -147,7 +230,7 @@ cluster-key-slot@^1.1.0:
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@@ -215,11 +298,6 @@ denque@^1.1.0, denque@^1.4.1:
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@@ -261,6 +339,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
event-target-shim@^5.0.0:
version "5.0.1"
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==
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@@ -286,11 +369,34 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
follow-redirects@^1.10.0:
version "1.13.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -300,6 +406,15 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^4.0.0"
universalify "^0.1.0"
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -317,6 +432,11 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -371,10 +491,10 @@ ioredis@^4.14.1:
redis-parser "^3.0.0"
standard-as-callback "^2.0.1"
is-buffer@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
is-callable@^1.1.4, is-callable@^1.1.5:
version "1.2.0"
@@ -400,11 +520,6 @@ is-regex@^1.0.5:
dependencies:
has-symbols "^1.0.1"
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
is-symbol@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
@@ -447,6 +562,13 @@ json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
optionalDependencies:
graceful-fs "^4.1.6"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -489,7 +611,7 @@ lodash.flatten@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
lodash@^4.14.0, lodash@^4.17.15:
lodash@^4.17.15:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -518,7 +640,7 @@ moment-timezone@^0.5.31:
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.21.0, moment@^2.22.2, moment@^2.24.0:
"moment@>= 2.9.0", moment@^2.24.0:
version "2.26.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
@@ -574,44 +696,6 @@ mquery@3.2.2:
safe-buffer "5.1.2"
sliced "1.0.1"
ms-rest-azure@^2.5.5:
version "2.6.0"
resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-2.6.0.tgz#2098efec529eecfa0c6e215b69143abcaba12140"
integrity sha512-J6386a9krZ4VtU7CRt+Ypgo9RGf8+d3gjMBkH7zbkM4zzkhbbMOYiPRaZ+bHZcfihkKLlktTgA6rjshTjF329A==
dependencies:
adal-node "^0.1.28"
async "2.6.0"
moment "^2.22.2"
ms-rest "^2.3.2"
request "^2.88.0"
uuid "^3.2.1"
ms-rest-azure@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-3.0.0.tgz#54d0341c2aeef7b9f17f2a46258788740b2f0ec5"
integrity sha512-cttN01/TtMDB4v3rt/WQ/slgffB6jcUYxcPzcL0VNSB+WFPE1j4y5ICNHMuD1RaNNekCYMI4Pv51BDQ/BXNq7Q==
dependencies:
adal-node "^0.1.28"
async "2.6.0"
moment "^2.22.2"
ms-rest "^2.3.2"
request "^2.88.0"
uuid "^3.2.1"
ms-rest@^2.3.2, ms-rest@^2.5.0:
version "2.5.4"
resolved "https://registry.yarnpkg.com/ms-rest/-/ms-rest-2.5.4.tgz#57b42299cf302e45d5e1a734220bf7d4a110167a"
integrity sha512-VeqCbawxRM6nhw0RKNfj7TWL7SL8PB6MypqwgylXCi+u412uvYoyY/kSmO8n06wyd8nIcnTbYToCmSKFMI1mCg==
dependencies:
duplexer "^0.1.1"
is-buffer "^1.1.6"
is-stream "^1.1.0"
moment "^2.21.0"
request "^2.88.0"
through "^2.3.8"
tunnel "0.0.5"
uuid "^3.2.1"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -707,13 +791,14 @@ punycode@^2.1.0, punycode@^2.1.1:
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qmi-cloud-common@../qmi-cloud-common:
version "1.1.2"
version "1.1.6"
dependencies:
"@azure/arm-compute" "^15.0.0"
"@azure/ms-rest-nodeauth" "^3.0.7"
"@hapi/boom" "^9.1.0"
azure-arm-compute "^10.0.0"
axios "^0.21.1"
bull "^3.11.0"
mongoose "^5.7.4"
ms-rest-azure "^3.0.0"
nodemailer "^6.4.2"
qs@~6.5.2:
@@ -756,7 +841,7 @@ regexp-clone@1.0.0, regexp-clone@^1.0.0:
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==
"request@>= 2.52.0", request@^2.88.0:
"request@>= 2.52.0":
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -817,6 +902,11 @@ saslprep@^1.0.0:
dependencies:
sparse-bitfield "^3.0.3"
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
semver@^5.1.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -905,10 +995,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
through@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tough-cookie@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
dependencies:
ip-regex "^2.1.0"
psl "^1.1.28"
punycode "^2.1.1"
tough-cookie@~2.5.0:
version "2.5.0"
@@ -918,6 +1012,16 @@ tough-cookie@~2.5.0:
psl "^1.1.28"
punycode "^2.1.1"
tslib@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
@@ -925,10 +1029,10 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.5.tgz#d1532254749ed36620fcd1010865495a1fa9d0ae"
integrity sha512-gj5sdqherx4VZKMcBA4vewER7zdK25Td+z1npBqpbDys4eJrLx+SlYjJvq1bDXs2irkuJM5pf8ktaEQVipkrbA==
tunnel@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
@@ -940,6 +1044,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf"
integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
@@ -962,7 +1071,7 @@ util.promisify@^1.0.1:
has-symbols "^1.0.1"
object.getownpropertydescriptors "^2.1.0"
uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.4.0:
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==
@@ -976,6 +1085,19 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
xml2js@^0.4.19:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
"xmldom@>= 0.1.x":
version "0.3.0"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a"

130
qmi-cloud-common/awscli.js Normal file
View File

@@ -0,0 +1,130 @@
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');
rgName = rgName + "-" + provision._id.toString();
return rgName;
}
function _getRegion(provision) {
let region = "eu-west-1";
if ( provision.deployOpts.location === 'East US') {
region = "us-east-1";
} else if ( provision.deployOpts.location === 'Southeast Asia' ) {
region = "ap-southeast-1";
}
return region;
}
function deallocate(provision, isSendEmailAfter) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
return new Promise((resolve, reject) => {
var ec2 = new AWS.EC2({apiVersion: '2016-11-15', region: region});
var params = {
DryRun: false,
Filters: [
{
Name: 'tag:Name',
Values: [ `fort-${provision._id}` ]
},
]
};
ec2.describeInstances(params, async function (err, data) {
if (err) {
console.log("AWSCLI# ERROR describing EC2s: "+rgName, err.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
reject(err);
} else if (data && data.Reservations && data.Reservations.length) {
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
console.log("AWSCLI# Stopping Ec2s ids...", ec2Ids);
params = {
DryRun: false,
InstanceIds: ec2Ids
};
ec2.stopInstances(params, async function(err1, data) {
if (err1) {
console.log("AWSCLI# ERROR stopping EC2s: "+rgName, err1.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
reject(err1);
} else {
console.log("AWSCLI# Ec2s stopped!");
await utils.afterStopVms( provision, isSendEmailAfter );
resolve(data);
}
});
} else {
console.log("AWSCLI# No Ec2 Instances found: "+rgName);
resolve(data);
}
});
});
}
function start(provision) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
return new Promise((resolve, reject) => {
var ec2 = new AWS.EC2({apiVersion: '2016-11-15', region: region});
var params = {
DryRun: false,
Filters: [
{
Name: 'tag:Name',
Values: [ `fort-${provision._id}` ]
},
]
};
ec2.describeInstances(params, async function (err, data) {
if (err) {
console.log("AWSCLI# ERROR describing EC2s: "+rgName, err.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
reject(err);
} else if (data && data.Reservations && data.Reservations.length) {
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
console.log("AWSCLI# Starting Ec2s ids...", ec2Ids);
params = {
DryRun: false,
InstanceIds: ec2Ids
};
ec2.startInstances(params, async function(err1, data) {
if (err1) {
console.log("AWSCLI# ERROR starting EC2s: "+rgName, err1.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
reject(err1);
} else {
console.log("AWSCLI# Ec2s started!");
await utils.afterStartVms( provision );
resolve(data);
}
});
} else {
console.log("AWSCLI# No Ec2 Instances found: "+rgName);
resolve(data);
}
});
});
}
module.exports.deallocate = deallocate;
module.exports.start = start;

View File

@@ -1,7 +1,8 @@
const MsRest = require('ms-rest-azure');
const computeManagementClient = require('azure-arm-compute');
const loginWithVmMSI = require("@azure/ms-rest-nodeauth").loginWithVmMSI;
const computeManagementClient = require("@azure/arm-compute").ComputeManagementClient;
const dnsManagementClient = require("@azure/arm-dns").DnsManagementClient;
const db = require("./mongo");
const sendEmail = require("./send-email");
const utils = require("./utils");
const SUBSCRIPTION_ID = "62ebff8f-c40b-41be-9239-252d6c0c8ad9";
@@ -18,10 +19,22 @@ async function _getClient(scenarioName) {
var scenario = await db.scenario.getOne({"name": scenarioName});
id = scenario.subscription? scenario.subscription.subsId : SUBSCRIPTION_ID;
}
var credentials = await MsRest.loginWithMSI({ port: 50342 });
console.log("Azure CLI authenticated", credentials);
return new computeManagementClient(credentials, id);
var credentials = await loginWithVmMSI({ port: 50342 });
//console.log("AzureCLI# authenticated", credentials);
return new computeManagementClient(credentials, id);
}
async function _getDNSClient(scenarioName) {
var id = SUBSCRIPTION_ID;
if (scenarioName){
var scenario = await db.scenario.getOne({"name": scenarioName});
id = scenario.subscription? scenario.subscription.subsId : SUBSCRIPTION_ID;
}
var credentials = await loginWithVmMSI({ port: 50342 });
//console.log("AzureCLI# authenticated", credentials);
return new dnsManagementClient(credentials, id);
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
@@ -30,65 +43,94 @@ async function asyncForEach(array, callback) {
}
async function deallocate(provision, isSendEmailAfter ) {
let rgName = _getRgName(provision);
console.log("Deallocating VMs for resource group: "+rgName);
var computeClient = await _getClient(provision.scenario);
let finalResult = await computeClient.virtualMachines.list(rgName);
if ( finalResult && finalResult.length > 0 ) {
db.provision.update(provision._id, {"statusVms": "Stopping"});
}
await asyncForEach(finalResult, async function(vm) {
await computeClient.virtualMachines.deallocate(rgName, vm.name);
});
console.log("AzureCLI# Deallocating VMs for resource group: "+rgName);
let timeRunning = db.utils.getNewTimeRunning(provision);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped", "timeRunning": timeRunning, "stoppedFrom": new Date(), "pendingNextAction": undefined});
if ( isSendEmailAfter && provision._scenario) {
await sendEmail.sendVMsStopped(provision, provision._scenario);
try {
var computeClient = await _getClient(provision.scenario);
let finalResult = await computeClient.virtualMachines.list(rgName);
if ( finalResult && finalResult.length > 0 ) {
db.provision.update(provision._id, {"statusVms": "Stopping"});
}
await asyncForEach(finalResult, async function(vm) {
await computeClient.virtualMachines.deallocate(rgName, vm.name);
});
await utils.afterStopVms(provision, isSendEmailAfter);
console.log("AzureCLI# All VMs DEALLOCATED for resource group: "+rgName);
} catch ( error ) {
console.log("AzureCLI# ERROR stopping VMs: "+rgName, error);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
}
console.log("All VMs DEALLOCATED for resource group: "+rgName);
}
async function start(provision){
let rgName = _getRgName(provision);
console.log("Starting VMs for resource group: "+rgName);
console.log("AzureCLI# Starting VMs for resource group: "+rgName);
var computeClient = await _getClient(provision.scenario);
let finalResult = await computeClient.virtualMachines.list(rgName);
try {
if ( finalResult && finalResult.length > 0 ) {
db.provision.update(provision._id, {"statusVms": "Starting"});
var computeClient = await _getClient(provision.scenario);
let finalResult = await computeClient.virtualMachines.list(rgName);
if ( finalResult && finalResult.length > 0 ) {
db.provision.update(provision._id, {"statusVms": "Starting"});
}
await asyncForEach(finalResult, async function(vm) {
await computeClient.virtualMachines.start(rgName, vm.name);
});
await utils.afterStartVms( provision );
console.log("AzureCLI# All VMs RUNNING for resource group: "+rgName);
} catch ( error ) {
console.log("AzureCLI# ERROR starting VMs: "+rgName, error);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
}
await asyncForEach(finalResult, async function(vm) {
await computeClient.virtualMachines.start(rgName, vm.name);
});
let countExtend = db.utils.getNewCountExtend(provision);
await db.provision.update(provision._id.toString(), {"statusVms": "Running", "runningFrom": new Date(), "countExtend": countExtend, "pendingNextAction": undefined});
console.log("All VMs RUNNING for resource group: "+rgName);
}
async function getResourceGroupVms(rgName){
var computeClient = await _getClient();
let computeClient = await _getClient();
return await computeClient.virtualMachines.list(rgName);
}
async function getAllVms(){
var computeClient = await _getClient();
return await computeClient.virtualMachines.listAll(rgName);
let computeClient = await _getClient();
console.log("AzureCLI# Retrieving all VMS from subscription");
return await computeClient.virtualMachines.listAll();
}
async function getAllVmsNext(nextLink){
let computeClient = await _getClient();
console.log("AzureCLI# Retrieving all VMS from subscription (next): " +nextLink);
return await computeClient.virtualMachines.listAllNext(nextLink)
}
async function updateVmsTags(provision, tagsEdit) {
async function updateVmsTags(provId, tagsEdit) {
if ( !tagsEdit ){ return {} ; }
let provision = await db.provision.getById(provId);
if ( !provision ){ return };
let rgName = _getRgName(provision);
console.log("Updating TAGS in VMs for resource group: "+rgName);
console.log("AzureCLI# Updating TAGS in VMs for resource group: "+rgName);
var computeClient = await _getClient(provision.scenario);
let finalResult = await computeClient.virtualMachines.list(rgName);
var computeClient;
var finalResult;
try {
computeClient = await _getClient(provision.scenario);
finalResult = await computeClient.virtualMachines.list(rgName);
} catch (e2) {
console.log(`AzureCLI# ERROR: Resource group '${rgName}' could not be found`, e2);
return {};
}
if (finalResult && finalResult.length > 0) {
var toDelete = [];
@@ -104,6 +146,8 @@ async function updateVmsTags(provision, tagsEdit) {
if ( toDelete.length > 0 || toAdd.length > 0 ) {
await asyncForEach(finalResult, async function(vm) {
var vmActualTags = JSON.stringify(vm.tags);
var tags = vm.tags;
toDelete.forEach(t=>{
delete tags[t];
@@ -111,11 +155,25 @@ async function updateVmsTags(provision, tagsEdit) {
toAdd.forEach(t=>{
tags[t.key] = t.value;
});
result[vm.name] = tags;
computeClient.virtualMachines.update(rgName, vm.name, {"tags": tags} );
tags["ProvId"] = provision._id.toString();
result[vm.name] = tags;
if ( JSON.stringify(tags) !== vmActualTags ) {
try {
console.log(`AzureCLI# setting new tags to VM ${vm.name} (${rgName})`);
await computeClient.virtualMachines.update(rgName, vm.name, {"tags": tags} );
} catch (e1) {
console.log(`AzureCLI# ERROR setting tags to VM ${vm.name} (${rgName})`, e1);
}
} else {
console.log(`AzureCLI# Same tags, no need to update tags for VM ${vm.name} (${rgName})`);
}
});
}
//db.event.add({ user: provision.user._id, provision: provision._id, type: 'provision.vms-tags-update' });
return result;
} else {
@@ -123,8 +181,53 @@ async function updateVmsTags(provision, tagsEdit) {
}
};
/**
* DNS Records
*/
const DNS_RESOURCE_GROUP = "QMI-Infra-DNS";
const DNS_ZONE_NAME = "qmi.qlik-poc.com";
async function getDNSRecords(type = "CNAME") {
let dnsClient = await _getDNSClient();
console.log(`AzureCLI# Retrieving '${type}' DNS records`);
return await dnsClient.recordSets.listByType(DNS_RESOURCE_GROUP, DNS_ZONE_NAME, type);
}
async function createDNSRecord(provision, cname, type = "CNAME"){
if ( !provision ) return;
let rgName = _getRgName(provision).toLowerCase();
let dnsClient = await _getDNSClient();
console.log(`AzureCLI# Creating '${type}' DNS record: ${rgName}.${DNS_ZONE_NAME} --> ${cname}`);
let parameters = {
"cnameRecord": {
"cname": cname
},
"tTL": 60
};
return await dnsClient.recordSets.createOrUpdate(DNS_RESOURCE_GROUP, DNS_ZONE_NAME, rgName, type, parameters );
}
async function deleteDNSRecord(provision, type = "CNAME"){
if ( !provision ) return;
let rgName = _getRgName(provision).toLowerCase();
let dnsClient = await _getDNSClient();
console.log(`AzureCLI# Deleting '${type}' DNS record: ${rgName}.${DNS_ZONE_NAME}`);
return await dnsClient.recordSets.deleteMethod(DNS_RESOURCE_GROUP, DNS_ZONE_NAME, rgName, type );
}
module.exports.start = start;
module.exports.deallocate = deallocate;
module.exports.getResourceGroupVms = getResourceGroupVms;
module.exports.getAllVms = getAllVms;
module.exports.updateVmsTags = updateVmsTags;
module.exports.getAllVmsNext = getAllVmsNext;
module.exports.updateVmsTags = updateVmsTags;
module.exports.getDNSRecords = getDNSRecords;
module.exports.createDNSRecord = createDNSRecord;
module.exports.deleteDNSRecord = deleteDNSRecord

View File

@@ -0,0 +1,85 @@
const BarracudaAPI = require('barracuda-api');
const azurecli = require('./azurecli');
const db = require('./mongo');
const DOMAIN = "qmi.qlik-poc.com";
async function createApp(prov) {
if ( !prov._scenarioDoc.barracudaTemplate || prov._scenarioDoc.barracudaTemplate === 'qs-only-443' ) {
_createApp(prov, "443", "HTTPS");
} else if ( prov._scenarioDoc.barracudaTemplate === 'qdc-only-8443' ) {
_createApp(prov, "8443", "HTTPS");
} else if (prov._scenarioDoc.barracudaTemplate) {
var spl = prov._scenarioDoc.barracudaTemplate.split("-");
_createApp(prov, spl[0], spl[1]);
}
}
async function _createApp(provision, backendPort, backendType) {
if ( !provision || !provision.barracudaAzureFqdn ) {
console.log(`Barracuda# Provision does not exist or does not have a barracudaAzureFqdn value. Do nothing.`);
return;
}
let appName = provision.scenario.toLowerCase();
appName = appName.replace(/azqmi/g, 'qmi');
appName = `${appName}-${provision._id}`
console.log(`Barracuda# Creating Barracuda App for provision (${appName})`);
const client = new BarracudaAPI.QlikSenseNPrintingApp();
client.create(appName, `${appName}.${DOMAIN}`, provision.barracudaAzureFqdn, backendPort, backendType).then(function(result) {
let bApp = result.app;
console.log("Barracuda# Barracuda App created! ID: "+ bApp.id);
let cname = bApp.waas_services && bApp.waas_services.length? bApp.waas_services[0].cname : null;
db.provision.update(provision._id, {"barracudaAppId": bApp.id, "barracudaAppCname":cname});
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' });
}).catch(function(err){
console.log("Barracuda# Error creating Barracuda App", err);
});
}
async function deleteApp(provision) {
if ( !provision || !provision.barracudaAppId ) {
console.log(`Barracuda# Provision does not exist or does not have a barracudaAppId value. Do nothing.`);
return;
}
console.log(`Barracuda# Deleting DNS record in Azure and Barracuda App ID (${provision.barracudaAppId}) for provision (${provision._id})`);
azurecli.deleteDNSRecord(provision);
const client = new BarracudaAPI.QlikSenseNPrintingApp();
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' });
}).catch(function(err){
console.log("Barracuda# Error deleting Barracuda App", err);
});
}
async function getApp(provision) {
if ( !provision || !provision.barracudaAppId ) {
console.log(`Barracuda# Provision does not exist or does not have a barracudaAppId value. Do nothing.`);
return;
}
const client = new BarracudaAPI.QlikSenseNPrintingApp();
return await client.getDetails(provision.barracudaAppId);
}
module.exports.getApp = getApp;
module.exports.createApp = createApp;
module.exports.deleteApp = deleteApp;

47
qmi-cloud-common/cli.js Normal file
View File

@@ -0,0 +1,47 @@
const awscli = require("./awscli");
const azurecli = require("./azurecli");
const db = require("./mongo");
async function deallocate(provId, isSendEmailAfter ) {
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
if (provision.scenario === 'azqmi-fort'){
return awscli.deallocate(provision, isSendEmailAfter);
} else {
return azurecli.deallocate(provision, isSendEmailAfter);
}
} catch (err) {
console.log("CLI# ERROR stopping VMs", err);
}
}
async function start(provId){
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
if (provision.scenario === 'azqmi-fort'){
return awscli.start(provision);
} else {
return azurecli.start(provision);
}
} catch (err) {
console.log("CLI# ERROR starting VMs", err);
}
}
async function updateVmsTags(provId, tagsEdit) {
try {
return azurecli.updateVmsTags(provId, tagsEdit);
} catch (err) {
console.log("CLI# ERROR updateVmsTags", err);
}
}
module.exports.deallocate = deallocate;
module.exports.start = start;
module.exports.updateVmsTags = updateVmsTags;

View File

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

View File

@@ -8,6 +8,9 @@ const schema = new mongoose.Schema({
user: {
type: mongoose.Types.ObjectId, ref: 'User'
},
description: {
type: String
},
created: {
type: Date,
default: Date.now,

View File

@@ -0,0 +1,31 @@
const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false);
const crypto = require("crypto");
//mongoose.set('debug', true)
const schema = new mongoose.Schema({
created: {
type: Date,
default: Date.now,
index : true
},
user: {
type: mongoose.Types.ObjectId,
ref: 'User',
index : true
},
provision: {
type: mongoose.Types.ObjectId,
ref: 'Provision'
},
type: {
type: String
},
message: {
type: String
}
});
module.exports = mongoose.model('Event', schema)

View File

@@ -4,7 +4,8 @@ mongoose.set('useFindAndModify', false);
const provisionSchema = new mongoose.Schema({
user: {
type: mongoose.Types.ObjectId, ref: 'User',
type: mongoose.Types.ObjectId,
ref: 'User',
index: true
},
created: {
@@ -38,6 +39,15 @@ const provisionSchema = new mongoose.Schema({
type: Boolean,
default: false
},
barracudaAppId: {
type: String
},
barracudaAppCname: {
type: String
},
barracudaAzureFqdn: {
type: String
},
isDestroyed: {
type: Boolean,
default: false
@@ -66,6 +76,12 @@ const provisionSchema = new mongoose.Schema({
type: Number,
default: 0
},
startDateOnSchedule: {
type: Date
},
endDateOnSchedule: {
type: Date
},
countExtend: {
type: Number,
default: 0
@@ -73,8 +89,23 @@ const provisionSchema = new mongoose.Schema({
pendingNextAction: {
type: String
},
autoShutdown: {
type: mongoose.Types.ObjectId, ref: 'ProvisionAutoShutdown'
schedule: {
type: mongoose.Types.ObjectId,
ref: 'Schedule'
},
deployOpts: {
type: mongoose.Types.ObjectId,
ref: "Subscription"
},
terraformImage: {
type: String
},
version: {
type: Number
},
parent: {
type: mongoose.Types.ObjectId,
ref: 'Provision'
}
},{
toObject: {virtuals:true},

View File

@@ -31,7 +31,7 @@ const scenarioSchema = new mongoose.Schema({
type: Boolean,
default: false
},
isExterrequirednal: {
isExternal: {
type: Boolean,
default: false
},
@@ -39,6 +39,9 @@ const scenarioSchema = new mongoose.Schema({
type: Boolean,
default: false
},
numSimultaneousProvisions: {
type: Number
},
isDisabled: {
type: Boolean,
default: false
@@ -47,13 +50,47 @@ const scenarioSchema = new mongoose.Schema({
type: String,
required: true
},
gitBranch: {
type: String
},
description: String,
availableProductVersions: Array,
productVersionDefault: String,
newImageName: String, //For Gen scenarios
subscription: {
type: mongoose.Types.ObjectId, ref: 'Subscription',
type: mongoose.Types.ObjectId,
ref: 'Subscription'
},
deployOpts: [{
type: mongoose.Types.ObjectId,
ref: 'Subscription',
required: true
}],
labels: [{
type: String
}],
allowedUsers: [{
type: mongoose.Types.ObjectId,
ref: 'User',
required: true
}],
support: [{
type: String
}],
barracudaTemplate: {
type: String
},
allowedInnactiveDays: {
type: Number,
default: 20
},
allowed24x7RunningDays: {
type: Number,
default: 7
},
allowedOnScheduleRunningDays: {
type: Number,
default: 4
}
});

View File

@@ -9,12 +9,19 @@ const sc = new mongoose.Schema({
required: true,
default: true
},
isStartupTimeEnable: {
type: Boolean,
default: true
},
localeShutdownTime: {
type: String
},
localeStartupTime: {
type: String
},
localTimezone: {
type: String
},
utcTagShutdownTime: {
type: String
},
@@ -31,4 +38,4 @@ const sc = new mongoose.Schema({
});
module.exports = mongoose.model('ProvisionAutoShutdown', sc)
module.exports = mongoose.model('Schedule', sc)

View File

@@ -0,0 +1,36 @@
const mongoose = require('mongoose')
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const schema = new mongoose.Schema({
created: {
type: Date,
default: Date.now,
index : true
},
updated: {
type: Date,
default: Date.now
},
user: {
type: mongoose.Types.ObjectId,
ref: 'User',
index: true
},
provision: {
type: mongoose.Types.ObjectId,
ref: 'Provision',
index: true
},
sharedWithUser: {
type: mongoose.Types.ObjectId,
ref: 'User',
index: true
},
canManage: {
type: Boolean,
default: false
}
});
module.exports = mongoose.model('sharedProvision', schema);

View File

@@ -30,6 +30,15 @@ const subSchema = new mongoose.Schema({
},
appGwSubnetId: {
type: String
},
location: {
type: String
},
wafPolicyName: {
type: String
},
wafPolicyRgName: {
type: String
}
});

View File

@@ -26,6 +26,12 @@ const userSchema = new mongoose.Schema({
lastLogin: {
type: Date
},
qcsUserId: {
type: String
},
qcsUserSubject: {
type: String
}
});

View File

@@ -9,7 +9,7 @@ const options = {
useUnifiedTopology: true
};
console.log("--- MongoDB connecting...");
console.log("--- MongoDB connecting... ", process.env.MONGO_URI);
// Connect to DB
mongoose.connect(process.env.MONGO_URI, options);
@@ -34,7 +34,7 @@ mongoose.connection.on('error', (err) => {
// Get Data Models
const Scenario = require('./models/Scenario');
const ProvisionAutoShutdown = require('./models/ProvisionAutoShutdown');
const Schedule = require('./models/Schedule');
const Provision = require('./models/Provision');
const Destroy = require('./models/Destroy');
const User = require('./models/User');
@@ -42,6 +42,8 @@ const VmType = require('./models/VmType');
const ApiKey = require('./models/ApiKey');
const Notification = require('./models/Notification');
const Subscription = require('./models/Subscription');
const Event = require('./models/Event');
const SharedProvision = require('./models/SharedProvision');
const getNewCountExtend = function(provision) {
@@ -64,13 +66,83 @@ const getNewTimeRunning = function (provision) {
};
const get = async (model, filter, select, skip, limit, populates, reply) => {
const getPage = async ( model, filter, page, populates, select ) => {
var sort = {};
var modelAttributes = Object.keys(model.schema.tree);
if ( modelAttributes.indexOf("created") !== -1) {
sort = {created: -1};
}
try {
var exec = model.find(filter, select).sort(sort);
var totalDocs = await model.countDocuments(filter);
var isPage = false;
if ( page && page.page > 0 && page.size !== undefined ) {
var skip = 0;
skip = (page.page - 1) * page.size;
exec = exec.skip(skip);
exec = exec.limit(page.size);
isPage = true;
}
if ( populates ) {
populates = JSON.parse(populates);
populates.forEach(p=> {
exec = exec.populate(p);
});
} else {
if ( model === Provision ) {
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'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
exec = exec.populate('subscription').populate('deployOpts');
}
}
const entity = await exec;
var out = {
total: totalDocs,
count: entity.length,
results: entity
}
if ( isPage && (page.page * page.size < totalDocs)) {
out.next = {
page: page.page + 1,
size: page.size
};
}
return out;
} catch (err) {
throw boom.boomify(err)
}
};
const get = async (model, filter, select, skip, limit, populates, reply) => {
var sort = {};
var modelAttributes = Object.keys(model.schema.tree);
if ( model === Scenario ) {
if ( modelAttributes.indexOf("updated") !== -1) {
sort.updated = -1;
}
}
if ( modelAttributes.indexOf("created") !== -1) {
sort.created = -1;
}
try {
var exec = model.find(filter, select).sort(sort);
var totalDocs = await model.countDocuments(filter);
@@ -90,7 +162,19 @@ const get = async (model, filter, select, skip, limit, populates, reply) => {
});
} else {
if ( model === Provision ) {
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "autoShutdown"});
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({path:'provision', populate: [{
path: 'schedule',
},{
path: '_scenarioDoc',
select: "-availableProductVersions -updated -created"
},{
path: 'destroy',
select: "-user -jobId"
}]}).populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
@@ -98,7 +182,7 @@ const get = async (model, filter, select, skip, limit, populates, reply) => {
}
if ( model = Scenario ) {
exec = exec.populate('subscription');
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
}
}
@@ -123,13 +207,16 @@ const getById = async (model, id, reply) => {
try {
var exec = model.findById(id);
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("autoShutdown");
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'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
exec = exec.populate('subscription');
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
}
const entity = await exec;
return entity;
@@ -142,13 +229,16 @@ const getOne = async (model, filter, reply) => {
try {
var exec = model.findOne(filter);
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("autoShutdown");
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'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
exec = exec.populate('subscription');
exec = exec.populate('subscription').populate('deployOpts').populate('allowedUsers');
}
const entity = await exec;
return entity;
@@ -170,16 +260,19 @@ const update = async (model, id, body, reply) => {
try {
const { ...updateData } = body;
updateData.updated = new Date();
console.log("UPDATE", id, updateData);
//console.log("UPDATE", id, updateData);
var exec = model.findByIdAndUpdate(id, updateData, { new: true });
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("autoShutdown");
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'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
exec = exec.populate('subscription');
exec = exec.populate('subscription').populate('deployOpts');
}
const update = await exec;
return update;
@@ -188,10 +281,54 @@ const update = async (model, id, body, reply) => {
}
};
const updateMany = async (model, filter, body, reply) => {
try {
const { ...updateData } = body;
updateData.updated = new Date();
var exec = model.updateMany(filter, updateData);
if ( model === Provision ) {
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'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
if ( model = Scenario ) {
exec = exec.populate('subscription').populate('deployOpts');
}
return await exec;
} catch (err) {
throw boom.boomify(err)
}
};
const del = async (model, id, reply) => {
try {
const entity = await model.findByIdAndRemove(id)
return entity;
return await model.findByIdAndRemove(id);
} catch (err) {
throw boom.boomify(err)
}
}
const delMany = async(model, filter, reply) => {
try {
return await model.deleteMany(filter);
} catch (err) {
throw boom.boomify(err)
}
}
const count = async (model, filter, reply) => {
try {
var totalDocs = await model.countDocuments(filter);
return totalDocs;
} catch (err) {
throw boom.boomify(err)
}
@@ -202,6 +339,9 @@ function _m(model) {
get: async (filter, select, skip, limit, populates, reply) => {
return get(model, filter, select, skip, limit, populates, reply);
},
getPage: async (filter, page, populates, select, reply) => {
return getPage(model, filter, page, populates, select, reply);
},
getById: async (id, reply) => {
return getById(model, id, reply);
},
@@ -214,15 +354,24 @@ function _m(model) {
update: async (id, data, reply) => {
return update(model, id, data, reply);
},
updateMany: async(filter, data, reply) => {
return updateMany(model, filter, data, reply);
},
del: async (id, reply) => {
return del(model, id, reply);
},
count: async (filter, reply) => {
return count(model, filter, reply);
},
delMany: async(filter, reply) => {
return delMany(model, filter, reply);
}
}
}
module.exports = {
provisionAutoShutdown: _m(ProvisionAutoShutdown),
schedule: _m(Schedule),
provision: _m(Provision),
destroy: _m(Destroy),
scenario: _m(Scenario),
@@ -230,7 +379,9 @@ module.exports = {
apiKey: _m(ApiKey),
notification: _m(Notification),
subscription: _m(Subscription),
event: _m(Event),
user: _m(User),
sharedProvision: _m(SharedProvision),
utils: {
getNewTimeRunning: getNewTimeRunning,
getNewCountExtend: getNewCountExtend
@@ -241,10 +392,13 @@ module.exports = {
Destroy: Destroy,
Scenario: Scenario,
User: User,
Schedule: Schedule,
VmType: VmType,
Notification: Notification,
ApiKey: ApiKey,
Subscription: Subscription
Subscription: Subscription,
Event: Event,
SharedProvision: SharedProvision
}
};

1315
qmi-cloud-common/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,17 @@
{
"name": "qmi-cloud-common",
"version": "1.1.4",
"version": "1.1.6",
"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#0.0.10",
"bull": "^3.11.0",
"mongoose": "^5.7.4",
"nodemailer": "^6.4.2",
"azure-arm-compute": "^10.0.0",
"ms-rest-azure": "^3.0.0",
"bull": "^3.11.0"
"uuid": "^8.3.2"
}
}
}

View File

@@ -4,24 +4,27 @@ const Queue = require('bull');
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';
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);
export const queues = {
[TF_APPLY_QUEUE]: terraformApplyQueue,
[TF_DESTROY_QUEUE]: terraformDestroyQueue,
[TF_APPLY_QSEOK_QUEUE]: terraformApplyQseokQueue
[TF_APPLY_QSEOK_QUEUE]: terraformApplyQseokQueue,
[STOP_CONTAINER_QUEUE]: stopContainerQueue
};
for (let key in queues) {
queues[key].on('completed', function(job, result) {
console.log(`Job ${job.id} completed! Result`, result);
//console.log(`Queues# Job ${job.id} completed! Result`, result);
console.log(`Queue ${key}# Job ${job.id} completed!`);
});
@@ -30,13 +33,13 @@ for (let key in queues) {
});
queues[key].on('waiting', function(jobId){
console.log(`Job ${jobId} is waiting...`);
console.log(`Queue ${key}# Job ${jobId} is waiting...`);
// A Job is waiting to be processed as soon as a worker is idling.
});
queues[key].on('active', function(job, jobPromise){
// A job has started. You can use `jobPromise.cancel()`` to abort it.
console.log(`Job ${job.id} is in active`);
console.log(`Queue ${key}# Job ${job.id} is now active`);
});
queues[key].on('stalled', function(job){
@@ -46,12 +49,12 @@ for (let key in queues) {
queues[key].on('progress', function(job, progress){
// A job's progress was updated!
console.log(`Job ${job.id} is ${progress * 100}% ready!`);
console.log(`Queue ${key}# Job ${job.id} is ${progress * 100}% ready!`);
});
queues[key].on('failed', function(job, err){
// A job failed with reason `err`!
console.log(`Job ${job.id} has failed:`, err);
console.log(`Queue ${key}# Job ${job.id} has failed:`, err);
});
}

View File

@@ -0,0 +1,278 @@
'use strict';
const nodemailer = require('nodemailer');
const FROM = '"Qlik" <no-reply@qlik.com>';
var transporter;
const HOSTNAME_URL = process.env.HOSTNAME_URL || "https://qmicloud.qliktech.com";
const FOOTER = `<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
</div>`;
if ( process.env.GMAIL_USERNAME && process.env.GMAIL_PASSWORD ) {
//GMAIL
transporter = nodemailer.createTransport({
service: 'gmail',
port: 587,
secure: false,
auth: {
user: process.env.GMAIL_USERNAME,
pass: process.env.GMAIL_PASSWORD
}
});
} else {
//QLIK
transporter = nodemailer.createTransport({
host: 'smtp.qliktech.com',
port: 587,
secure: false, // true for 465, false for other ports
});
}
async function _doSend(to, subject, htmlText) {
// send mail with defined transport object
try {
let info = await transporter.sendMail( {
from: FROM, // sender address
to: to, // list of receivers
subject: subject, // Subject line
text: subject, // plain text body
html: htmlText // html body
} );
console.log('SendEmail# message id ('+info.messageId+') sent to: ' + to);
return info;
} catch (err) {
console.log('SendEmail# ERROR!! -> could not send email to: ' + to);
console.log(err);
}
}
function _getCommonDetails(provision, scenario){
var description = decodeURI(scenario.description);
var externalAccess = provision.isExternalAccess? 'Yes' : 'No';
var schedule = "";
if ( !provision.schedule || provision.schedule.is24x7 ) {
schedule = "24x7";
} else if ( provision.schedule && !provision.schedule.is24x7 ) {
schedule = `from ${provision.schedule.localeStartupTime}h until ${provision.schedule.localeShutdownTime}h (${provision.schedule.localTimezone})`;
}
return `<div style="color:#404040;font-size:18px;margin:20px 0px">
<p style="margin:0px">Provision information:</p>
</div>
<div>
<span style="color:#404040">ID: </span> ${provision._id}
</div>
<div>
<span style="color:#404040">VMs Running schedule: </span> ${schedule}
</div>
<div>
<span style="color:#404040">Purpose: </span> ${provision.description}
</div>
<div>
<span style="color:#404040">Scenario: </span> ${scenario.title}
</div>
<div>
<span style="color:#404040">With external access: </span> ${externalAccess}
</div>
<div>
<span style="color:#404040">Description: </span> ${description}
</div>`;
}
function getHtmlScenarioDestroyIn24( 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}' inactive more than ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario will be automatically DESTROYED in ${(warningDays*24)} hours.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">If you don't want this to happen, you've got ${(warningDays*24)} hours (from when this email was sent) as a grace period to get back at 'Running' status this provision.</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlScenarioVMsStopped( 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:10px 0px">
<p style="margin:0px;color: #FF2020">All VMs for this provision <b>stopped</b> automatically.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">You can start them up again from <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></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">
<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}' - VMs running for ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario will automatically stop its VMs in ${warningDays*24} hours.</p>
</div>
<div style="color:#404040;font-size:18px;margin:20px 0px 10px 0px">
<p style="margin:0px;color: #FF2020">Take action and extend the period ${(period+warningDays)} extra days.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">If you don't want the VMs to automatically stop, you've got ${warningDays*24} hours (from when this email was sent) as a grace period to extend this scenario's <b style="color: #009845">Running</b> VMs for ${(period+warningDays)} extra days.</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlNewProvision(provision, scenario) {
var htmlint;
if ( provision && provision.outputs ) {
htmlint = `<div style="color:#404040;font-size:18px;padding: 10px 0px;">Connection resources</div>`;
} 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>`;
}
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">
<p style="margin:0px">Scenario '${scenario.title}' successfully provisioned!</p>
</div>
${common}
<div style="margin: 30px 0px;">
${htmlint}
</div>
${FOOTER}
</div>
</div>`;
}
function getHtmlErrorProvision(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:20px;margin:20px 0px">
<p style="margin:0px;color: #FF2020">Oops! Something didn't work.</p>
</div>
<div style="color:#404040;font-size:20px;margin:20px 0px 50px 0px">
<p style="margin:0px">Scenario '${scenario.title}' failed during provision.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Please, follow these steps:</p>
<ul>
<li>Reach out the person responsible for this scenario for support.</li>
<li>As soon as it's possible, consider destroy this provision since it's taking valuable cloud resources which might imply relevant cost.</li>
</ul>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
function getHtmlDestroyProvision(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:20px;margin:40px 0px">
<p style="margin:0px">Scenario '${scenario.title}' successfully destroyed!</p>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
// async..await is not allowed in global scope, must use a wrapper
async function sendProvisionSuccess( provision, scenario ) {
const htmlText = getHtmlNewProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision finished successfully', htmlText);
}
async function sendProvisionError(provision, scenario ) {
const htmlText = getHtmlErrorProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision with errors', htmlText);
}
async function sendDestroyedSuccess(provision, scenario ) {
const htmlText = getHtmlDestroyProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision destroyed successfully', htmlText);
}
async function sendWillStopIn24( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - VMs will stop in ${warningDays*24} hours`, htmlText);
}
async function sendWillDestroyIn24( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${(warningDays*24)} hours`, htmlText);
}
async function sendVMsStopped( provision, scenario ) {
const htmlText = getHtmlScenarioVMsStopped( provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - VMs stopped automatically', htmlText);
}
module.exports.sendProvisionSuccess = sendProvisionSuccess;
module.exports.sendProvisionError = sendProvisionError;
module.exports.sendDestroyedSuccess = sendDestroyedSuccess;
module.exports.sendWillStopIn24 = sendWillStopIn24;
module.exports.sendVMsStopped = sendVMsStopped;
module.exports.sendWillDestroyIn24 = sendWillDestroyIn24;
module.exports._doSend = _doSend;

View File

@@ -1,55 +1,55 @@
'use strict';
const nodemailer = require('nodemailer');
const FROM = '"Qlik" <no-reply@qlik.com>';
var transporter;
const axios = require('axios');
const https = require("https");
const SMTP_EMAIL_SENDER = process.env.SMTP_EMAIL_SENDER;
const RUNNING_PERIOD = 4;
const RUNNING_PERIOD_WARNING_DAYS = 1;
const STOP_PERIOD = 20;
const STOP_PERIOD_WARNING_DAYS = 2;
const HOSTNAME_URL = process.env.HOSTNAME_URL || "https://qmicloud.qliktech.com";
const FOOTER = `<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
</div>`;
const RUNNING_PERIOD_WARNING_HOURS = RUNNING_PERIOD_WARNING_DAYS*24;
const STOP_PERIOD_WARNING_HOURS = STOP_PERIOD_WARNING_DAYS*24;
if ( process.env.GMAIL_USERNAME && process.env.GMAIL_PASSWORD ) {
//GMAIL
transporter = nodemailer.createTransport({
service: 'gmail',
port: 587,
secure: false,
auth: {
user: process.env.GMAIL_USERNAME,
pass: process.env.GMAIL_PASSWORD
}
});
} else {
//QLIK
transporter = nodemailer.createTransport({
host: 'smtp.qliktech.com',
port: 587,
secure: false, // true for 465, false for other ports
});
}
async function _doSend(to, subject, htmlText) {
// send mail with defined transport object
let info = await transporter.sendMail( {
from: FROM, // sender address
to: to, // list of receivers
subject: subject, // Subject line
text: subject, // plain text body
html: htmlText // html body
} );
try {
var body = {
"to": to,
"subject": subject,
"html": htmlText
};
await axios({
url: SMTP_EMAIL_SENDER,
method: "post",
data: body,
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
});
console.log('SendEmail (Qlik SMTP)# message sent to: ' + to);
console.log('Provision Email ('+info.messageId+') sent to: ' + to);
} catch (err) {
// Handle Error Here
console.log("SendEmail (Qlik SMTP) _doSend error: could not send the email to: " +to);
}
}
function _getCommonDetails(provision, scenario){
var description = decodeURI(scenario.description);
var externalAccess = provision.isExternalAccess? 'Yes' : 'No';
var schedule = "";
if ( !provision.schedule || provision.schedule.is24x7 ) {
schedule = "24x7";
} else if ( provision.schedule && !provision.schedule.is24x7 ) {
schedule = `from ${provision.schedule.localeStartupTime}h until ${provision.schedule.localeShutdownTime}h (${provision.schedule.localTimezone})`;
}
return `<div style="color:#404040;font-size:18px;margin:20px 0px">
<p style="margin:0px">Provision information:</p>
</div>
<div>
<span style="color:#404040">ID: </span> ${provision._id}
</div>
<div>
<span style="color:#404040">VMs Running schedule: </span> ${schedule}
</div>
<div>
<span style="color:#404040">Purpose: </span> ${provision.description}
</div>
@@ -57,14 +57,14 @@ function _getCommonDetails(provision, scenario){
<span style="color:#404040">Scenario: </span> ${scenario.title}
</div>
<div>
<span style="color:#404040">Description: </span> ${scenario.description}
<span style="color:#404040">With external access: </span> ${externalAccess}
</div>
<div>
<span style="color:#404040">ProvisionID: </span> ${provision._id}
<span style="color:#404040">Description: </span> ${description}
</div>`;
}
function getHtmlScenarioDestroyIn24( provision, scenario) {
function getHtmlScenarioDestroyIn24( 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">
@@ -72,18 +72,16 @@ function getHtmlScenarioDestroyIn24( provision, scenario) {
<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}' inactive more than ${(STOP_PERIOD - STOP_PERIOD_WARNING_DAYS)} days</p>
<p style="margin:0px">Provision '${scenario.title}' inactive more than ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario will be automatically DESTROYED in ${STOP_PERIOD_WARNING_HOURS} hours.</p>
<p style="margin:0px;color: #FF2020">This scenario will be automatically DESTROYED in ${(warningDays*24)} hours.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">If you don't want this to happen, you've got ${STOP_PERIOD_WARNING_HOURS} hours (from when this email was sent) as a grace period to get back at 'Running' status this provision.</p>
<p style="margin:0px">If you don't want this to happen, you've got ${(warningDays*24)} hours (from when this email was sent) as a grace period to get back at 'Running' status this provision.</p>
</div>
${common}
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
</div>
${FOOTER}
</div>
</div>`;
}
@@ -103,17 +101,15 @@ function getHtmlScenarioVMsStopped( provision, scenario) {
<p style="margin:0px;color: #FF2020">All VMs for this provision <b>stopped</b> automatically.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">You can start them up again from <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
<p style="margin:0px">You can start them up again from <a href="${HOSTNAME_URL}">${HOSTNAME_URL}</a></p>
</div>
${common}
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
</div>
${FOOTER}
</div>
</div>`;
}
function getHtmlScenarioWillStopIn24( provision, scenario ) {
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">
<div style="background-color:white;height:100%;padding:20px 10px">
@@ -121,21 +117,19 @@ function getHtmlScenarioWillStopIn24( provision, scenario ) {
<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}' - VMs running for ${(RUNNING_PERIOD - RUNNING_PERIOD_WARNING_DAYS)} days</p>
<p style="margin:0px">Provision '${scenario.title}' - VMs running for ${period} days</p>
</div>
<div style="color:#404040;font-size:18px;margin:10px 0px">
<p style="margin:0px;color: #FF2020">This scenario will automatically stop its VMs in ${RUNNING_PERIOD_WARNING_HOURS} hours.</p>
<p style="margin:0px;color: #FF2020">This scenario will automatically stop its VMs in ${warningDays*24} hours.</p>
</div>
<div style="color:#404040;font-size:18px;margin:20px 0px 10px 0px">
<p style="margin:0px;color: #FF2020">Take action and extend the period ${RUNNING_PERIOD} extra days.</p>
<p style="margin:0px;color: #FF2020">Take action and extend the period ${(period+warningDays)} extra days.</p>
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">If you don't want the VMs to automatically stop, you've got ${RUNNING_PERIOD_WARNING_HOURS} hours (from when this email was sent) as a grace period to extend this scenario's <b style="color: #009845">Running</b> VMs for ${RUNNING_PERIOD} extra days.</p>
<p style="margin:0px">If you don't want the VMs to automatically stop, you've got ${warningDays*24} hours (from when this email was sent) as a grace period to extend this scenario's <b style="color: #009845">Running</b> VMs for ${(period+warningDays)} extra days.</p>
</div>
${common}
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
</div>
${FOOTER}
</div>
</div>`;
}
@@ -169,9 +163,7 @@ function getHtmlNewProvision(provision, scenario) {
<div style="margin: 30px 0px;">
${htmlint}
</div>
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
</div>
${FOOTER}
</div>
</div>`;
@@ -190,10 +182,15 @@ function getHtmlErrorProvision(provision, scenario) {
<div style="color:#404040;font-size:20px;margin:20px 0px 50px 0px">
<p style="margin:0px">Scenario '${scenario.title}' failed during provision.</p>
</div>
${common}
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
<p style="margin:0px">Please, follow these steps:</p>
<ul>
<li>Reach out the person responsible for this scenario for support.</li>
<li>As soon as it's possible, consider destroy this provision since it's taking valuable cloud resources which might imply relevant cost.</li>
</ul>
</div>
${common}
${FOOTER}
</div>
</div>`;
}
@@ -209,43 +206,41 @@ function getHtmlDestroyProvision(provision, scenario) {
<p style="margin:0px">Scenario '${scenario.title}' successfully destroyed!</p>
</div>
${common}
<div style="color:#404040;font-size:16px;margin:30px 0px">
<p style="margin:0px">Check it out at <a href="https://qmicloud.qliktech.com">https://qmicloud.qliktech.com</a></p>
</div>
${FOOTER}
</div>
</div>`;
}
// async..await is not allowed in global scope, must use a wrapper
async function send( provision, scenario ) {
async function sendProvisionSuccess( provision, scenario ) {
const htmlText = getHtmlNewProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision finished successfully', htmlText);
}
async function sendError(provision, scenario ) {
async function sendProvisionError(provision, scenario ) {
const htmlText = getHtmlErrorProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision with failures', htmlText);
await _doSend(provision.user.upn, 'QMI Cloud - Provision with errors', htmlText);
}
async function sendDestroyed(provision, scenario ) {
async function sendDestroyedSuccess(provision, scenario ) {
const htmlText = getHtmlDestroyProvision(provision, scenario);
await _doSend(provision.user.upn, 'QMI Cloud - Provision destroyed successfully', htmlText);
}
async function sendWillStopIn24( provision, scenario ) {
async function sendWillStopIn24( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioWillStopIn24( provision, scenario);
await _doSend(provision.user.upn, `QMI Cloud - VMs will stop in ${RUNNING_PERIOD_WARNING_HOURS} hours`, htmlText);
const htmlText = getHtmlScenarioWillStopIn24( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - VMs will stop in ${warningDays*24} hours`, htmlText);
}
async function sendWillDestroyIn24( provision, scenario ) {
async function sendWillDestroyIn24( provision, scenario, period, warningDays ) {
const htmlText = getHtmlScenarioDestroyIn24( provision, scenario);
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${STOP_PERIOD_WARNING_HOURS} hours`, htmlText);
const htmlText = getHtmlScenarioDestroyIn24( provision, scenario, period, warningDays);
await _doSend(provision.user.upn, `QMI Cloud - Provision will destroy in ${(warningDays*24)} hours`, htmlText);
}
async function sendVMsStopped( provision, scenario ) {
@@ -253,11 +248,12 @@ async function sendVMsStopped( provision, scenario ) {
await _doSend(provision.user.upn, 'QMI Cloud - VMs stopped automatically', htmlText);
}
module.exports.send = send;
module.exports.sendError = sendError;
module.exports.sendDestroyed = sendDestroyed;
module.exports.sendProvisionSuccess = sendProvisionSuccess;
module.exports.sendProvisionError = sendProvisionError;
module.exports.sendDestroyedSuccess = sendDestroyedSuccess;
module.exports.sendWillStopIn24 = sendWillStopIn24;
module.exports.sendVMsStopped = sendVMsStopped;
module.exports.sendWillDestroyIn24 = sendWillDestroyIn24;
module.exports._doSend = _doSend;

View File

@@ -0,0 +1,7 @@
var sendEmail = require("./send-email");
function test(){
sendEmail._doSend("AOR@qlik.com", "Test subject", "Hi world");
}
test();

84
qmi-cloud-common/utils.js Normal file
View File

@@ -0,0 +1,84 @@
const db = require("./mongo");
const sendEmail = require("./send-email");
async function afterStopVms( provision, auto ) {
let timeRunning = db.utils.getNewTimeRunning(provision);
const dateNow = new Date();
let patch = {
"statusVms": "Stopped",
"timeRunning": timeRunning,
"stoppedFrom": dateNow,
"pendingNextAction": undefined
};
if ( auto && provision._scenarioDoc ) { //From CLI (auto stop)
let msg = "";
// Actual onschedule reset
if ( provision.schedule && !provision.schedule.is24x7 ) {
patch["startDateOnSchedule"] = dateNow;
patch["endDateOnSchedule"] = dateNow;
msg = "Schedule time has been reset.";
} else {
msg = "24x7 vms auto-stopped due to limitted running time 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 });
} 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 });
}
}
async function afterStartVms( provision ) {
const dateNow = new Date();
let countExtend = db.utils.getNewCountExtend(provision);
var patch = {
"statusVms": "Running",
"runningFrom": dateNow,
"countExtend": countExtend,
"pendingNextAction": undefined
};
// Actual onschedule reset
let msg = "";
if ( provision.schedule && !provision.schedule.is24x7 ) {
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`;
await db.provision.update(provision._id.toString(), patch);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.start-ondemand', message: msg });
}
module.exports.afterStopVms = afterStopVms;
module.exports.afterStartVms = afterStartVms;

1286
qmi-cloud-common/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
# Stage 1: NOTE: context is actually ../
FROM node:13.8-alpine AS sources
FROM node:15.12.0-alpine AS sources
RUN apk --no-cache add yarn
RUN apk --no-cache add yarn git
WORKDIR /app
@@ -11,8 +11,9 @@ ADD ./qmi-cloud-common ../qmi-cloud-common
RUN yarn install --production
# Stage 2:
FROM node:13.8-alpine AS production
FROM node:15.12.0-alpine AS production
WORKDIR /app
COPY --from=sources /app ./
CMD ["node", "-r", "esm", "index.js"]

View File

@@ -4,6 +4,7 @@ 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");
module.exports = async function(job) {
@@ -15,15 +16,19 @@ module.exports = async function(job) {
});
if ( !prov ) {
console.log(`Error: Not found Provision object in Database (it should exist!), provisionId is: ${job.data.id}` );
console.log(`ProcessorApply# 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"});
}
var idProv = prov._id.toString();
db.event.add({ user: prov.user._id, provision: prov._id, type: 'provision.init' });
// TERRAFORM INIT
return tf.init(prov)
.then(async function(res) {
if ( res.statusCode === 1 ) {
console.log("Error at INIT");
console.log(`ProcessorApply# Error at Terraform INIT for provision (${idProv})`);
return Promise.reject({"success": false, "error": "Error at Terraform Init", provStatus: "error_init"});
} else {
// TERRAFORM PLAN
@@ -32,24 +37,52 @@ module.exports = async function(job) {
} )
.then( async function(res) {
if ( res.statusCode === 1 ) {
console.log("Error at PLAN");
console.log(`ProcessorApply# Error at Terraform PLAN for provision (${idProv}) `);
return Promise.reject({"success": false, "error": "Error at Terraform Plan", provStatus: "error_plan"});
} else {
return await db.provision.update(prov._id,{"status": "provisioning", "statusVms": "Running", "runningFrom": new Date(), "runningTime": 0, "countExtend": 0});
const dateNow = new Date();
let patch = {
"status": "provisioning",
"statusVms": (prov.vmImage && prov.vmImage.vm1)? "Running" : "N/A",
"runningFrom": dateNow,
"runningTime": 0,
"countExtend": 0
};
if ( prov.schedule && !prov.schedule.is24x7 ) {
patch["startDateOnSchedule"] = dateNow;
patch["endDateOnSchedule"] = dateNow;
}
return await db.provision.update(prov._id, patch);
}
} ).then( function(prov) {
// TERRAFORM APPLY
return tf.apply(prov);
} ).then( async function(res) {
if ( res.statusCode === 1 ) {
console.log("Error at APPLY");
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){
return await db.provision.update(prov._id, {"outputs": 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);
@@ -58,15 +91,22 @@ module.exports = async function(job) {
return azure.createimage(prov, job.data._scenario);
} ).then( function(prov) {
if (prov.status === "provisioned") {
sendEmail.send(prov, job.data._scenario);
sendEmail.sendProvisionSuccess(prov, job.data._scenario);
} else {
sendEmail.sendError(prov, job.data._scenario);
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("Provision: error", err);
db.provision.update(prov._id, {"status": err.provStatus? err.provStatus : 'error'});
sendEmail.sendError(prov, job.data._scenario);
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 });
if ( errormsg !== "aborted") {
sendEmail.sendProvisionError(prov, job.data._scenario);
}
return Promise.reject({"success": false, "error": err});
} );
}

View File

@@ -13,9 +13,9 @@ const appgateway = function( provision, scenario ) {
var provision_id = provision._id.toString();
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
var name = 'qmi-azureps-appgw-'+provision_id;
console.log(`AzurePS: will spin up container: ${name}`);
console.log(`AzurePS# will spin up container: ${name}`);
return docker.run(DOCKERIMAGE, ['pwsh', 'appgw.ps1', "-ProvisionId", provision_id ], processStream, {
return docker.run(DOCKERIMAGE, ['pwsh', 'appgw.ps1', "-ProvisionId", provision_id, "-Subscription", provision.deployOpts.subsId, "-PolicyName", provision.deployOpts.wafPolicyName , "-PolicyResourceGroup", provision.deployOpts.wafPolicyRgName ], processStream, {
"name": name,
"WorkingDir": "/myapp",
"HostConfig": {
@@ -27,10 +27,10 @@ const appgateway = function( provision, scenario ) {
}).then(function(data) {
var output = data[0];
var container = data[1];
console.log(`AzurePS: ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
console.log(`AzurePS# ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
return container.remove();
}).then(function() {
console.log(`AzurePS: ${name} removed!`);
console.log(`AzurePS# ${name} removed!`);
return Promise.resolve(provision);
});
@@ -52,7 +52,7 @@ const createimage = function( provision, scenario ) {
let imageName = scenario.newImageName + "-" + new Date().getTime();
console.log(`AzurePS: will spin up container: ${name}`);
console.log(`AzurePS# will spin up container: ${name}`);
return docker.run(DOCKERIMAGE, ['pwsh', 'createimage.ps1', "-rgName", rgName, "-imageName", imageName ], processStream, {
"name": name,
@@ -66,10 +66,10 @@ const createimage = function( provision, scenario ) {
}).then(function(data) {
var output = data[0];
var container = data[1];
console.log(`AzurePS: ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
console.log(`AzurePS# ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
return container.remove();
}).then(function() {
console.log(`AzurePS: ${name} removed!`);
console.log(`AzurePS# ${name} removed!`);
return Promise.resolve(provision);
});

View File

@@ -12,14 +12,14 @@ const KUBE_PATH = path.join(PROJECT_PATH, 'logs', 'kube');
const _done = function(data){
let output = data[0];
let container = data[1];
console.log(`kubectl: (${container.id}) has finished with code: ${output.StatusCode}`);
console.log(`kubectl# (${container.id}) has finished with code: ${output.StatusCode}`);
return container.remove();
};
const kubeconfig = function( provId ) {
fs.writeFileSync(`/var/www/app/logs/kube/config_${provId}`, kubeConfigContent);
console.log(`kubectl: will spin up container`);
console.log(`kubectl# will spin up container`);
return docker.run(DOCKERIMAGE, ['config', 'current-context', '--kubeconfig', `/app/config_${provId}` ], processStream, {
//"name": initContName,
@@ -35,7 +35,7 @@ const kubeconfig = function( provId ) {
const apply = function( provId, kubeConfigContent ) {
fs.writeFileSync(`/logs/kube/${provId}.config`, kubeConfigContent);
console.log(`kubectl: will spin up container`);
console.log(`kubectl# will spin up container`);
const yaml = path.join(PROJECT_PATH, 'az-tf-templates', 'azqmi-qseok', 'scripts', 'azure-sc.yaml');
return docker.run(DOCKERIMAGE, ['apply','-f', '/yamlfile', '--kubeconfig', `/app/${provId}.config` ], process.stdout, {
//"name": initContName,
@@ -52,7 +52,7 @@ const apply = function( provId, kubeConfigContent ) {
const getpod = function( provId, kubeConfigContent ) {
fs.writeFileSync(`/logs/kube/${provId}.config`, kubeConfigContent);
console.log(`kubectl: will spin up container`);
console.log(`kubectl# will spin up container`);
return docker.run(DOCKERIMAGE, ['get', 'pod', '--kubeconfig', `/app/${provId}.config` ], process.stdout, {
//"name": initContName,
@@ -68,9 +68,9 @@ const getpod = function( provId, kubeConfigContent ) {
const getsvc = function( provId, kubeConfigContent ) {
fs.writeFileSync(`/logs/kube/config_${provId}`, kubeConfigContent);
console.log(`kubectl: will spin up container`);
console.log(`kubectl# will spin up container`);
console.log(`kubectl: will spin up container`);
console.log(`kubectl# will spin up container`);
return docker.run(DOCKERIMAGE, ['get', 'svc', '--kubeconfig', `/app/config_${provId}` ], process.stdout, {
//"name": initContName,
"WorkingDir": "/app",

View File

@@ -3,6 +3,7 @@ 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";
@@ -25,18 +26,42 @@ function hook_stdout(callback) {
function _buildVarsExec( exec, provision, scenario ) {
let prefix = scenario.name.toUpperCase();
let prefix = provision.scenario.toUpperCase();
prefix = prefix.replace(/AZQMI/g, 'QMI');
exec.push('-var');
exec.push(`prefix=${prefix}`);
if ( scenario.subscription && scenario.subscription.subsId ) {
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}`);
}
if ( scenario.subscription && scenario.subscription.vnetExists ) {
if ( provision.deployOpts ) {
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}`);
if ( provision.isExternalAccess ) {
exec.push('-var');
exec.push(`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}`);
@@ -50,6 +75,16 @@ function _buildVarsExec( exec, provision, scenario ) {
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
@@ -65,25 +100,37 @@ function _buildVarsExec( exec, provision, scenario ) {
} else if ( provision.vmImage ) {
//New way
for ( let key in provision.vmImage ) {
if ( provision.vmImage[key].nodeCount ) {
exec.push('-var');
exec.push(`agent_count_${key}=${provision[key].nodeCount}`);
}
if ( !provision.vmImage[key].disabled ) {
if ( provision.vmImage[key].vmType ) {
exec.push('-var');
exec.push(`vm_type_${key}=${provision.vmImage[key].vmType}`);
}
if ( provision.vmImage[key].nodeCount ) {
exec.push('-var');
exec.push(`agent_count_${key}=${provision[key].nodeCount}`);
}
if ( provision.vmImage[key].diskSizeGb ) {
exec.push('-var');
exec.push(`disk_size_gb_${key}=${provision.vmImage[key].diskSizeGb}`);
}
if ( provision.vmImage[key].vmType ) {
exec.push('-var');
exec.push(`vm_type_${key}=${provision.vmImage[key].vmType}`);
} else {
exec.push('-var');
exec.push(`vm_type_${key}=ENABLED`);
}
if ( provision.vmImage[key].version ) {
exec.push('-var');
exec.push(`image_reference_${key}=${provision.vmImage[key].version.image}`);
}
if ( provision.vmImage[key].diskSizeGb ) {
exec.push('-var');
exec.push(`disk_size_gb_${key}=${provision.vmImage[key].diskSizeGb}`);
}
if ( provision.vmImage[key].version && provision.vmImage[key].version.image) {
exec.push('-var');
exec.push(`image_reference_${key}=${provision.vmImage[key].version.image}`);
}
if ( provision.vmImage[key].version && provision.vmImage[key].version.init_password) {
exec.push('-var');
exec.push(`image_reference_${key}_init_password=${provision.vmImage[key].version.init_password}`);
}
}
}
}
@@ -92,20 +139,20 @@ function _buildVarsExec( exec, provision, scenario ) {
exec.push(`is_external_access=${provision.isExternalAccess}`);
}
if ( provision.autoShutdown ) {
if ( provision.autoShutdown.is24x7 === true ) {
if ( provision.schedule ) {
if ( provision.schedule.is24x7 === true ) {
exec.push('-var');
exec.push(`is_24x7=true`);
} else if ( provision.autoShutdown.is24x7 === false ) {
} else if ( provision.schedule.is24x7 === false ) {
exec.push('-var');
exec.push(`is_24x7=false`);
if ( provision.autoShutdown.utcTagStartupTime ) {
if ( provision.schedule.utcTagStartupTime && provision.schedule.isStartupTimeEnable ) {
exec.push('-var');
exec.push(`startupTime=${provision.autoShutdown.utcTagStartupTime}`);
exec.push(`startupTime=${provision.schedule.utcTagStartupTime}`);
}
if ( provision.autoShutdown.utcTagShutdownTime ) {
if ( provision.schedule.utcTagShutdownTime ) {
exec.push('-var');
exec.push(`shutdownTime=${provision.autoShutdown.utcTagShutdownTime}`);
exec.push(`shutdownTime=${provision.schedule.utcTagShutdownTime}`);
}
}
}
@@ -116,12 +163,17 @@ function _buildVarsExec( exec, provision, scenario ) {
const init = function( provision ) {
const name = `qmi-tf-init-${provision._id}`;
console.log(`Init: will spin up container: ${name}`);
console.log(`Terraform# Init: will spin up container: ${name}`);
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
let exec = ['terraform', 'init', '-no-color', `-from-module=${GIT_SCENARIOS}//${provision.scenario}?ref=${GIT_TAG}`];
console.log('Init: exec: '+exec.join(" "));
return docker.run(DOCKERIMAGE, exec, processStream, {
let gitBranch = GIT_TAG;
if ( provision._scenarioDoc && provision._scenarioDoc.gitBranch && provision._scenarioDoc.gitBranch.trim() !== "") {
gitBranch = provision._scenarioDoc.gitBranch;
}
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"],
"name": name,
"WorkingDir": "/app",
@@ -133,9 +185,9 @@ const init = function( provision ) {
}).then(function(data) {
var output = data[0];
var container = data[1];
console.log(`Init: ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
console.log(`Terraform# Init: ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
return container.remove().then(function(){
console.log(`Init: ${name} removed!`);
console.log(`Terraform# Init: ${name} removed!`);
return output.StatusCode;
});
}).then(function(statusCode) {
@@ -146,14 +198,15 @@ const init = function( provision ) {
const plan = function( provision, scenario ) {
const name = `qmi-tf-plan-${provision._id}`;
console.log(`Plan: will spin up container: ${name}`);
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);
console.log('Plan: exec: '+exec.join(" "));
return docker.run(DOCKERIMAGE, exec, processStream, {
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"],
"name": name,
"WorkingDir": "/app",
@@ -166,9 +219,9 @@ const plan = function( provision, scenario ) {
}
}).then(function(data){
var container = data[1];
console.log(`Plan: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`);
console.log(`Terraform# Plan: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove().then(function(){
console.log(`Plan: ${name} removed!`);
console.log(`Terraform# Plan: ${name} removed!`);
return data[0].StatusCode;
});
}).then(function(statusCode) {
@@ -179,14 +232,15 @@ const plan = function( provision, scenario ) {
const apply = function( provision ) {
const name = `qmi-tf-apply-${provision._id}`;
console.log(`Apply: will spin up container: ${name}`);
console.log(`Terraform# Apply: will spin up container: ${name}`);
var processStream = fs.createWriteStream(provision.logFile, {flags:'a'});
//var processStream = process.stdout;
var exec = ['terraform', 'apply', 'tfplan', '-no-color'];
console.log('Apply: exec: '+exec.join(" "));
return docker.run(DOCKERIMAGE, exec, processStream, {
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"],
"name": name,
"WorkingDir": "/app",
@@ -199,9 +253,9 @@ const apply = function( provision ) {
}
}).then(function(data){
let container = data[1];
console.log(`Apply: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`);
console.log(`Terraform# Apply: ${name} (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove().then(function(){
console.log(`Apply: ${name} removed!`);
console.log(`Terraform# Apply: Container '${name}' removed!`);
return data[0].StatusCode;
});
}).then(function(statusCode) {
@@ -212,13 +266,16 @@ const apply = function( provision ) {
const destroy = function(destroyMongo, provision, scenario) {
const name = `qmi-tf-destroy-${destroyMongo._id}`;
console.log(`Destroy: will spin up container: ${name}`);
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);
console.log('Destroy: exec: '+exec.join(" "));
console.log('Terraform# Destroy: exec: '+exec.join(" "));
return docker.run(DOCKERIMAGE, exec, processStream, {
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"],
"name": name,
"WorkingDir": "/app",
@@ -230,9 +287,9 @@ const destroy = function(destroyMongo, provision, scenario) {
}
}).then(function(data) {
var container = data[1];
console.log(`Destroy: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
console.log(`Terraform# Destroy: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove().then(function(){
console.log(`Destroy: '${name}' removed!`);
console.log(`Terraform# Destroy: Container '${name}' removed!`);
return data[0].StatusCode;
});
}).then(async function(statusCode) {
@@ -244,18 +301,20 @@ const destroy = function(destroyMongo, provision, scenario) {
const outputs = function(provision) {
const name = `qmi-tf-output-${provision._id}`;
console.log(`Output: will spin up container: ${name}`);
console.log(`Terraform# Output: will spin up container: ${name}`);
var exec = ['terraform', 'output', '-no-color', '-json'];
console.log('Output: exec: '+exec.join(" "));
console.log('Terraform# Output: exec: '+exec.join(" "));
var tfout = "";
var unhook = hook_stdout(function(string, encoding, fd) {
tfout += string.trim();
});
return docker.run(DOCKERIMAGE, exec, process.stdout, {
var terraformImage = provision.terraformImage? provision.terraformImage : DOCKERIMAGE;
return docker.run(terraformImage, exec, process.stdout, {
//"Env": ["VAR_ENV=whatever"],
"name": name,
"WorkingDir": "/app",
@@ -267,11 +326,11 @@ const outputs = function(provision) {
}).then(function(data) {
unhook();
var container = data[1];
console.log(`Output: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
console.log(`Terraform# Output: '${name}' (${container.id}) has finished with code: ${data[0].StatusCode}`);
return container.remove();
}).then(async function(data) {
console.log(`Output: '${name}' removed!`);
console.log("Output: tfout: " + tfout);
console.log(`Terraform# Output: Container '${name}' removed!`);
//console.log("Terraform# Output: tfout: " + tfout);
var out = JSON.parse(tfout);
var o = {};
for (var key in out) {
@@ -281,8 +340,48 @@ const outputs = function(provision) {
});
};
const stop = function(provision) {
if ( provision.status !== "provisioning" ) {
console.log(`Terraform# Stop: provision (${provision._id}) is not provisioning`);
return {"message": `Won't stop container: provision (${provision._id}) is not provisioning`};
}
const name = `qmi-tf-apply-${provision._id}`;
console.log(`Terraform# Stop: will try to stop container: ${name}`);
return docker.listContainers().then(function(containers) {
var containerID;
for (let i=0;i<containers.length;i++) {
if ( containers[i].Names && containers[i].Names.length ) {
//console.log(`Terraform# Stop: debug container Names: ${containers[i].Names[0]}`);
if ( containers[i].Names[0] === ("/"+name) ) {
console.log(`Terraform# Stop: container found: ${name}`);
containerID = containers[i].Id;
break;
}
}
}
if ( containerID ) {
console.log(`Terraform# Stop: stopping container: ${name} with ID ${containerID}`);
return docker.getContainer(containerID).kill().then(function(){
console.log(`Terraform# Stop: container stopped!!: ${name}`);
return {"message": `container ${name} stopped`}
});
} else {
console.log(`Terraform# Stop: container ${name} not found!`);
return {"message": `container ${name} not found`};
}
});
}
module.exports.init = init;
module.exports.plan = plan;
module.exports.apply = apply;
module.exports.destroy = destroy;
module.exports.outputs = outputs;
module.exports.outputs = outputs;
module.exports.stop = stop;

View File

@@ -1,4 +1,4 @@
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from 'qmi-cloud-common/queues';
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE } from 'qmi-cloud-common/queues';
var path = require("path");
@@ -6,6 +6,7 @@ var path = require("path");
queues[TF_APPLY_QUEUE].process("tf_apply_job", 10, path.resolve(__dirname, 'processor-apply.js'));
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'));
console.log(`Worker queues started!`);

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-worker",
"version": "1.1.4",
"version": "1.3.1",
"scripts": {
"start": "node -r esm index.js",
"start:dev": "nodemon -r esm index.js",
@@ -11,6 +11,7 @@
"qmi-cloud-common": "../qmi-cloud-common",
"dockerode": "^3.0.2",
"esm": "^3.2.25",
"nodemon": "^1.19.1"
"nodemon": "^1.19.1",
"axios": "^0.21.1"
}
}

View File

@@ -2,6 +2,7 @@ const tf = require('./docker/tf');
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");
module.exports = async function(job){
@@ -12,11 +13,21 @@ module.exports = async function(job){
});
if ( !destroyMongo ) {
console.log(`Error: Not found Destroy object in Database (it should exist!), detroyId is: ${job.data.id}` );
console.log(`ProcessorDestroy# Not found Destroy object in Database (it should exist!), detroyId is: ${job.data.id}` );
return Promise.reject({"success": false, "err": "Not found Destroy object in Worker"});
}
var provMongo = await db.provision.getById(job.data.provId);
// Deleting Barracuda App if existed
if ( provMongo.barracudaAppId ) {
console.log(`ProcessorDestroy# Deleting Barracuda App...` );
barracuda.deleteApp(provMongo);
} else {
console.log(`ProcessorDestroy# There is no barracuda to be deleted` );
}
db.event.add({ user: provMongo.user._id, provision: provMongo._id, type: 'provision.destroy-init' });
return tf.destroy(destroyMongo, provMongo, job.data._scenario)
.then(async function(res) {
@@ -28,14 +39,22 @@ module.exports = async function(job){
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()});
sendEmail.sendDestroyed(update2, job.data._scenario);
sendEmail.sendDestroyedSuccess(update2, job.data._scenario);
}
return { destroy: update, provision: update2 };
}).then(async function(res) {
}).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);
}
}
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("Processor Destroy: err", err);
console.log("ProcessorDestroy# err", err);
db.event.add({ user: provMongo.user._id, 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,21 @@
const db = require('qmi-cloud-common/mongo');
const tf = require("./docker/tf");
module.exports = async function(job) {
var prov = await db.provision.getById(job.data.provId);
if ( !prov ) {
console.log(`ProcessorStopContainer# 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"});
}
// TERRAFORM INIT
return tf.stop(prov)
.then( function(res) {
return Promise.resolve( { "success": true, "output": res });
} ).catch( function(err) {
console.log("ProcessorStopContainer# Error:", err);
return Promise.reject({"success": false, "error": err});
} );
}

View File

@@ -2,6 +2,70 @@
# yarn lockfile v1
"@azure/abort-controller@^1.0.0":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd"
integrity sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==
dependencies:
tslib "^2.0.0"
"@azure/arm-compute@^15.0.0":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@azure/arm-compute/-/arm-compute-15.0.0.tgz#5d0d0c1db16adbb6db3d33ca376b120c68e6ae23"
integrity sha512-ElV2MuYZ+B2+ygMx2iiM/u3C7WDRruZjkXzfk5p2O+UbWxjG6j/686OH3T+VSDqmzg+47AnIOTLu2v0u0H8FOw==
dependencies:
"@azure/ms-rest-azure-js" "^2.0.1"
"@azure/ms-rest-js" "^2.0.4"
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"
integrity sha512-KUl+Nwn/Sm6Lw5d3U90m1jZfNSL087SPcqHLxwn2T6PupNKmcgsEbDjHB25gDvHO4h7pBsTlrdJAY7dz+Qk8GA==
dependencies:
"@azure/abort-controller" "^1.0.0"
tslib "^2.0.0"
"@azure/ms-rest-azure-env@^2.0.0":
version "2.0.0"
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":
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==
dependencies:
"@azure/core-auth" "^1.1.4"
"@azure/ms-rest-js" "^2.2.0"
tslib "^1.10.0"
"@azure/ms-rest-js@^2.0.4", "@azure/ms-rest-js@^2.2.0":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-2.2.3.tgz#8f0085f7768c69f17b3cdb20ce95728b452dc304"
integrity sha512-sXOhOu/37Tr8428f32Jwuwga975Xw64pYg1UeUwOBMhkNgtn5vUuNRa3fhmem+I6f8EKoi6hOsYDFlaHeZ52jA==
dependencies:
"@azure/core-auth" "^1.1.4"
"@types/node-fetch" "^2.3.7"
"@types/tunnel" "0.0.1"
abort-controller "^3.0.0"
form-data "^2.5.0"
node-fetch "^2.6.0"
tough-cookie "^3.0.1"
tslib "^1.10.0"
tunnel "0.0.6"
uuid "^3.3.2"
xml2js "^0.4.19"
"@azure/ms-rest-nodeauth@^3.0.7":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-3.0.7.tgz#73c399b0aef45c75104324b6617aa4e0a6c27875"
integrity sha512-7Q1MyMB+eqUQy8JO+virSIzAjqR2UbKXE/YQZe+53gC8yakm8WOQ5OzGfPP+eyHqeRs6bQESyw2IC5feLWlT2A==
dependencies:
"@azure/ms-rest-azure-env" "^2.0.0"
"@azure/ms-rest-js" "^2.0.4"
adal-node "^0.1.28"
"@hapi/boom@^9.1.0":
version "9.1.0"
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.0.tgz#0d9517657a56ff1e0b42d0aca9da1b37706fec56"
@@ -14,16 +78,43 @@
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010"
integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==
"@types/node-fetch@^2.3.7":
version "2.5.8"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb"
integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "14.14.35"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313"
integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==
"@types/node@^8.0.47":
version "8.10.61"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.61.tgz#d299136ce54bcaf1abaa4a487f9e4bedf6b0d393"
integrity sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==
"@types/tunnel@0.0.1":
version "0.0.1"
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c"
integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==
dependencies:
"@types/node" "*"
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
adal-node@^0.1.28:
version "0.1.28"
resolved "https://registry.yarnpkg.com/adal-node/-/adal-node-0.1.28.tgz#468c4bb3ebbd96b1270669f4b9cba4e0065ea485"
@@ -118,13 +209,6 @@ async-each@^1.0.1:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
async@2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
integrity sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==
dependencies:
lodash "^4.14.0"
async@>=0.6.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
@@ -150,13 +234,12 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2"
integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==
azure-arm-compute@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/azure-arm-compute/-/azure-arm-compute-10.0.0.tgz#ce9ba2e4d6dd6b1174c34da2219700a8080f389b"
integrity sha512-ehafNtcMKI6c00FT+xhPWPtzhYgHCZ675TUsaJ1FJ2bSpznih5EUrpir/4w519T4zbFBigszhnRK6eBkl78I9g==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
ms-rest "^2.5.0"
ms-rest-azure "^2.5.5"
follow-redirects "^1.10.0"
balanced-match@^1.0.0:
version "1.0.0"
@@ -406,7 +489,7 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@@ -609,11 +692,6 @@ duplexer3@^0.1.4:
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@@ -672,6 +750,11 @@ esm@^3.2.25:
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
event-target-shim@^5.0.0:
version "5.0.1"
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==
execa@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
@@ -767,6 +850,11 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
follow-redirects@^1.10.0:
version "1.13.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -777,6 +865,24 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -985,6 +1091,11 @@ ioredis@^4.14.1:
redis-parser "^3.0.0"
standard-as-callback "^2.0.1"
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
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"
@@ -1006,7 +1117,7 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
is-buffer@^1.1.5, is-buffer@^1.1.6:
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -1301,7 +1412,7 @@ lodash.flatten@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
lodash@^4.14.0, lodash@^4.17.15:
lodash@^4.17.15:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -1406,7 +1517,7 @@ moment-timezone@^0.5.31:
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.21.0, moment@^2.22.2:
"moment@>= 2.9.0":
version "2.26.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==
@@ -1462,44 +1573,6 @@ mquery@3.2.2:
safe-buffer "5.1.2"
sliced "1.0.1"
ms-rest-azure@^2.5.5:
version "2.6.0"
resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-2.6.0.tgz#2098efec529eecfa0c6e215b69143abcaba12140"
integrity sha512-J6386a9krZ4VtU7CRt+Ypgo9RGf8+d3gjMBkH7zbkM4zzkhbbMOYiPRaZ+bHZcfihkKLlktTgA6rjshTjF329A==
dependencies:
adal-node "^0.1.28"
async "2.6.0"
moment "^2.22.2"
ms-rest "^2.3.2"
request "^2.88.0"
uuid "^3.2.1"
ms-rest-azure@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ms-rest-azure/-/ms-rest-azure-3.0.0.tgz#54d0341c2aeef7b9f17f2a46258788740b2f0ec5"
integrity sha512-cttN01/TtMDB4v3rt/WQ/slgffB6jcUYxcPzcL0VNSB+WFPE1j4y5ICNHMuD1RaNNekCYMI4Pv51BDQ/BXNq7Q==
dependencies:
adal-node "^0.1.28"
async "2.6.0"
moment "^2.22.2"
ms-rest "^2.3.2"
request "^2.88.0"
uuid "^3.2.1"
ms-rest@^2.3.2, ms-rest@^2.5.0:
version "2.5.4"
resolved "https://registry.yarnpkg.com/ms-rest/-/ms-rest-2.5.4.tgz#57b42299cf302e45d5e1a734220bf7d4a110167a"
integrity sha512-VeqCbawxRM6nhw0RKNfj7TWL7SL8PB6MypqwgylXCi+u412uvYoyY/kSmO8n06wyd8nIcnTbYToCmSKFMI1mCg==
dependencies:
duplexer "^0.1.1"
is-buffer "^1.1.6"
is-stream "^1.1.0"
moment "^2.21.0"
request "^2.88.0"
through "^2.3.8"
tunnel "0.0.5"
uuid "^3.2.1"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -1532,6 +1605,11 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
node-fetch@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
nodemailer@^6.4.2:
version "6.4.8"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.8.tgz#aca52886e4e56f71f6b8a65f5ca6b767ca751fc7"
@@ -1752,13 +1830,14 @@ punycode@^2.1.0, punycode@^2.1.1:
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qmi-cloud-common@../qmi-cloud-common:
version "1.1.2"
version "1.1.6"
dependencies:
"@azure/arm-compute" "^15.0.0"
"@azure/ms-rest-nodeauth" "^3.0.7"
"@hapi/boom" "^9.1.0"
azure-arm-compute "^10.0.0"
axios "^0.21.1"
bull "^3.11.0"
mongoose "^5.7.4"
ms-rest-azure "^3.0.0"
nodemailer "^6.4.2"
qs@~6.5.2:
@@ -1867,7 +1946,7 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
"request@>= 2.52.0", request@^2.88.0:
"request@>= 2.52.0":
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@@ -1945,6 +2024,11 @@ saslprep@^1.0.0:
dependencies:
sparse-bitfield "^3.0.3"
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -2226,11 +2310,6 @@ term-size@^1.2.0:
dependencies:
execa "^0.7.0"
through@^2.3.8:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
timed-out@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
@@ -2268,6 +2347,15 @@ touch@^3.1.0:
dependencies:
nopt "~1.0.10"
tough-cookie@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
dependencies:
ip-regex "^2.1.0"
psl "^1.1.28"
punycode "^2.1.1"
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@@ -2276,6 +2364,16 @@ tough-cookie@~2.5.0:
psl "^1.1.28"
punycode "^2.1.1"
tslib@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
@@ -2283,10 +2381,10 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.5.tgz#d1532254749ed36620fcd1010865495a1fa9d0ae"
integrity sha512-gj5sdqherx4VZKMcBA4vewER7zdK25Td+z1npBqpbDys4eJrLx+SlYjJvq1bDXs2irkuJM5pf8ktaEQVipkrbA==
tunnel@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
@@ -2400,7 +2498,7 @@ util.promisify@^1.0.1:
has-symbols "^1.0.1"
object.getownpropertydescriptors "^2.1.0"
uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.4.0:
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==
@@ -2447,6 +2545,19 @@ 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.4.19:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
"xmldom@>= 0.1.x":
version "0.3.0"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a"

View File

@@ -66,7 +66,7 @@ exports.creds = {
scope: ['openid', 'email', 'profile'],
// Optional, 'error', 'warn' or 'info'
loggingLevel: 'info',
loggingLevel: 'warn',
// Optional. The lifetime of nonce in session or cookie, the default value is 3600 (seconds).
nonceLifetime: null,

View File

@@ -6,6 +6,9 @@ const config = require('./config');
const MongoStore = require('connect-mongo')(expressSession);
const mongoose = require('mongoose');
const db = require("qmi-cloud-common/mongo");
const axios = require('axios');
const path = require('path');
const fs = require('fs');
// Start QuickStart here
@@ -88,7 +91,20 @@ passport.use(new OIDCStrategy({
//console.log("refreshToken", refreshToken);
//console.log("jwtClaims", jwtClaims);
//console.log("params", params);
console.log("New Auth: profile", profile);
console.log(`Passport# new login from: ${profile.upn} (${profile.displayName})` );
//Save user photo
axios({
method: 'get',
url: 'https://graph.microsoft.com/v1.0/me/photo/$value',
responseType: 'stream',
headers: { 'Authorization' : 'Bearer '+accessToken }
}).then(function (response) {
response.data.pipe(fs.createWriteStream(path.resolve(__dirname, '..', 'photos', `${profile.oid}.jpg`)));
}).catch(function(err){
console.log('Passport# Warning: No picture found');
});
// asynchronous verification, for effect...
process.nextTick(function () {
_findByOid(profile.oid, async function(err, user) {
@@ -125,7 +141,9 @@ module.exports.init = function(app){
autoRemove: 'interval',
autoRemoveInterval: 10
//clear_interval: config.mongoDBSessionMaxAge
})
}),
resave: true,
saveUninitialized: false
}));
} else {
app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false }));
@@ -168,7 +186,7 @@ module.exports.init = function(app){
)(req, res, next);
},
function(req, res) {
console.log('We received a return from AzureAD.');
console.log('Passport# We received a return from AzureAD.');
res.redirect('/provisions');
}
);
@@ -187,7 +205,7 @@ module.exports.init = function(app){
)(req, res, next);
},
function(req, res) {
console.log('We received a return from AzureAD.');
console.log('Passport# We received a return from AzureAD.');
res.redirect('/provisions');
}
);
@@ -240,7 +258,8 @@ module.exports.ensureAuthenticatedAndAdmin = async function(req, res, next) {
module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
if ( await isApiKeyAuthenticated(req) || req.isAuthenticated() ) {
if ( req.user._id == req.params.userId || req.user.role === 'admin' || req.user.role === 'superadmin' ) {
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");

View File

@@ -12,6 +12,15 @@ const passport = require('../passport');
* summary: Get all API keys
* tags:
* - admin
* parameters:
* - name: filter
* in: query
* required: false
* type: object
* content:
* application/json:
* schema:
* type: object
* produces:
* - application/json
* responses:
@@ -20,7 +29,8 @@ const passport = require('../passport');
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.apiKey.get();
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
const result = await db.apiKey.get(filter);
return res.json(result);
} catch (error) {
next(error);
@@ -42,19 +52,27 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
* in: path
* type: string
* required: true
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* description:
* type: string
* responses:
* 200:
* description: API KEY
*/
router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const user = await db.user.getById(req.params.userId);
const user = await db.user.getById(req.params.userId);
if ( !user ) {
res.status(404).json({"err": "user not found"});
}
var body = {
user: req.params.userId
}
var body = req.body;
body.user = req.params.userId;
const result = await db.apiKey.add(body);
return res.json(result);
} catch (error) {
@@ -64,10 +82,10 @@ router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, n
/**
* @swagger
* /apikeys/{id}:
* /apikeys/{id}/revoke:
* put:
* description: Deactivate API Key
* summary: Deactivate API Key
* description: Revoke API Key
* summary: Revoke API Key
* tags:
* - admin
* produces:
@@ -81,7 +99,7 @@ router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, n
* 200:
* description: API KEY
*/
router.put('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
router.put('/:id/revoke', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.apiKey.update(req.params.id, {"isActive": false});
return res.json(result);
@@ -90,4 +108,32 @@ router.put('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next)
}
});
/**
* @swagger
* /apikeys/{id}:
* delete:
* description: Delete API Key
* summary: Delete API Key
* tags:
* - admin
* produces:
* - application/json
* parameters:
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: API KEY
*/
router.delete('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.apiKey.del(req.params.id);
return res.json(result);
} catch (error) {
next(error);
}
});
module.exports = router;

View File

@@ -55,7 +55,6 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
*/
router.post('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
console.log("BODY", req.body);
const result = await db.subscription.add(req.body);
return res.json(result);
} catch (error) {

View File

@@ -23,35 +23,114 @@ const passport = require('../passport');
* description: Notifications
*/
router.post('/updates', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
const dateNow = new Date();
const now = dateNow.toISOString();
try {
let event = req.body;
const event = req.body;
let logEvent = event.logEvent || 'DivvyCloud';
if ( event.cloudName === 'QMI Automation' && event.provID ) {
console.log(`DivvyCloud: update received`, event);
let provision = await db.provision.getById(event.provID);
if ( event.cloudName === 'QMI Automation' ) {
console.log(`${logEvent} (${now})# event received for subscription (${event.cloudName}) - provision (${event.provID}) -> new status (${event.instanceState})`);
if ( provision ) {
if ( event.instanceState === 'Stopped' && provision.statusVms !== 'Stopped' ) {
console.log(`DivvyCloud: VMs for provision are now Stopped!: '${provision._id}'`);
let timeRunning = db.utils.getNewTimeRunning(provision);
db.provision.update(provision._id.toString(), {"statusVms": "Stopped", "timeRunning": timeRunning, "stoppedFrom": new Date(), "pendingNextAction": undefined});
} else if ( event.instanceState === 'Running' && provision.statusVms !== 'Running' ) {
console.log(`DivvyCloud: VMs for provision are now Running!: '${provision._id}'`);
db.provision.update(provision._id.toString(), {"statusVms": "Running", "runningFrom": new Date(), "pendingNextAction": undefined});
if ( event.provID && event.provID !== 'None' ) {
let provision = await db.provision.getById(event.provID);
if ( provision ) {
let id = provision._id.toString();
console.log(`${logEvent} (${now})# provision (${id}) - scenario is (${provision.scenario} - v${provision.scenarioVersion})`);
if ( provision.status === 'provisioned' || provision.status === 'error' ) {
if ( event.instanceState === 'Stopped' ) {
if ( provision.statusVms === 'Stopped' ) {
console.log(`${logEvent} (${now})# provision (${id}) - VMs were already Stopped!`);
} else {
let timeRunning = db.utils.getNewTimeRunning(provision);
let patch = {
"statusVms": "Stopped",
"timeRunning": timeRunning,
"stoppedFrom": dateNow,
"pendingNextAction": undefined
};
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 });
}
} else if ( event.instanceState === 'Running' ) {
if ( provision.statusVms === 'Running' ) {
console.log(`${logEvent} (${now})# provision (${id}) - VMs were already Running!`);
} else {
let patch = {
"statusVms": "Running",
"runningFrom": dateNow,
"pendingNextAction": undefined
};
// This is temporary, only to make sure there are values soon
if ( provision.schedule && !provision.schedule.is24x7 ) {
if ( !provision["startDateOnSchedule"] ) {
patch["startDateOnSchedule"] = dateNow;
patch["endDateOnSchedule"] = dateNow;
}
}
await db.provision.update(id, patch);
console.log(`${logEvent} (${now})# provision (${id}) - VMs changed to Running!`);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.start-schedule', message: `TimeRunning so far: ${provision.timeRunning} mins` });
}
}
} else {
console.log(`${logEvent} (${now})# provision (${event.provID}) - Scenario not yet 'provisioned'`);
}
} else {
console.log(`${logEvent} (${now})# provision (${event.provID}) - Provision not found.`);
}
} else {
console.log(`DivvyCloud: provision '${event.provID}' not found.`)
console.log(`${logEvent} (${now})# 'provID' attribute is missing.`);
}
} else {
console.log(`DivvyCloud: update received for vm (${event.vmName}) for another subcription (${event.cloudName}), won't be processed`);
//console.log(`${logEvent} (${now}): event received for subscription (${event.cloudName}) --> won't be processed`);
}
return res.json(req.body);
} catch (error) {
console.log(`${logEvent} (${now})# error!!!!`, error);
next(error);
}
});
});
module.exports = router;

View File

@@ -2,6 +2,7 @@ const express = require('express');
const router = express.Router();
const db = require('qmi-cloud-common/mongo');
const passport = require('../passport');
const sendEmail = require('qmi-cloud-common/send-email');
/**
@@ -20,11 +21,41 @@ const passport = require('../passport');
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.notification.get();
var page;
if ( req.query.page && req.query.size ) {
page = {
page: parseInt(req.query.page),
size: parseInt(req.query.size)
}
}
const result = await db.notification.getPage({}, page);
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /notifications/testsendemail:
* post:
* description: Get all notifications
* summary: Get all notifications
* tags:
* - admin
* produces:
* - application/json
* responses:
* 200:
* description: Notifications
*/
router.post('/testsendemail', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await sendEmail._doSend(req.user.upn, "QMI - Test email", "Hi! This is a test email from QMI Cloud.");
return res.json(result);
} catch (error) {
next(error);
}
});
module.exports = router;

View File

@@ -33,11 +33,11 @@ const fs = require('fs-extra');
* in: query
* required: false
* type: string
* - name: skip
* - name: page
* in: query
* required: false
* type: integer
* - name: limit
* - name: size
* in: query
* required: false
* type: integer
@@ -53,27 +53,31 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
if ( filter.isDeleted === undefined ) {
filter.isDeleted = false;
}
const result = await db.provision.get(filter, req.query.select, req.query.skip, req.query.limit, req.query.populates);
var page;
if ( req.query.page && req.query.size ) {
page = {
page: parseInt(req.query.page),
size: parseInt(req.query.size)
}
}
const result = await db.provision.getPage(filter, page, req.query.populates, req.query.select);
var out = {
total: result.total,
count: result.count
};
if ( result.nextSkip && result.nextLimit ) {
out.nextUrl = new URL(req.protocol + '://' + req.get('Host') + req.baseUrl);
if (result.next){
result.nextUrl = new URL(req.protocol + '://' + req.get('Host') + req.baseUrl);
if ( req.query.filter ) {
out.nextUrl.searchParams.append("filter", req.query.filter);
result.nextUrl.searchParams.append("filter", req.query.filter);
}
if ( req.query.populates ) {
out.nextUrl.searchParams.append("populates", req.query.populates);
result.nextUrl.searchParams.append("populates", req.query.populates);
}
out.nextUrl.searchParams.append("skip", result.nextSkip);
out.nextUrl.searchParams.append("limit", result.nextLimit);
if ( req.query.select ) {
result.nextUrl.searchParams.append("select", req.query.select);
}
result.nextUrl.searchParams.append("page", result.next.page);
result.nextUrl.searchParams.append("size", result.next.size);
}
out.results = result.results;
return res.json(out);
return res.json(result);
} catch (error) {
next(error);
}
@@ -334,7 +338,7 @@ router.post('/:id/updatetagsvms', passport.ensureAuthenticatedAndAdmin, async (r
if (!provision) {
return res.status(404).json({"msg": "Not found provision with id: "+req.params.id, "success": false});
}
var result = await azurecli.updateVmsTags(provision, tagsEdit);
var result = await cli.updateVmsTags(provision._id, tagsEdit);
return res.json({"msg": "Tags are being updated", "result": result, "success": true});
} catch (error) {

227
server/routes/api-stats.js Normal file
View File

@@ -0,0 +1,227 @@
const express = require('express')
const router = express.Router()
const db = require('qmi-cloud-common/mongo');
const moment = require('moment');
const azurecli = require('qmi-cloud-common/azurecli');
const CACHED_PERIOD = 30; //minutes
var cachedTime;
var cachedTimeVms;
var cachedStats;
var cachedVms;
/**
* @swagger
* /stats:
* get:
* description: Get overall stats
* summary: Get overall stats
* produces:
* - application/json
* responses:
* 200:
* description: Stats
* 404:
* description: Not found
*
*/
router.get('/', async (req, res, next) => {
try {
var now = new Date().getTime();
var nowMinus5mins = now - CACHED_PERIOD*60*1000;
if ( (!req.query.disablecache || req.query.disablecache === 'no') && cachedStats && cachedTime && cachedTime > nowMinus5mins ) {
console.log("APIStats# Stats: return cached value");
cachedTime = now;
return res.json(cachedStats);
} else {
console.log("APIStats# Stats: new value");
let filterActiveP = { "isDestroyed": false, "status": "provisioned" };
let filterPRunning = { "isDestroyed": false, "status": "provisioned", "statusVms" : "Running" };
let initCurrentMonth = moment().startOf('month');
let initLastMonth = moment(initCurrentMonth).add(-1, 'months');
let today = moment();
let todayLastMonth = moment().add(-1, "months");
let filterTotalPCurrentMonth = { "status": "provisioned", "created": {
$gte: initCurrentMonth.toISOString(),
$lt: today.toISOString()
} };
let filterTotalPLastMonth = { "status": "provisioned", "created": {
$gte: initLastMonth.toISOString(),
$lt: todayLastMonth.toISOString()
} };
//Counts
let totalActiveP = await db.provision.count(filterActiveP);
let totalCPRunning = await db.provision.count(filterPRunning);
let totalPCurrentmonth = await db.provision.count(filterTotalPCurrentMonth);
let totalPLastmonth = await db.provision.count(filterTotalPLastMonth);
let totalUsers = await db.user.count({});
let totalAuthUsers = await db.user.count({
"lastLogin": {$gte: moment().add(-12, "hours").toISOString()}
});
let totalScenarios = await db.scenario.count({"isDisabled":false, "isAdminOnly": false});
cachedStats = {
provisions: {
active: totalActiveP,
running: totalCPRunning,
totalCurrentMonthPeriod: totalPCurrentmonth,
totalLastMonthPerdiod:totalPLastmonth
},
users: {
total: totalUsers,
activeNow: totalAuthUsers,
active7days: await db.user.count({
"lastLogin": {$gte: moment().add(-7, "days").toISOString()}
}),
active30days: await db.user.count({
"lastLogin": {$gte: moment().add(-30, "days").toISOString()}
})
},
scenarios: {
total: totalScenarios
}
};
cachedTime = now;
return res.json(cachedStats);
}
} catch (error) {
next(error);
}
});
function appendResult(list, output ){
list.forEach(v=>{
if ( v.tags && v.tags["QMI_user"] ) {
if (v.location) {
if ( !output.locations[v.location] ) {
output.locations[v.location] = 0;
}
output.locations[v.location] += 1;
}
if (v.storageProfile && v.storageProfile.osDisk && v.storageProfile.osDisk.osType) {
if ( !output.types[v.storageProfile.osDisk.osType] ) {
output.types[v.storageProfile.osDisk.osType] = 0;
}
output.types[v.storageProfile.osDisk.osType] += 1;
}
output.out.push(v);
}
});
return output;
}
/**
* @swagger
* /stats/vms:
* get:
* description: List azure vms
* summary: List azure vms
* produces:
* - application/json
* responses:
* 200:
* description: Stats
* 404:
* description: Not found
*
*/
router.get('/vms', async (req, res, next) => {
try {
var now = new Date().getTime();
var nowMinus5mins = now - CACHED_PERIOD*60*1000;
if ( (!req.query.disablecache || req.query.disablecache === 'no') && cachedVms && cachedTimeVms && cachedTimeVms > nowMinus5mins ) {
console.log("APIStats# VMs: return cached value");
cachedTimeVms = now;
return res.json(cachedVms);
} else {
console.log("APIStats# VMs: new value");
var output = {
out : [],
locations: {
"eastus": 0,
"westeurope": 0,
"southeastasia": 0
},
types: {
"Windows": 0,
"Linux": 0
}
}
var result = await azurecli.getAllVms();
output = appendResult(result, output);
if ( result.nextLink ) {
console.log("There is a second page");
result = await azurecli.getAllVmsNext(result.nextLink);
output = appendResult(result, output);
if ( result.nextLink ) {
console.log("There is a third page");
result = await azurecli.getAllVmsNext(result.nextLink);
output = appendResult(result, output);
if ( result.nextLink ) {
console.log("There is a forth page");
result = await azurecli.getAllVmsNext(result.nextLink);
output = appendResult(result, output);
if ( result.nextLink ) {
console.log("There is a fifth page");
result = await azurecli.getAllVmsNext(result.nextLink);
output = appendResult(result, output);
if ( result.nextLink ) {
console.log("There is a sixth page");
result = await azurecli.getAllVmsNext(result.nextLink);
output = appendResult(result, output);
if ( result.nextLink ) {
console.log("There is a seventh page");
}
}
}
}
}
}
cachedTimeVms = now;
cachedVms = {
total: output.out.length,
locations: output.locations,
types: output.types
};
return res.json(cachedVms);
}
} catch (error) {
next(error);
}
});
module.exports = router;

View File

@@ -1,11 +1,19 @@
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 fs = require('fs-extra');
const azurecli = require('qmi-cloud-common/azurecli');
const cli = require('qmi-cloud-common/cli');
const barracuda = require('qmi-cloud-common/barracuda');
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from 'qmi-cloud-common/queues';
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE } from 'qmi-cloud-common/queues';
/**
* @swagger
@@ -15,6 +23,15 @@ import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from '
* summary: Get all users
* tags:
* - admin
* parameters:
* - name: filter
* in: query
* required: false
* type: object
* content:
* application/json:
* schema:
* type: object
* produces:
* - application/json
* responses:
@@ -23,7 +40,8 @@ import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from '
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.user.get();
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
const result = await db.user.get(filter);
return res.json(result);
} catch (error) {
next(error);
@@ -70,7 +88,8 @@ router.get('/me', passport.ensureAuthenticated, async (req, res, next) => {
*/
router.get('/:userId', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const result = await db.user.getById(req.params.userId);
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const result = await db.user.getById(userId);
return res.json(result);
} catch (error) {
next(error);
@@ -103,7 +122,8 @@ router.get('/:userId', passport.ensureAuthenticatedAndIsMe, async (req, res, nex
router.put('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.user.update(req.params.userId, req.body);
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const result = await db.user.update(userId, req.body);
return res.json(result);
} catch (error) {
next(error);
@@ -161,32 +181,48 @@ router.put('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, ne
router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
req.body.user = req.params.userId;
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
req.body.user = userId;
const scenarioSource = await db.scenario.getOne({name: req.body.scenario});
if (!scenarioSource) {
return res.status(404).json({"msg": "Scenario not found "});
}
if (!req.body.vmImage || !req.body.vmImage.vm1 || !req.body.vmImage.vm1.vmType ) {
return res.status(400).json({"msg": "Invalid vmImage"});
const filterProvisions = {"user": userId, "isDestroyed": false, "isDeleted": false, "scenario": scenarioSource.name };
const result = await db.provision.get(filterProvisions);
if ( scenarioSource.numSimultaneousProvisions && result.total >= scenarioSource.numSimultaneousProvisions ) {
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"});
//}
req.body.scenarioVersion = scenarioSource.version;
if ( req.body.autoShutdownData && req.body.autoShutdownData.is24x7 !== undefined ) {
const autoShutdown = await db.provisionAutoShutdown.add(req.body.autoShutdownData);
req.body.autoShutdown = autoShutdown._id;
if ( req.body.scheduleData && req.body.scheduleData.is24x7 !== undefined ) {
const schedule = await db.schedule.add(req.body.scheduleData);
req.body.schedule = schedule._id;
}
req.body.terraformImage = config.DOCKERIMAGE_TERRAFORM;
if ( req.body.scenario.indexOf('azqmi-qdi') !== -1 ) {
req.body.version = config.PROVISION_VERSION;
}
const mongoJob = await db.provision.add(req.body);
const provision = await db.provision.add(req.body);
if ( mongoJob.scenario === "azqmi-qseok" ){
if ( provision.scenario === "azqmi-qseok" ){
queues[TF_APPLY_QSEOK_QUEUE].add("tf_apply_qseok_job", {
scenario: req.body.scenario,
vmType: req.body.vmType,
vmType: req.body.vmType || null,
nodeCount: req.body.nodeCount,
id: mongoJob._id,
id: provision._id,
user: req.user,
_scenario: scenarioSource
});
@@ -195,13 +231,120 @@ router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (r
scenario: req.body.scenario,
vmType: req.body.vmType,
nodeCount: req.body.nodeCount,
id: mongoJob._id,
id: provision._id,
user: req.user,
_scenario: scenarioSource
});
}
return res.status(200).json(mongoJob);
return res.status(200).json(provision);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}:
* get:
* description: Get provision details
* summary: Get provision details
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: Provision
*/
router.get('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
let provision = await db.provision.getById(req.params.id);
return res.json(provision);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}:
* put:
* description: Update Provision by ID
* summary: Update Provision by ID
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* - in: body
* name: body
* description: Provision object
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: Provision
*/
router.put('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
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});
}
try {
let schedule;
var msg = "";
if ( req.body.scheduleData ) {
if ( req.body.scheduleData._id ) {
schedule = await db.schedule.update(req.body.scheduleData._id, req.body.scheduleData);
} else {
schedule = await db.schedule.add(req.body.scheduleData);
}
var tagsEdit = {
"24x7": schedule.is24x7? " " : false,
"StartupTime": (schedule.isStartupTimeEnable && !schedule.is24x7 && schedule.utcTagStartupTime)? schedule.utcTagStartupTime : false,
"ShutdownTime": (!schedule.is24x7 && schedule.utcTagShutdownTime)? schedule.utcTagShutdownTime : false
}
cli.updateVmsTags(provision._id, tagsEdit);
}
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}`;
}
let result = {
provision: await db.provision.update(provision._id, patch),
event: await db.event.updateMany({"provision": provision._id}, {"user": req.body.user})
}
db.event.add({ user: provision.user._id, provision: provision._id, type: 'provision.update', message: msg });
return res.json(result);
} catch (error) {
next(error);
@@ -235,26 +378,219 @@ router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (r
router.delete('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const mongoJob = await db.provision.getById(req.params.id);
if (!mongoJob){
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});
}
//var delDest = mongoJob.destroy._id;
//if ( mongoJob.destroy ) {
// delDest = await db.destroy.del(mongoJob.destroy._id);
//var delDest = provision.destroy._id;
//if ( provision.destroy ) {
// delDest = await db.destroy.del(provision.destroy._id);
//}
const delProv = await db.provision.update(req.params.id, {"isDeleted": true});
//Move folder
if (fs.existsSync(`/provisions/${mongoJob.scenario}_${req.params.id}`)) {
fs.moveSync(`/provisions/${mongoJob.scenario}_${req.params.id}`, `/provisions/deleted/${mongoJob.scenario}_${req.params.id}`, { overwrite: true })
if (fs.existsSync(`/provisions/${provision.scenario}_${req.params.id}`)) {
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' });
return res.json({"provision": delProv, "destroy": delProv.destroy});
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/barracuda:
* get:
* description: Barracuda - get details and provision status
* summary: Barracuda - get details and provision status
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.get('/:userId/provisions/:id/barracuda', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
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});
}
if ( !provision.barracudaAppId ) {
console.log(`APIUser# Provision (${req.params.id}) does not have a barracudaAppId value!`);
return res.status(404).json({"msg": "Not found Barracuda App for this provision"});
}
var app = await barracuda.getApp(provision);
return res.json(app);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/barracuda:
* post:
* description: Barracuda - give a provision external access
* summary: Barracuda - give a provision external access
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.post('/:userId/provisions/:id/barracuda', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
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});
}
if ( !provision.barracudaAzureFqdn ) {
console.log(`APIUser# Provision (${req.params.id}) does not have a barracudaAzureFqdn value!`);
return res.json(provision);
}
if ( provision.barracudaAppId ) {
console.log(`APIUser# Provision (${req.params.id}) already have a Barracuda App (${provision.barracudaAppId})!`);
return res.json(provision);
}
console.log(`APIUser# Calling Barracuda service to create App and DNS CName for provision (${provision._id})`);
barracuda.createApp(provision);
return res.json(provision);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/barracuda:
* delete:
* description: Barracuda - delete a provision external access
* summary: Barracuda - delete a provision external access
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.delete('/:userId/provisions/:id/barracuda', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
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});
}
if ( !provision.barracudaAppId ) {
console.log(`APIUser# Provision (${req.params.id}) does not have a barracudaAppId value!`);
return res.json({});
}
console.log(`APIUser# Calling Barracuda service to delete App and DNS CName for provision (${provision._id})`);
barracuda.deleteApp(provision);
return res.json(provision);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/abort:
* post:
* description: Abort provision
* summary: Abort provision
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.post('/:userId/provisions/:id/abort', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
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});
}
queues[STOP_CONTAINER_QUEUE].add("tf_abort_apply_job", {
provId: provision._id,
user: req.user
});
return res.json({"status": "aborting"});
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/deallocatevms:
@@ -282,11 +618,25 @@ router.delete('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, as
router.post('/:userId/provisions/:id/deallocatevms', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
let mongoJob = await db.provision.getById(req.params.id);
if (!mongoJob){
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});
}
azurecli.deallocate(mongoJob);
//Set DivvyTags according to Schedule
if ( provision.schedule && req.body.isStartupTimeEnable !== undefined ) {
console.log("APIUser# Set DivvyTags according to schedule");
var schedule = await db.schedule.update(provision.schedule._id, { "isStartupTimeEnable": req.body.isStartupTimeEnable });
var tagsEdit = {
"24x7": schedule.is24x7? " " : false,
"StartupTime": (schedule.isStartupTimeEnable && !schedule.is24x7 && schedule.utcTagStartupTime)? schedule.utcTagStartupTime : false,
"ShutdownTime": (!schedule.is24x7 && schedule.utcTagShutdownTime)? schedule.utcTagShutdownTime : false
}
cli.updateVmsTags(provision._id, tagsEdit);
}
cli.deallocate(provision._id);
return res.json({"statusVms": "Stopping"});
} catch (error) {
@@ -322,12 +672,27 @@ router.post('/:userId/provisions/:id/deallocatevms', passport.ensureAuthenticate
router.post('/:userId/provisions/:id/startvms', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
let mongoJob = await db.provision.getById(req.params.id);
if (!mongoJob){
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});
}
//Re-enable DivvyTags according to schedule
if ( provision.schedule ) {
let schedule = await db.schedule.getById(provision.schedule._id);
console.log("APIUser# Re-enabling DivvyTags according to schedule");
var tagsEdit = {
"24x7": schedule.is24x7? " " : false,
"StartupTime": (schedule.isStartupTimeEnable && !schedule.is24x7 && schedule.utcTagStartupTime)? schedule.utcTagStartupTime : false,
"ShutdownTime": (!schedule.is24x7 && schedule.utcTagShutdownTime)? schedule.utcTagShutdownTime : false
}
cli.updateVmsTags(provision._id, tagsEdit);
}
cli.start(provision._id);
azurecli.start(mongoJob);
return res.json({"statusVms": "Starting"});
} catch (error) {
@@ -363,20 +728,22 @@ router.post('/:userId/provisions/:id/startvms', passport.ensureAuthenticatedAndI
router.post('/:userId/provisions/:id/extend', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
let mongoJob = await db.provision.getById(req.params.id);
if (!mongoJob){
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});
}
/*if ( mongoJob.countExtend === 5 ) {
/*if ( provision.countExtend === 5 ) {
return res.status(200).json({"msg": "You have reached the limit for the number of times to extend the Running VMs period."});
}*/
let timeRunning = db.utils.getNewTimeRunning(mongoJob);
let countExtend = db.utils.getNewCountExtend(mongoJob);
mongoJob = await db.provision.update(req.params.id, {"runningFrom":new Date(), "timeRunning": timeRunning, "countExtend": countExtend, "pendingNextAction": undefined});
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});
return res.json(mongoJob);
console.log(`APIUser# Extending running period fo provision (${provision._id}), new total extends: ${countExtend}`);
return res.json(provision);
} catch (error) {
next(error);
@@ -410,25 +777,269 @@ router.post('/:userId/provisions/:id/extend', passport.ensureAuthenticatedAndIsM
router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
let mongoJob = await db.provision.getById(req.params.id);
if (!mongoJob){
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
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});
}
const destroyJob = await db.destroy.add({ "user": req.params.userId });
mongoJob = await db.provision.update(req.params.id, {"destroy": destroyJob._id});
const scenarioSource = await db.scenario.getOne({name: mongoJob.scenario});
if ( provision.destroy && provision.destroy.status !== 'error' ) {
console.log(`APIUser# This provision is already destroyed or being destroyed right now: ${provision._id}`);
return res.status(200).json(provision);
}
//
console.log(`APIUser# Queueing destroy provision: ${provision._id}`);
const destroyJob = await db.destroy.add({ "user": userId });
provision = await db.provision.update(req.params.id, {"destroy": destroyJob._id});
const scenarioSource = await db.scenario.getOne({name: provision.scenario});
queues[TF_DESTROY_QUEUE].add("tf_destroy_job", {
scenario: mongoJob.scenario,
provId: mongoJob._id,
scenario: provision.scenario,
provId: provision._id,
user: req.user,
id: destroyJob._id,
_scenario: scenarioSource
});
return res.status(200).json(mongoJob);
//Check children provisions
let children = await db.provision.get({ "parent": provision._id, "isDestroyed": false, "isDeleted": false });
if (children.results.length > 0 ) {
await asyncForEach(children.results, async function(child) {
if ( !child.destroy || child.destroy.status === 'error' ) {
console.log(`APIUser# Queueing destroy children provision: ${child._id}`);
let destroyJobChild = await db.destroy.add({ "user": userId });
await db.provision.update(child._id, {"destroy": destroyJobChild._id});
let scenarioSourceChild = await db.scenario.getOne({name: child.scenario});
queues[TF_DESTROY_QUEUE].add("tf_destroy_job", {
scenario: child.scenario,
provId: child._id,
user: req.user,
id: destroyJobChild._id,
_scenario: scenarioSourceChild
});
} else {
console.log(`APIUser# This child provision is already destroyed or being destroyed right now: ${child._id}`);
}
})
}
return res.status(200).json(provision);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share/{withUserId}:
* put:
* description: Share provision with another user
* summary: Share provision with another user
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* - name: withUserId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.put('/:userId/provisions/:id/share/:withUserId', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
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){
return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
}
let found = await db.sharedProvision.getOne({
"user": userId,
"provision": provision._id,
"sharedWithUser": req.params.withUserId
});
if ( !found ) {
found = await db.sharedProvision.add({"user": userId, "provision": provision._id, "sharedWithUser": req.params.withUserId})
}
return res.json(found);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share/{withUserId}:
* delete:
* description: Stop sharing this provision with another user
* summary: Stop sharing this provision with another user
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* - name: withUserId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.delete('/:userId/provisions/:id/share/:withUserId', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
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"});
}
const result = await db.sharedProvision.delMany({"user": userId, "provision": req.params.id, "sharedWithUser": req.params.withUserId});
return res.json(result);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share:
* delete:
* description: Stop sharing this provision with everybody
* summary: Stop sharing this provision with everybody
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.delete('/:userId/provisions/:id/share', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
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"});
}
const result = await db.sharedProvision.delMany({"user": userId, "provision": req.params.id});
return res.json(result);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share:
* get:
* description: Get shares of this provision
* summary: Get shares of this provision
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.get('/:userId/provisions/:id/share', passport.ensureAuthenticatedAndIsMe, 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});
return res.json(result);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/sharedprovisions:
* get:
* description: Get provision shared with me
* summary: Get provision shared with me
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.get('/:userId/sharedprovisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
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);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
@@ -455,7 +1066,8 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const filter = {"user": req.params.userId, "isDeleted": false};
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const filter = {"user": userId, "isDeleted": false};
const result = await db.provision.get(filter);
return res.json(result);
} catch (error) {
@@ -463,6 +1075,109 @@ router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (re
}
});
/**
* @swagger
* /users/{userId}/events:
* get:
* description: Get all Events for an User
* summary: Get all Events for an User
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: JSON Array
*/
router.get('/:userId/events', passport.ensureAuthenticatedAndIsMe, 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});
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/events:
* get:
* description: Get all Events for an User and Provision
* summary: Get all Events for an User and Provision
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: JSON Array
*/
router.get('/:userId/provisions/:id/events', passport.ensureAuthenticatedAndIsMe, 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});
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
* /users/{userId}/scenarios:
* get:
* description: Get all Provisions for an User
* summary: Get all Provisions for an User
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: JSON Array
*/
router.get('/:userId/scenarios', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
let filter = {};
if (req.user.role === "user") {
filter.isAdminOnly = false;
}
filter.isDisabled = filter.isDisabled || false;
var result = await db.scenario.get(filter);
if (req.user.role === "user") {
result.results = result.results.filter( scenario => {
let noAllowedUsers = !scenario.allowedUsers || scenario.allowedUsers.length === 0;
if ( noAllowedUsers ) {
return true;
} else {
let allowedUserIds = scenario.allowedUsers.map( u=> u._id.toString());
return allowedUserIds.indexOf(req.user._id.toString()) !== -1;
}
});
}
return res.json(result);
} catch (error) {
next(error);
}
});
/**
* @swagger
@@ -484,7 +1199,8 @@ router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (re
router.get('/:userId/destroyprovisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const result = await db.destroy.get({"user": req.params.userId});
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const result = await db.destroy.get({"user": userId});
return res.json(result);
} catch (error) {
next(error);

View File

@@ -2,7 +2,7 @@ const url = require("url");
const express = require("express");
import Arena from 'bull-arena';
import { TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from 'qmi-cloud-common/queues';
import { TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE } from 'qmi-cloud-common/queues';
const app = express();
const routesApiScenarios = require('./routes/api-scenarios');
@@ -13,6 +13,7 @@ const routesApiNotifications = require('./routes/api-notifications');
const routesApiDivvy = require('./routes/api-divvy');
const routesApiDeployOpts = require('./routes/api-deployopts')
const routesApiApikeys = require('./routes/api-apikeys')
const routesApiStats = require('./routes/api-stats')
const swaggerUi = require('swagger-ui-express');
const swaggerJsdoc = require('swagger-jsdoc');
const cookieParser = require('cookie-parser');
@@ -33,6 +34,7 @@ function _getRedisConfig(redisUrl) {
};
}
app.use('/arena', Arena(
{
queues: [
@@ -50,6 +52,11 @@ app.use('/arena', Arena(
name: TF_DESTROY_QUEUE,
hostId: 'Worker',
redis: _getRedisConfig(process.env.REDIS_URL)
},
{
name: STOP_CONTAINER_QUEUE,
hostId: 'Worker',
redis: _getRedisConfig(process.env.REDIS_URL)
}
]
},
@@ -71,7 +78,8 @@ app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(express.static(__dirname + '/../dist/qmi-cloud'));
app.use('/',express.static(__dirname + '/../dist/qmi-cloud'));
passport.init(app);
@@ -83,14 +91,29 @@ app.use("/api/v1/notifications", routesApiNotifications);
app.use("/api/v1/divvy", routesApiDivvy);
app.use("/api/v1/deployopts", routesApiDeployOpts);
app.use("/api/v1/apikeys", routesApiApikeys);
app.use("/api/v1/stats", routesApiStats);
function _isAllowedPath(path){
const allowedPaths = [ '/api-docs', '/arena', '/costexport', '/backendlogs', '/photos/user/' ];
let isAllowed = false;
for (let i=0; i<allowedPaths.length; i++) {
if ( path.startsWith( allowedPaths[i]) ) {
isAllowed = true;
break;
}
}
return isAllowed;
}
/* Checking allowedPaths */
app.get('/*',(req, res, next) =>{
if (req.originalUrl.indexOf("/api-docs") !== -1 || req.originalUrl.indexOf("/arena") !== -1 ) {
if ( _isAllowedPath(req.originalUrl) ) {
return next();
} else {
res.sendFile(path.join(__dirname,'/../dist/qmi-cloud/index.html'));
}
});
/* -----------------------*/
app.get('/login', passport.ensureAuthenticatedDoLogin, function(req, res) {
res.redirect("/");
@@ -100,6 +123,10 @@ app.get('/logout', function(req, res) {
res.redirect("/");
});
app.get('/backendlogs', function (req, res) {
res.redirect(process.env.BACKEND_LOGS_URL);
})
const options = {
definition: {
@@ -125,25 +152,7 @@ const options = {
name: "apiKey",
in: "query"
}
},
/*schemas: {
"user": {
"properties": {
"displayName": {
"type": "string"
},
"upn": {
"type": "string"
},
"oid": {
"type": "string"
},
"role": {
"type": "string"
}
}
}
}*/
}
},
security: [{
ApiKeyAuth: []
@@ -155,16 +164,41 @@ const options = {
]
};
app.use('/costexport*', passport.ensureAuthenticatedAndAdmin, function(req, res){
if ( !req.query.file ) {
res.status(404).send("Not found");
} else {
res.header("Content-Type",'application/json');
res.sendFile(path.resolve(__dirname, '..', 'costexport', req.query.file ));
}
} );
app.use('/photos/user/:oid', passport.ensureAuthenticated, function(req, res){
if ( !req.params.oid ) {
res.status(404).send("Not found");
} else {
var pic = path.resolve(__dirname, '..', 'photos', `${req.params.oid}.jpg` );
if (fs.existsSync(pic)){
res.sendFile(pic);
} else {
res.status(404).send();
}
}
} );
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
/**
* Create necessary folders
*/
console.log("--- Create necessary folders");
var dirs = ['/logs', '/logs/provision', '/logs/destroy'];
var dirs = ['/logs', '/logs/provision', '/logs/destroy', '/costexports', '/photos'];
dirs.forEach(d => {
if (!fs.existsSync(d)){
console.log(`--- Creating folder '${d}' since it does not exist`);
fs.mkdirSync(d);
}
});

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