From 5695b00d2b0f8287b065661bfccfc8900ddced5c Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Thu, 17 Mar 2022 16:58:50 +0100 Subject: [PATCH] F #5655: Implement Guacamole remote console (#1849) (cherry picked from commit 1154d568c34a089b79af3314ec94fe0c5fcbe1e8) --- src/fireedge/package-lock.json | 888 +++++++++--------- src/fireedge/package.json | 1 + .../src/client/apps/sunstone/index.js | 4 +- .../src/client/apps/sunstone/routes.js | 10 + .../components/Buttons/ConsoleAction.js | 108 +++ .../src/client/components/Buttons/index.js | 1 + .../components/Cards/VirtualMachineCard.js | 5 +- .../components/Consoles/Guacamole/buttons.js | 213 +++++ .../components/Consoles/Guacamole/client.js | 217 +++++ .../components/Consoles/Guacamole/index.js | 63 ++ .../Consoles/Guacamole/plugins/clipboard.js | 158 ++++ .../Consoles/Guacamole/plugins/display.js | 147 +++ .../Consoles/Guacamole/plugins/index.js | 20 + .../Consoles/Guacamole/plugins/keyboard.js | 55 ++ .../Consoles/Guacamole/plugins/mouse.js | 78 ++ .../components/Consoles/Guacamole/utils.js | 41 + .../src/client/components/Consoles/index.js | 16 + .../inputOutput/graphicsSchema.js | 6 +- .../src/client/components/HOC/AuthLayout.js | 3 + .../components/HOC/InternalLayout/index.js | 30 +- .../components/HOC/InternalLayout/styles.js | 11 +- .../src/client/components/Header/Popover.js | 7 +- .../src/client/components/Header/index.js | 57 +- .../src/client/components/Sidebar/index.js | 14 + .../src/client/components/Tables/Vms/index.js | 52 +- .../src/client/components/Tables/Vms/row.js | 32 +- .../client/components/Tabs/Cluster/index.js | 12 +- .../client/components/Tabs/Datastore/index.js | 12 +- .../src/client/components/Tabs/Group/index.js | 12 +- .../src/client/components/Tabs/Host/index.js | 12 +- .../src/client/components/Tabs/Image/index.js | 12 +- .../components/Tabs/Marketplace/index.js | 12 +- .../components/Tabs/MarketplaceApp/index.js | 12 +- .../src/client/components/Tabs/User/index.js | 12 +- .../client/components/Tabs/VNetwork/index.js | 12 +- .../components/Tabs/VNetworkTemplate/index.js | 12 +- .../src/client/components/Tabs/Vm/index.js | 12 +- .../components/Tabs/VmTemplate/index.js | 12 +- .../src/client/components/Tabs/Zone/index.js | 12 +- .../src/client/constants/guacamole.js | 101 ++ src/fireedge/src/client/constants/index.js | 30 +- .../src/client/constants/translates.js | 19 +- src/fireedge/src/client/constants/vm.js | 13 +- .../src/client/containers/Guacamole/index.js | 152 +++ .../src/client/features/Auth/hooks.js | 16 +- .../src/client/features/Guacamole/hooks.js | 73 ++ .../src/client/features/Guacamole/index.js | 17 + .../src/client/features/Guacamole/slice.js | 126 +++ src/fireedge/src/client/features/OneApi/vm.js | 66 +- .../src/client/features/middleware.js | 10 +- src/fireedge/src/client/models/Guacamole.js | 56 ++ .../src/client/models/VirtualMachine.js | 14 + src/fireedge/src/client/router/index.js | 6 +- src/fireedge/src/client/store/index.js | 2 + src/fireedge/src/client/utils/helpers.js | 26 + src/fireedge/src/server/index.js | 2 +- .../src/server/routes/api/auth/utils.js | 11 +- .../src/server/routes/api/vm/functions.js | 230 ++++- .../src/server/routes/api/vm/index.js | 11 +- .../src/server/routes/api/vm/routes.js | 20 +- 60 files changed, 2780 insertions(+), 614 deletions(-) create mode 100644 src/fireedge/src/client/components/Buttons/ConsoleAction.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/buttons.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/client.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/index.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/plugins/clipboard.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/plugins/display.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/plugins/index.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/plugins/keyboard.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/plugins/mouse.js create mode 100644 src/fireedge/src/client/components/Consoles/Guacamole/utils.js create mode 100644 src/fireedge/src/client/components/Consoles/index.js create mode 100644 src/fireedge/src/client/constants/guacamole.js create mode 100644 src/fireedge/src/client/containers/Guacamole/index.js create mode 100644 src/fireedge/src/client/features/Guacamole/hooks.js create mode 100644 src/fireedge/src/client/features/Guacamole/index.js create mode 100644 src/fireedge/src/client/features/Guacamole/slice.js create mode 100644 src/fireedge/src/client/models/Guacamole.js diff --git a/src/fireedge/package-lock.json b/src/fireedge/package-lock.json index 916f89acbb..d2d67ff4d1 100644 --- a/src/fireedge/package-lock.json +++ b/src/fireedge/package-lock.json @@ -47,6 +47,7 @@ "fast-xml-parser": "3.19.0", "fs-extra": "9.0.1", "fuse.js": "6.4.1", + "guacamole-common-js": "1.3.1", "helmet": "4.1.1", "http": "0.0.1-security", "http-proxy-middleware": "1.0.5", @@ -220,9 +221,9 @@ } }, "node_modules/@babel/generator": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.0.tgz", - "integrity": "sha512-I3Omiv6FGOC29dtlZhkfXO6pgkmukJSlT26QjVvS1DGZe/NzSVCPG41X0tS21oZkJYlovfj9qDWgKP+Cn4bXxw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", + "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", "dependencies": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -273,9 +274,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz", - "integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7", @@ -405,9 +406,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", + "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -415,8 +416,8 @@ "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -533,9 +534,9 @@ } }, "node_modules/@babel/helpers": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.0.tgz", - "integrity": "sha512-Xe/9NFxjPwELUvW2dsukcMZIp6XwPSbI4ojFBJuX5ramHuVE22SVcZIwqzdWo5uCgeTXW8qV97lMvSOjq+1+nQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", "dependencies": { "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.0", @@ -581,9 +582,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.0.tgz", - "integrity": "sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -655,11 +656,11 @@ } }, "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz", - "integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", + "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.17.6", "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, @@ -1127,9 +1128,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz", - "integrity": "sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", + "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1394,15 +1395,15 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.7.tgz", - "integrity": "sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", + "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-jsx": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -1707,9 +1708,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", - "integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -1718,9 +1719,9 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.0.tgz", - "integrity": "sha512-qeydncU80ravKzovVncW3EYaC1ji3GpntdPgNcJy9g7hHSY6KX+ne1cbV3ov7Zzm4F1z0+QreZPCuw1ynkmYNg==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz", + "integrity": "sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg==", "dev": true, "dependencies": { "core-js-pure": "^3.20.2", @@ -1744,17 +1745,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.0.tgz", - "integrity": "sha512-fpFIXvqD6kC7c7PUNnZ0Z8cQXlarCLtCUpt2S1Dx7PjoRtCFffvOkHHSom+m5HIxMZn5bIBVb71lhabcmjEsqg==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dependencies": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.0", + "@babel/generator": "^7.17.3", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.0", + "@babel/parser": "^7.17.3", "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" @@ -1775,10 +1776,18 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@dabh/diagnostics": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", - "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", @@ -1855,9 +1864,9 @@ } }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", - "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "engines": { "node": ">=10.0.0" } @@ -1902,9 +1911,9 @@ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz", - "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", "dependencies": { "@emotion/memoize": "^0.7.4" } @@ -1988,9 +1997,9 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "node_modules/@emotion/utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz", - "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" }, "node_modules/@emotion/weak-memoize": { "version": "0.2.5", @@ -2212,6 +2221,7 @@ "version": "5.0.0-alpha.54", "resolved": "https://registry.npmjs.org/@mui/core/-/core-5.0.0-alpha.54.tgz", "integrity": "sha512-8TxdHqDdSb6wjhsnpE5n7qtkFKDG3PUSlVY0gR3VcdsHXscUY13l3VbMQW1brI4D/R9zx5VYmxIHWaHFgw4RtA==", + "deprecated": "You can now upgrade to @mui/base. See https://github.com/mui/material-ui/releases/tag/v5.1.1", "dependencies": { "@babel/runtime": "^7.16.0", "@emotion/is-prop-valid": "^1.1.0", @@ -2295,17 +2305,17 @@ } }, "node_modules/@mui/lab/node_modules/@mui/system": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.0.tgz", - "integrity": "sha512-LX7g5gK5yCwiueSUVG73uVNc0yeHjsWUIFLrnPjP3m+J7O38RkPqyao5nZahhaSL1PGNbR9+zfkxljXthO9QqA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.5.0.tgz", + "integrity": "sha512-zFOfERv3Y4m5ehwTRR9cGaPuMvlD2qVXmFKC60P0Gte3aD6vYObyNriZv+mDVGlhDxZTZhxBrNPH3ns25xSFtQ==", "dependencies": { - "@babel/runtime": "^7.16.7", - "@mui/private-theming": "^5.3.0", - "@mui/styled-engine": "^5.3.0", - "@mui/types": "^7.1.0", - "@mui/utils": "^5.3.0", + "@babel/runtime": "^7.17.2", + "@mui/private-theming": "^5.4.4", + "@mui/styled-engine": "^5.4.4", + "@mui/types": "^7.1.2", + "@mui/utils": "^5.4.4", "clsx": "^1.1.1", - "csstype": "^3.0.10", + "csstype": "^3.0.11", "prop-types": "^15.7.2" }, "engines": { @@ -2418,12 +2428,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.3.0.tgz", - "integrity": "sha512-EBobUEyM9fMnteKrVPp8pTMUh81xXakyfdpkoh7Y19q9JpD2eh7QGAQVJVj0JBFlcUJD60NIE4K8rdokrRmLwg==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.4.4.tgz", + "integrity": "sha512-V/gxttr6736yJoU9q+4xxXsa0K/w9Hn9pg99zsOHt7i/O904w2CX5NHh5WqDXtoUzVcayLF0RB17yr6l79CE+A==", "dependencies": { - "@babel/runtime": "^7.16.7", - "@mui/utils": "^5.3.0", + "@babel/runtime": "^7.17.2", + "@mui/utils": "^5.4.4", "prop-types": "^15.7.2" }, "engines": { @@ -2444,11 +2454,11 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.3.0.tgz", - "integrity": "sha512-I4YemFy9WnCLUdZ5T+6egpzc8e7Jq/uh9AJ3QInZHbyNu/9I2SWvNn7vHjWOT/D8Y8LMzIOhu5WwZbzjez7YRw==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.4.4.tgz", + "integrity": "sha512-AKx3rSgB6dmt5f7iP4K18mLFlE5/9EfJe/5EH9Pyqez8J/CPkTgYhJ/Va6qtlrcunzpui+uG/vfuf04yAZekSg==", "dependencies": { - "@babel/runtime": "^7.16.7", + "@babel/runtime": "^7.17.2", "@emotion/cache": "^11.7.1", "prop-types": "^15.7.2" }, @@ -2553,9 +2563,9 @@ } }, "node_modules/@mui/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.0.tgz", - "integrity": "sha512-Hh7ALdq/GjfIwLvqH3XftuY3bcKhupktTm+S6qRIDGOtPtRuq2L21VWzOK4p7kblirK0XgGVH5BLwa6u8z/6QQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.2.tgz", + "integrity": "sha512-SD7O1nVzqG+ckQpFjDhXPZjRceB8HQFHEvdLLrPhlJy4lLbwEBbxK74Tj4t6Jgk0fTvLJisuwOutrtYe9P/xBQ==", "peerDependencies": { "@types/react": "*" }, @@ -2566,11 +2576,11 @@ } }, "node_modules/@mui/utils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.3.0.tgz", - "integrity": "sha512-O/E9IQKPMg0OrN7+gkn7Ga5o5WA2iXQGdyqNBFPNrYzxOvwzsEtM5K7MtTCGGYKFe8mhTRM0ZOjh5OM0dglw+Q==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.4.4.tgz", + "integrity": "sha512-hfYIXEuhc2mXMGN5nUPis8beH6uE/zl3uMWJcyHX0/LN/+QxO9zhYuV6l8AsAaphHFyS/fBv0SW3Nid7jw5hKQ==", "dependencies": { - "@babel/runtime": "^7.16.7", + "@babel/runtime": "^7.17.2", "@types/prop-types": "^15.7.4", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.7.2", @@ -2690,9 +2700,9 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -2915,9 +2925,9 @@ } }, "node_modules/@types/d3-path": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.1.tgz", - "integrity": "sha512-6K8LaFlztlhZO7mwsZg7ClRsdLg3FJRzIIi6SZXDWmmSJc2x8dd2VkESbLXdk3p8cuvz71f36S0y8Zv2AxqvQw==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.2.tgz", + "integrity": "sha512-3YHpvDw9LzONaJzejXLOwZ3LqwwkoXb9LI2YN7Hbd6pkGo5nIlJ09ul4bQhBN4hQZJKmUpX8HkVqbzgUKY48cg==" }, "node_modules/@types/d3-polygon": { "version": "2.0.1", @@ -3049,14 +3059,14 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + "version": "4.14.179", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz", + "integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==" }, "node_modules/@types/node": { - "version": "17.0.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.15.tgz", - "integrity": "sha512-zWt4SDDv1S9WRBNxLFxFRHxdD9tvH8f5/kg5/IaLFdnSNXsDY4eL3Q3XXN+VxUnWIhyVFDwcsmAprvwXoM/ClA==" + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -3069,9 +3079,9 @@ "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, "node_modules/@types/react": { - "version": "17.0.39", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", - "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", + "version": "17.0.40", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.40.tgz", + "integrity": "sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3087,9 +3097,9 @@ } }, "node_modules/@types/react-redux": { - "version": "7.1.22", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.22.tgz", - "integrity": "sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==", + "version": "7.1.23", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.23.tgz", + "integrity": "sha512-D02o3FPfqQlfu2WeEYwh3x2otYd2Dk1o8wAfsA0B1C2AJEFxE663Ozu7JzuWbznGgW248NaOF6wsqCGNq9d3qw==", "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -4107,14 +4117,14 @@ } }, "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.0.tgz", + "integrity": "sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==", "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001313", + "electron-to-chromium": "^1.4.76", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.2", "picocolors": "^1.0.0" }, "bin": { @@ -4240,9 +4250,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==", + "version": "1.0.30001316", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001316.tgz", + "integrity": "sha512-JgUdNoZKxPZFzbzJwy4hDSyGuH/gXz2rN51QmoR8cBQsVo58llD3A0vlRKKRt8FGf5u69P9eQyIH8/z9vN/S0Q==", "funding": { "type": "opencollective", "url": "https://opencollective.com/browserslist" @@ -4404,14 +4414,6 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -4650,9 +4652,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.0.tgz", - "integrity": "sha512-OSXseNPSK2OPJa6GdtkMz/XxeXx8/CJvfhQWTqd6neuUraujcL4jVsjkLQz1OWnax8xVQJnRPe0V2jqNWORA+A==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", + "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", "dependencies": { "browserslist": "^4.19.1", "semver": "7.0.0" @@ -4671,9 +4673,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.0.tgz", - "integrity": "sha512-VaJUunCZLnxuDbo1rNOzwbet9E1K9joiXS5+DQMPtgxd24wfsZbJZMMfQLGYMlCUvSxLfsRUUhoOR2x28mFfeg==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", + "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", "dev": true, "hasInstallScript": true, "funding": { @@ -4877,9 +4879,9 @@ } }, "node_modules/csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "node_modules/d3-color": { "version": "2.0.0", @@ -5192,9 +5194,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron-to-chromium": { - "version": "1.4.65", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.65.tgz", - "integrity": "sha512-0/d8Skk8sW3FxXP0Dd6MnBlrwx7Qo9cqQec3BlIAlvKnrmS3pHsIbaroEi+nd0kZkGpQ6apMEre7xndzjlEnLw==" + "version": "1.4.82", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.82.tgz", + "integrity": "sha512-Ks+ANzLoIrFDUOJdjxYMH6CMKB8UQo5modAwvSZTxgF+vEs/U7G5IbWFUp6dS4klPkTDVdxbORuk8xAXXhMsWw==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -5253,9 +5255,9 @@ } }, "node_modules/engine.io": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.2.tgz", - "integrity": "sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", + "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -5265,7 +5267,7 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3" }, "engines": { @@ -5393,9 +5395,9 @@ } }, "node_modules/error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.7.tgz", + "integrity": "sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==", "dev": true, "dependencies": { "stackframe": "^1.1.1" @@ -6694,9 +6696,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "funding": [ { "type": "individual", @@ -7033,6 +7035,11 @@ "lodash": "^4.17.15" } }, + "node_modules/guacamole-common-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/guacamole-common-js/-/guacamole-common-js-1.3.1.tgz", + "integrity": "sha512-PrB+Z4DKERXyrTt20JKkLteR7ZgesD/HihwnnHdo0cwgsgv1i0898is6IUTo0iBHCEcH9vLJDULax18EUJSgvg==" + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -7087,9 +7094,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -7822,9 +7829,9 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "node_modules/jest-worker": { - "version": "27.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.0.tgz", - "integrity": "sha512-8OEHiPNOPTfaWnJ2SUHM8fmgeGq37uuGsQBvGKQJl1f+6WIy6g7G3fE2ruI5294bUKUI9FaCWt5hDvO8HSwsSg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -7880,9 +7887,9 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "node_modules/jsdoc-type-pratt-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.2.tgz", - "integrity": "sha512-zRokSWcPLSWkoNzsWn9pq7YYSwDhKyEe+cJYT2qaPqLOOJb5sFSi46BPj81vP+e8chvCNdQL9RG86Bi9EI6MDw==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.5.tgz", + "integrity": "sha512-2a6eRxSxp1BW040hFvaJxhsCMI9lT8QB8t14t+NY5tC5rckIR0U9cr2tjOeaFirmEOy6MHvmJnY7zTBHq431Lw==", "dev": true, "engines": { "node": ">=12.0.0" @@ -8299,14 +8306,14 @@ "dev": true }, "node_modules/logform": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.3.2.tgz", - "integrity": "sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz", + "integrity": "sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==", "dependencies": { - "colors": "1.4.0", + "@colors/colors": "1.5.0", "fecha": "^4.2.0", "ms": "^2.1.1", - "safe-stable-stringify": "^1.1.0", + "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, @@ -8482,19 +8489,19 @@ } }, "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -8545,9 +8552,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8646,9 +8653,9 @@ "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" }, "node_modules/nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -8788,9 +8795,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, "node_modules/node-zendesk": { "version": "2.1.0", @@ -9494,11 +9501,11 @@ } }, "node_modules/postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz", + "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==", "dependencies": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -9753,9 +9760,9 @@ } }, "node_modules/qrcode/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "engines": { "node": ">=6" } @@ -10669,9 +10676,12 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", - "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", + "engines": { + "node": ">=10" + } }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -11000,9 +11010,9 @@ } }, "node_modules/socket.io-client/node_modules/socket.io-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.1.tgz", - "integrity": "sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", + "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", "dependencies": { "@socket.io/component-emitter": "~3.0.0", "debug": "~4.3.1" @@ -11133,9 +11143,9 @@ } }, "node_modules/stackframe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", - "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", + "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==", "dev": true }, "node_modules/statuses": { @@ -11481,6 +11491,23 @@ "node": ">= 6" } }, + "node_modules/terser": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.0.tgz", + "integrity": "sha512-R3AUhNBGWiFc77HXag+1fXpAxTAFRQTJemlJKjAgD9r8xXTpjNKqIXwHM/o7Rh+O0kUJtS3WQVdBeMKFk5sw9A==", + "dependencies": { + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/terser-webpack-plugin": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", @@ -11504,24 +11531,6 @@ "webpack": "^5.1.0" } }, - "node_modules/terser-webpack-plugin/node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "optional": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "node_modules/terser-webpack-plugin/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11530,31 +11539,23 @@ "node": ">=0.10.0" } }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, + "node_modules/terser/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "bin": { - "terser": "bin/terser" + "acorn": "bin/acorn" }, "engines": { - "node": ">=10" - }, - "peerDependencies": { - "acorn": "^8.5.0" - }, - "peerDependenciesMeta": { - "acorn": { - "optional": true - } + "node": ">=0.4.0" } }, - "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": { + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/terser/node_modules/source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", @@ -11675,9 +11676,9 @@ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "node_modules/tsconfig-paths": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", - "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz", + "integrity": "sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -12185,9 +12186,9 @@ } }, "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", + "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -12239,9 +12240,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "node_modules/which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "engines": { + "node": ">=4" + } }, "node_modules/wide-align": { "version": "1.1.5", @@ -12601,9 +12605,9 @@ } }, "@babel/generator": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.0.tgz", - "integrity": "sha512-I3Omiv6FGOC29dtlZhkfXO6pgkmukJSlT26QjVvS1DGZe/NzSVCPG41X0tS21oZkJYlovfj9qDWgKP+Cn4bXxw==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", + "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", "requires": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -12639,9 +12643,9 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.17.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz", - "integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7", @@ -12735,9 +12739,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", + "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", "requires": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -12745,8 +12749,8 @@ "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/helper-optimise-call-expression": { @@ -12830,9 +12834,9 @@ } }, "@babel/helpers": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.0.tgz", - "integrity": "sha512-Xe/9NFxjPwELUvW2dsukcMZIp6XwPSbI4ojFBJuX5ramHuVE22SVcZIwqzdWo5uCgeTXW8qV97lMvSOjq+1+nQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", "requires": { "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.0", @@ -12863,9 +12867,9 @@ } }, "@babel/parser": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.0.tgz", - "integrity": "sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw==" + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.16.7", @@ -12909,11 +12913,11 @@ } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz", - "integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", + "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.17.6", "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" } @@ -13210,9 +13214,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz", - "integrity": "sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", + "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -13369,15 +13373,15 @@ } }, "@babel/plugin-transform-react-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.7.tgz", - "integrity": "sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", + "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-jsx": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/plugin-transform-react-jsx-development": { @@ -13589,17 +13593,17 @@ } }, "@babel/runtime": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", - "integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.0.tgz", - "integrity": "sha512-qeydncU80ravKzovVncW3EYaC1ji3GpntdPgNcJy9g7hHSY6KX+ne1cbV3ov7Zzm4F1z0+QreZPCuw1ynkmYNg==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz", + "integrity": "sha512-NcKtr2epxfIrNM4VOmPKO46TvDMCBhgi2CrSHaEarrz+Plk2K5r9QemmOFTGpZaoKnWoGH5MO+CzeRsih/Fcgg==", "dev": true, "requires": { "core-js-pure": "^3.20.2", @@ -13617,17 +13621,17 @@ } }, "@babel/traverse": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.0.tgz", - "integrity": "sha512-fpFIXvqD6kC7c7PUNnZ0Z8cQXlarCLtCUpt2S1Dx7PjoRtCFffvOkHHSom+m5HIxMZn5bIBVb71lhabcmjEsqg==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.0", + "@babel/generator": "^7.17.3", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.0", + "@babel/parser": "^7.17.3", "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" @@ -13642,10 +13646,15 @@ "to-fast-properties": "^2.0.0" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, "@dabh/diagnostics": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", - "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", "requires": { "colorspace": "1.1.x", "enabled": "2.0.x", @@ -13690,9 +13699,9 @@ } }, "@discoveryjs/json-ext": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", - "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==" + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==" }, "@emotion/babel-plugin": { "version": "11.7.2", @@ -13731,9 +13740,9 @@ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, "@emotion/is-prop-valid": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz", - "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", "requires": { "@emotion/memoize": "^0.7.4" } @@ -13792,9 +13801,9 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, "@emotion/utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz", - "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" }, "@emotion/weak-memoize": { "version": "0.2.5", @@ -13978,17 +13987,17 @@ }, "dependencies": { "@mui/system": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.4.0.tgz", - "integrity": "sha512-LX7g5gK5yCwiueSUVG73uVNc0yeHjsWUIFLrnPjP3m+J7O38RkPqyao5nZahhaSL1PGNbR9+zfkxljXthO9QqA==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.5.0.tgz", + "integrity": "sha512-zFOfERv3Y4m5ehwTRR9cGaPuMvlD2qVXmFKC60P0Gte3aD6vYObyNriZv+mDVGlhDxZTZhxBrNPH3ns25xSFtQ==", "requires": { - "@babel/runtime": "^7.16.7", - "@mui/private-theming": "^5.3.0", - "@mui/styled-engine": "^5.3.0", - "@mui/types": "^7.1.0", - "@mui/utils": "^5.3.0", + "@babel/runtime": "^7.17.2", + "@mui/private-theming": "^5.4.4", + "@mui/styled-engine": "^5.4.4", + "@mui/types": "^7.1.2", + "@mui/utils": "^5.4.4", "clsx": "^1.1.1", - "csstype": "^3.0.10", + "csstype": "^3.0.11", "prop-types": "^15.7.2" } }, @@ -14048,21 +14057,21 @@ } }, "@mui/private-theming": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.3.0.tgz", - "integrity": "sha512-EBobUEyM9fMnteKrVPp8pTMUh81xXakyfdpkoh7Y19q9JpD2eh7QGAQVJVj0JBFlcUJD60NIE4K8rdokrRmLwg==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.4.4.tgz", + "integrity": "sha512-V/gxttr6736yJoU9q+4xxXsa0K/w9Hn9pg99zsOHt7i/O904w2CX5NHh5WqDXtoUzVcayLF0RB17yr6l79CE+A==", "requires": { - "@babel/runtime": "^7.16.7", - "@mui/utils": "^5.3.0", + "@babel/runtime": "^7.17.2", + "@mui/utils": "^5.4.4", "prop-types": "^15.7.2" } }, "@mui/styled-engine": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.3.0.tgz", - "integrity": "sha512-I4YemFy9WnCLUdZ5T+6egpzc8e7Jq/uh9AJ3QInZHbyNu/9I2SWvNn7vHjWOT/D8Y8LMzIOhu5WwZbzjez7YRw==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.4.4.tgz", + "integrity": "sha512-AKx3rSgB6dmt5f7iP4K18mLFlE5/9EfJe/5EH9Pyqez8J/CPkTgYhJ/Va6qtlrcunzpui+uG/vfuf04yAZekSg==", "requires": { - "@babel/runtime": "^7.16.7", + "@babel/runtime": "^7.17.2", "@emotion/cache": "^11.7.1", "prop-types": "^15.7.2" } @@ -14107,17 +14116,17 @@ } }, "@mui/types": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.0.tgz", - "integrity": "sha512-Hh7ALdq/GjfIwLvqH3XftuY3bcKhupktTm+S6qRIDGOtPtRuq2L21VWzOK4p7kblirK0XgGVH5BLwa6u8z/6QQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.2.tgz", + "integrity": "sha512-SD7O1nVzqG+ckQpFjDhXPZjRceB8HQFHEvdLLrPhlJy4lLbwEBbxK74Tj4t6Jgk0fTvLJisuwOutrtYe9P/xBQ==", "requires": {} }, "@mui/utils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.3.0.tgz", - "integrity": "sha512-O/E9IQKPMg0OrN7+gkn7Ga5o5WA2iXQGdyqNBFPNrYzxOvwzsEtM5K7MtTCGGYKFe8mhTRM0ZOjh5OM0dglw+Q==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.4.4.tgz", + "integrity": "sha512-hfYIXEuhc2mXMGN5nUPis8beH6uE/zl3uMWJcyHX0/LN/+QxO9zhYuV6l8AsAaphHFyS/fBv0SW3Nid7jw5hKQ==", "requires": { - "@babel/runtime": "^7.16.7", + "@babel/runtime": "^7.17.2", "@types/prop-types": "^15.7.4", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.7.2", @@ -14186,9 +14195,9 @@ } }, "@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==" + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==" }, "@reach/observe-rect": { "version": "1.2.0", @@ -14392,9 +14401,9 @@ } }, "@types/d3-path": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.1.tgz", - "integrity": "sha512-6K8LaFlztlhZO7mwsZg7ClRsdLg3FJRzIIi6SZXDWmmSJc2x8dd2VkESbLXdk3p8cuvz71f36S0y8Zv2AxqvQw==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-2.0.2.tgz", + "integrity": "sha512-3YHpvDw9LzONaJzejXLOwZ3LqwwkoXb9LI2YN7Hbd6pkGo5nIlJ09ul4bQhBN4hQZJKmUpX8HkVqbzgUKY48cg==" }, "@types/d3-polygon": { "version": "2.0.1", @@ -14526,14 +14535,14 @@ "dev": true }, "@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + "version": "4.14.179", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz", + "integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==" }, "@types/node": { - "version": "17.0.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.15.tgz", - "integrity": "sha512-zWt4SDDv1S9WRBNxLFxFRHxdD9tvH8f5/kg5/IaLFdnSNXsDY4eL3Q3XXN+VxUnWIhyVFDwcsmAprvwXoM/ClA==" + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==" }, "@types/parse-json": { "version": "4.0.0", @@ -14546,9 +14555,9 @@ "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==" }, "@types/react": { - "version": "17.0.39", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.39.tgz", - "integrity": "sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==", + "version": "17.0.40", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.40.tgz", + "integrity": "sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -14564,9 +14573,9 @@ } }, "@types/react-redux": { - "version": "7.1.22", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.22.tgz", - "integrity": "sha512-GxIA1kM7ClU73I6wg9IRTVwSO9GS+SAKZKe0Enj+82HMU6aoESFU2HNAdNi3+J53IaOHPiUfT3kSG4L828joDQ==", + "version": "7.1.23", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.23.tgz", + "integrity": "sha512-D02o3FPfqQlfu2WeEYwh3x2otYd2Dk1o8wAfsA0B1C2AJEFxE663Ozu7JzuWbznGgW248NaOF6wsqCGNq9d3qw==", "requires": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -15406,14 +15415,14 @@ } }, "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.0.tgz", + "integrity": "sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==", "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001313", + "electron-to-chromium": "^1.4.76", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.2", "picocolors": "^1.0.0" } }, @@ -15508,9 +15517,9 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001307", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001307.tgz", - "integrity": "sha512-+MXEMczJ4FuxJAUp0jvAl6Df0NI/OfW1RWEE61eSmzS7hw6lz4IKutbhbXendwq8BljfFuHtu26VWsg4afQ7Ng==" + "version": "1.0.30001316", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001316.tgz", + "integrity": "sha512-JgUdNoZKxPZFzbzJwy4hDSyGuH/gXz2rN51QmoR8cBQsVo58llD3A0vlRKKRt8FGf5u69P9eQyIH8/z9vN/S0Q==" }, "caseless": { "version": "0.12.0", @@ -15641,11 +15650,6 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" - }, "colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -15845,9 +15849,9 @@ "integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==" }, "core-js-compat": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.0.tgz", - "integrity": "sha512-OSXseNPSK2OPJa6GdtkMz/XxeXx8/CJvfhQWTqd6neuUraujcL4jVsjkLQz1OWnax8xVQJnRPe0V2jqNWORA+A==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", + "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", "requires": { "browserslist": "^4.19.1", "semver": "7.0.0" @@ -15861,9 +15865,9 @@ } }, "core-js-pure": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.0.tgz", - "integrity": "sha512-VaJUunCZLnxuDbo1rNOzwbet9E1K9joiXS5+DQMPtgxd24wfsZbJZMMfQLGYMlCUvSxLfsRUUhoOR2x28mFfeg==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", + "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", "dev": true }, "core-util-is": { @@ -16023,9 +16027,9 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, "csstype": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==" }, "d3-color": { "version": "2.0.0", @@ -16289,9 +16293,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.4.65", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.65.tgz", - "integrity": "sha512-0/d8Skk8sW3FxXP0Dd6MnBlrwx7Qo9cqQec3BlIAlvKnrmS3pHsIbaroEi+nd0kZkGpQ6apMEre7xndzjlEnLw==" + "version": "1.4.82", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.82.tgz", + "integrity": "sha512-Ks+ANzLoIrFDUOJdjxYMH6CMKB8UQo5modAwvSZTxgF+vEs/U7G5IbWFUp6dS4klPkTDVdxbORuk8xAXXhMsWw==" }, "elliptic": { "version": "6.5.4", @@ -16346,9 +16350,9 @@ } }, "engine.io": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.2.tgz", - "integrity": "sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", + "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", "requires": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -16358,7 +16362,7 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3" }, "dependencies": { @@ -16441,9 +16445,9 @@ } }, "error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.7.tgz", + "integrity": "sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==", "dev": true, "requires": { "stackframe": "^1.1.1" @@ -17447,9 +17451,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" }, "forever-agent": { "version": "0.6.1", @@ -17694,6 +17698,11 @@ "lodash": "^4.17.15" } }, + "guacamole-common-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/guacamole-common-js/-/guacamole-common-js-1.3.1.tgz", + "integrity": "sha512-PrB+Z4DKERXyrTt20JKkLteR7ZgesD/HihwnnHdo0cwgsgv1i0898is6IUTo0iBHCEcH9vLJDULax18EUJSgvg==" + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -17732,9 +17741,9 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -18250,9 +18259,9 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jest-worker": { - "version": "27.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.0.tgz", - "integrity": "sha512-8OEHiPNOPTfaWnJ2SUHM8fmgeGq37uuGsQBvGKQJl1f+6WIy6g7G3fE2ruI5294bUKUI9FaCWt5hDvO8HSwsSg==", + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -18295,9 +18304,9 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdoc-type-pratt-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.2.tgz", - "integrity": "sha512-zRokSWcPLSWkoNzsWn9pq7YYSwDhKyEe+cJYT2qaPqLOOJb5sFSi46BPj81vP+e8chvCNdQL9RG86Bi9EI6MDw==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.5.tgz", + "integrity": "sha512-2a6eRxSxp1BW040hFvaJxhsCMI9lT8QB8t14t+NY5tC5rckIR0U9cr2tjOeaFirmEOy6MHvmJnY7zTBHq431Lw==", "dev": true }, "jsesc": { @@ -18657,14 +18666,14 @@ "dev": true }, "logform": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.3.2.tgz", - "integrity": "sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz", + "integrity": "sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==", "requires": { - "colors": "1.4.0", + "@colors/colors": "1.5.0", "fecha": "^4.2.0", "ms": "^2.1.1", - "safe-stable-stringify": "^1.1.0", + "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, @@ -18799,16 +18808,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -18843,9 +18852,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -18931,9 +18940,9 @@ "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" }, "nanoid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", - "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" }, "napi-build-utils": { "version": "1.0.2", @@ -19063,9 +19072,9 @@ } }, "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, "node-zendesk": { "version": "2.1.0", @@ -19581,11 +19590,11 @@ "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" }, "postcss": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", - "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", + "version": "8.4.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz", + "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==", "requires": { - "nanoid": "^3.2.0", + "nanoid": "^3.3.1", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -19776,9 +19785,9 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "buffer": { "version": "5.7.1", @@ -20460,9 +20469,9 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", - "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==" }, "safer-buffer": { "version": "2.1.2", @@ -20730,9 +20739,9 @@ }, "dependencies": { "socket.io-parser": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.1.tgz", - "integrity": "sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", + "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", "requires": { "@socket.io/component-emitter": "~3.0.0", "debug": "~4.3.1" @@ -20838,9 +20847,9 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stackframe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", - "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", + "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==", "dev": true }, "statuses": { @@ -21117,6 +21126,34 @@ } } }, + "terser": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.0.tgz", + "integrity": "sha512-R3AUhNBGWiFc77HXag+1fXpAxTAFRQTJemlJKjAgD9r8xXTpjNKqIXwHM/o7Rh+O0kUJtS3WQVdBeMKFk5sw9A==", + "requires": { + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, "terser-webpack-plugin": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz", @@ -21130,39 +21167,10 @@ "terser": "^5.7.0" }, "dependencies": { - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "optional": true, - "peer": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "terser": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", - "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - } - } } } }, @@ -21261,9 +21269,9 @@ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "tsconfig-paths": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", - "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz", + "integrity": "sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g==", "dev": true, "requires": { "@types/json5": "^0.0.29", @@ -21554,9 +21562,9 @@ "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" }, "enhanced-resolve": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", - "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", + "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -21690,9 +21698,9 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==" }, "wide-align": { "version": "1.1.5", diff --git a/src/fireedge/package.json b/src/fireedge/package.json index 493f801f2d..d444069661 100644 --- a/src/fireedge/package.json +++ b/src/fireedge/package.json @@ -81,6 +81,7 @@ "fast-xml-parser": "3.19.0", "fs-extra": "9.0.1", "fuse.js": "6.4.1", + "guacamole-common-js": "1.3.1", "helmet": "4.1.1", "http": "0.0.1-security", "http-proxy-middleware": "1.0.5", diff --git a/src/fireedge/src/client/apps/sunstone/index.js b/src/fireedge/src/client/apps/sunstone/index.js index c66b1f08c5..d90e40467c 100644 --- a/src/fireedge/src/client/apps/sunstone/index.js +++ b/src/fireedge/src/client/apps/sunstone/index.js @@ -62,8 +62,8 @@ const Sunstone = ({ store = {}, location = '' }) => ( Sunstone.propTypes = { location: PropTypes.string, - context: PropTypes.shape({}), - store: PropTypes.shape({}), + context: PropTypes.object, + store: PropTypes.object, } Sunstone.displayName = 'SunstoneApp' diff --git a/src/fireedge/src/client/apps/sunstone/routes.js b/src/fireedge/src/client/apps/sunstone/routes.js index 738ff21278..740b986f4d 100644 --- a/src/fireedge/src/client/apps/sunstone/routes.js +++ b/src/fireedge/src/client/apps/sunstone/routes.js @@ -27,10 +27,14 @@ const Dashboard = loadable( const Settings = loadable(() => import('client/containers/Settings'), { ssr: false, }) +const Guacamole = loadable(() => import('client/containers/Guacamole'), { + ssr: false, +}) export const PATH = { DASHBOARD: '/dashboard', SETTINGS: '/settings', + GUACAMOLE: '/guacamole/:id/:type', } export const ENDPOINTS = [ @@ -50,6 +54,12 @@ export const ENDPOINTS = [ position: -1, Component: Settings, }, + { + label: 'Guacamole', + disabledSidebar: true, + path: PATH.GUACAMOLE, + Component: Guacamole, + }, ] /** diff --git a/src/fireedge/src/client/components/Buttons/ConsoleAction.js b/src/fireedge/src/client/components/Buttons/ConsoleAction.js new file mode 100644 index 0000000000..7571a619ec --- /dev/null +++ b/src/fireedge/src/client/components/Buttons/ConsoleAction.js @@ -0,0 +1,108 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { memo, useMemo, useCallback, ReactElement } from 'react' +import PropTypes from 'prop-types' +import { useHistory, generatePath } from 'react-router-dom' + +import { + AppleImac2021 as VncIcon, + TerminalOutline as SshIcon, + Windows as RdpIcon, +} from 'iconoir-react' +import { SubmitButton } from 'client/components/FormControl' + +import { useViews } from 'client/features/Auth' +import { useLazyGetGuacamoleSessionQuery } from 'client/features/OneApi/vm' +import { + nicsIncludesTheConnectionType, + isAvailableAction, +} from 'client/models/VirtualMachine' +import { Translate } from 'client/components/HOC' +import { T, VM, RESOURCE_NAMES, VM_ACTIONS } from 'client/constants' +import { PATH } from 'client/apps/sunstone/routes' + +const GUACAMOLE_BUTTON = { + vnc: { tooltip: T.Vnc, icon: }, + ssh: { tooltip: T.Ssh, icon: }, + rdp: { tooltip: T.Rdp, icon: }, +} + +const GuacamoleButton = memo( + /** + * @param {object} options - Options + * @param {VM} options.vm - Virtual machine + * @param {'vnc'|'ssh'|'rdp'} options.connectionType - Connection type + * @param {Function} [options.onClick] - Handle click for button + * @returns {ReactElement} - Guacamole button + */ + ({ vm, connectionType, onClick }) => { + const history = useHistory() + const { view, [RESOURCE_NAMES.VM]: vmView } = useViews() + const [getSession, { isLoading }] = useLazyGetGuacamoleSessionQuery() + + const isDisabled = useMemo(() => { + const noAction = vmView?.actions?.[connectionType] !== true + const noAvailable = isAvailableAction(connectionType)(vm) + + return noAction || noAvailable + }, [view, vm]) + + const { tooltip, icon } = GUACAMOLE_BUTTON[connectionType] + + const goToConsole = + onClick ?? + useCallback( + async (evt) => { + try { + evt.stopPropagation() + + const params = { id: vm?.ID, type: connectionType } + await getSession(params) + history.push(generatePath(PATH.GUACAMOLE, params)) + } catch {} + }, + [vm?.ID, connectionType, history] + ) + + if ( + isDisabled || + (connectionType !== VM_ACTIONS.VNC && + !nicsIncludesTheConnectionType(vm, connectionType)) + ) { + return null + } + + return ( + } + isSubmitting={isLoading} + onClick={goToConsole} + /> + ) + } +) + +GuacamoleButton.propTypes = { + vm: PropTypes.object, + connectionType: PropTypes.string, + onClick: PropTypes.func, +} + +GuacamoleButton.displayName = 'GuacamoleButton' + +export { GuacamoleButton } diff --git a/src/fireedge/src/client/components/Buttons/index.js b/src/fireedge/src/client/components/Buttons/index.js index 8c7ccdd35d..c4b46ca3ab 100644 --- a/src/fireedge/src/client/components/Buttons/index.js +++ b/src/fireedge/src/client/components/Buttons/index.js @@ -15,3 +15,4 @@ * ------------------------------------------------------------------------- */ export * from 'client/components/Buttons/ScheduleAction' +export * from 'client/components/Buttons/ConsoleAction' diff --git a/src/fireedge/src/client/components/Cards/VirtualMachineCard.js b/src/fireedge/src/client/components/Cards/VirtualMachineCard.js index f1fc495cbd..dc02b1bcc8 100644 --- a/src/fireedge/src/client/components/Cards/VirtualMachineCard.js +++ b/src/fireedge/src/client/components/Cards/VirtualMachineCard.js @@ -36,9 +36,10 @@ const VirtualMachineCard = memo( * @param {object} props - Props * @param {VM} props.vm - Virtual machine resource * @param {object} props.rootProps - Props to root component + * @param {ReactElement} [props.actions] - Actions * @returns {ReactElement} - Card */ - ({ vm, rootProps }) => { + ({ vm, rootProps, actions }) => { const classes = rowStyles() const { ID, NAME, UNAME, GNAME, IPS, STIME, ETIME, LOCK } = vm @@ -89,6 +90,7 @@ const VirtualMachineCard = memo( )} + {actions &&
{actions}
} ) } @@ -99,6 +101,7 @@ VirtualMachineCard.propTypes = { rootProps: PropTypes.shape({ className: PropTypes.string, }), + actions: PropTypes.any, } VirtualMachineCard.displayName = 'VirtualMachineCard' diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/buttons.js b/src/fireedge/src/client/components/Consoles/Guacamole/buttons.js new file mode 100644 index 0000000000..db99f2013c --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/buttons.js @@ -0,0 +1,213 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { memo, useCallback, useState, ReactElement } from 'react' +import PropTypes from 'prop-types' +import { Refresh, Maximize, Camera } from 'iconoir-react' +import { + Tooltip, + Typography, + Button, + IconButton, + CircularProgress, +} from '@mui/material' + +import { Translate } from 'client/components/HOC' +import { downloadFile } from 'client/utils' +import { T, GuacamoleSession } from 'client/constants' + +const GuacamoleCtrlAltDelButton = memo( + /** + * @param {GuacamoleSession} session - Guacamole session + * @returns {ReactElement} Guacamole mouse plugin + */ + (session) => { + const { id, client } = session + + const handleClick = useCallback(() => { + if (!client) return + + const ctrlKey = 65507 + const altKey = 65513 + const delKey = 65535 + + client?.sendKeyEvent(1, ctrlKey) + client?.sendKeyEvent(1, altKey) + client?.sendKeyEvent(1, delKey) + client?.sendKeyEvent(0, delKey) + client?.sendKeyEvent(0, altKey) + client?.sendKeyEvent(0, ctrlKey) + }, [client]) + + return ( + + ) + } +) + +/** + * @param {GuacamoleSession} session - Guacamole session + * @returns {ReactElement} Guacamole mouse plugin + */ +const GuacamoleReconnectButton = (session) => { + const { id, isLoading, handleReconnect } = session + const [reconnecting, setReconnecting] = useState(false) + + const handleReconnectSession = async () => { + if (isLoading) return + + setReconnecting(true) + await handleReconnect() + setReconnecting(false) + } + + return ( + + + + } + > + + {reconnecting || isLoading ? ( + + ) : ( + + )} + + + ) +} + +const GuacamoleFullScreenButton = memo( + /** + * @param {GuacamoleSession} session - Guacamole session + * @returns {ReactElement} Guacamole mouse plugin + */ + (session) => { + const { id, viewport } = session + + const handleClick = useCallback(() => { + // If the document is not in full screen mode make the video full screen + if (!document.fullscreenElement && document.fullscreenEnabled) { + viewport?.requestFullscreen?.() + } else if (document.exitFullscreen) { + document.exitFullscreen() + } + }, [viewport]) + + return ( + + + + } + > + + + + + ) + } +) + +const GuacamoleScreenshotButton = memo( + /** + * @param {GuacamoleSession} session - Guacamole session + * @returns {ReactElement} Guacamole mouse plugin + */ + (session) => { + const { id, client } = session + + const handleClick = useCallback(() => { + if (!client) return + + const canvas = client.getDisplay().getDefaultLayer().getCanvas() + + canvas.toBlob((blob) => { + downloadFile(new File([blob], 'screenshot.png')) + }, 'image/png') + }, [client]) + + return ( + + + + } + > + + + + + ) + } +) + +const ButtonPropTypes = { + client: PropTypes.object, + viewport: PropTypes.object, +} + +GuacamoleCtrlAltDelButton.displayName = 'GuacamoleCtrlAltDelButton' +GuacamoleCtrlAltDelButton.propTypes = ButtonPropTypes +GuacamoleReconnectButton.displayName = 'GuacamoleReconnectButton' +GuacamoleReconnectButton.propTypes = ButtonPropTypes +GuacamoleFullScreenButton.displayName = 'GuacamoleFullScreenButton' +GuacamoleFullScreenButton.propTypes = ButtonPropTypes +GuacamoleScreenshotButton.displayName = 'GuacamoleScreenshotButton' +GuacamoleScreenshotButton.propTypes = ButtonPropTypes + +export { + GuacamoleCtrlAltDelButton, + GuacamoleReconnectButton, + GuacamoleFullScreenButton, + GuacamoleScreenshotButton, +} diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/client.js b/src/fireedge/src/client/components/Consoles/Guacamole/client.js new file mode 100644 index 0000000000..ff96c76615 --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/client.js @@ -0,0 +1,217 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { useRef, useEffect, RefObject } from 'react' +import { WebSocketTunnel, Tunnel, Client } from 'guacamole-common-js' + +import { useGeneralApi } from 'client/features/General' +import { useGuacamole, useGuacamoleApi } from 'client/features/Guacamole' +import { getConnectString, clientStateToString } from 'client/models/Guacamole' +import { fakeDelay, isDevelopment } from 'client/utils' +import { + GuacamoleSession, // eslint-disable-line no-unused-vars + SOCKETS, + GUACAMOLE_STATES_STR, + THUMBNAIL_UPDATE_FREQUENCY, +} from 'client/constants' + +const { + CONNECTING, + CONNECTED, + DISCONNECTING, + DISCONNECTED, + CLIENT_ERROR, + TUNNEL_ERROR, +} = GUACAMOLE_STATES_STR + +// eslint-disable-next-line jsdoc/valid-types +/** + * @typedef {GuacamoleSession & { + * handleConnect: Function, + * handleDisconnect: Function, + * handleReconnect: function():Promise, + * }} GuacamoleClientType + */ + +/** + * @param {object} options - Client options + * @param {string} options.id - Session includes type and VM id. Eg: '6-vnc' + * @param {RefObject} options.display - Session display. Only exists if display plugins is enabled + * @returns {GuacamoleClientType} Guacamole client props + */ +const GuacamoleClient = ({ id, display }) => { + const guac = useRef(createGuacamoleClient()).current + + // Automatically update the client thumbnail + guac.client.onsync = () => handleUpdateThumbnail() + + const { enqueueError, enqueueInfo, enqueueSuccess } = useGeneralApi() + const { token, ...session } = useGuacamole(id) + const { + setConnectionState, + setTunnelUnstable, + setMultiTouchSupport, + updateThumbnail, + } = useGuacamoleApi(id) + + const handleConnect = (width, height, force = false) => { + if (!session?.isUninitialized && !session.isDisconnected && !force) return + + isDevelopment() && console.log(`connect ${id} 🔵`) + + const options = { token, display, width, height } + const connectString = getConnectString(options) + + guac.client.connect(connectString) + } + + const handleDisconnect = () => { + try { + isDevelopment() && console.log(`disconnect ${id} 🔴`) + guac.client?.disconnect() + } catch {} + } + + const handleReconnect = async (width, height) => { + session?.isConnected && handleDisconnect() + + // sleep to avoid quick reconnection + await fakeDelay(1500) + handleConnect(width, height, true) + } + + /** + * Store the thumbnail of the given managed client within the connection + * history under its associated ID. If the client is not connected, this + * function has no effect. + */ + const handleUpdateThumbnail = () => { + const nowTimestamp = new Date().getTime() + const lastTimestamp = session?.thumbnail?.timestamp + + if ( + lastTimestamp && + nowTimestamp - lastTimestamp < THUMBNAIL_UPDATE_FREQUENCY + ) + return + + const clientDisplay = guac.client?.getDisplay() + + if (clientDisplay?.getWidth() <= 0 || clientDisplay?.getHeight() <= 0) + return + + // Get screenshot + const canvas = clientDisplay.flatten() + + // Calculate scale of thumbnail (max 320x240, max zoom 100%) + const scale = Math.min(320 / canvas.width, 240 / canvas.height, 1) + + // Create thumbnail canvas + const thumbnail = document.createElement('canvas') + thumbnail.width = canvas.width * scale + thumbnail.height = canvas.height * scale + + // Scale screenshot to thumbnail + const context = thumbnail.getContext('2d') + context.drawImage( + canvas, + 0, + 0, + canvas.width, + canvas.height, + 0, + 0, + thumbnail.width, + thumbnail.height + ) + + thumbnail.toBlob((blob) => { + const url = URL.createObjectURL(blob) + const newThumbnail = { timestamp: nowTimestamp, canvas: url } + updateThumbnail({ thumbnail: newThumbnail }) + }, 'image/webp') + } + + useEffect(() => { + guac.tunnel.onerror = (status) => { + setConnectionState({ state: TUNNEL_ERROR, statusCode: status.code }) + } + + guac.tunnel.onstatechange = (state) => { + ;({ + [Tunnel.State.CONNECTING]: () => { + setConnectionState({ state: CONNECTING }) + }, + [Tunnel.State.OPEN]: () => { + setTunnelUnstable({ unstable: false }) + }, + [Tunnel.State.UNSTABLE]: () => { + setTunnelUnstable({ unstable: true }) + }, + [Tunnel.State.CLOSED]: () => { + setConnectionState({ state: DISCONNECTED }) + }, + }[state]?.()) + } + + guac.client.onstatechange = (state) => { + const stateString = clientStateToString(state) + const isDisconnect = [DISCONNECTING, DISCONNECTED].includes(stateString) + const isDisconnected = DISCONNECTED === stateString + const isConnected = CONNECTED === stateString + + isConnected && enqueueSuccess('Connection established') + isDisconnected && enqueueInfo('Disconnected') + + !isDisconnect && setConnectionState({ state: stateString }) + } + + guac.client.onerror = (status) => { + enqueueError(status.message) + setConnectionState({ state: CLIENT_ERROR, statusCode: status.code }) + } + + guac.client.onmultitouch = (layer, touches) => { + setMultiTouchSupport({ touches }) + } + + return () => { + handleDisconnect() + } + }, [id]) + + useEffect(() => { + session?.isError && handleDisconnect() + }, [session?.isError]) + + useEffect(() => { + !session.isConnected && handleConnect() + }, [token]) + + return { token, ...session, ...guac, handleReconnect } +} + +const createGuacamoleClient = () => { + const { protocol, host } = window.location + const websocketProtocol = protocol === 'https:' ? 'wss:' : 'ws:' + const guacamoleWs = `${websocketProtocol}//${host}/fireedge/${SOCKETS.GUACAMOLE}` + + const tunnel = new WebSocketTunnel(guacamoleWs) + const client = new Client(tunnel) + + return { client, tunnel } +} + +export default GuacamoleClient diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/index.js b/src/fireedge/src/client/components/Consoles/Guacamole/index.js new file mode 100644 index 0000000000..e77301bf07 --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/index.js @@ -0,0 +1,63 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +/* eslint-disable no-unused-vars */ +/* eslint-disable jsdoc/valid-types */ +import { useRef } from 'react' + +import { + useGetLatest, + reducePlugin, +} from 'client/components/Consoles/Guacamole/utils' +import GuacamoleClient, { + GuacamoleClientType, +} from 'client/components/Consoles/Guacamole/client' +import { + GuacamoleDisplayPlugin, + GuacamoleKeyboardPlugin, + GuacamoleMousePlugin, +} from 'client/components/Consoles/Guacamole/plugins' + +/** + * Creates guacamole session. + * + * @param {object} options - Options + * @param {string} options.id - Session includes type and VM id. Eg: '6-vnc' + * @param {...any} [plugins] - Plugins + * @returns {GuacamoleClientType & + * GuacamoleDisplayPlugin & + * GuacamoleKeyboardPlugin & + * GuacamoleMousePlugin} session + */ +const useGuacamoleSession = (options, ...plugins) => { + // Create the guacamole instance + const instanceRef = useRef({}) + const getInstance = useGetLatest(instanceRef.current) + + // Assign the options to the instance + Object.assign(getInstance(), { ...options }) + + // Assign the session and plugins to the instance + Object.assign( + getInstance(), + [GuacamoleClient, ...plugins].reduce(reducePlugin, getInstance()) + ) + + return getInstance() +} + +export * from 'client/components/Consoles/Guacamole/plugins' +export * from 'client/components/Consoles/Guacamole/buttons' +export { useGuacamoleSession } diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/plugins/clipboard.js b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/clipboard.js new file mode 100644 index 0000000000..26cea72be1 --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/clipboard.js @@ -0,0 +1,158 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { useEffect, useState } from 'react' +import { + StringReader, + StringWriter, + BlobReader, + BlobWriter, +} from 'guacamole-common-js' + +import { GuacamoleSession } from 'client/constants' +import { isDevelopment } from 'client/utils' + +const createClipboardData = ({ id, type, data } = {}) => ({ + source: id, + /** + * The mimetype of the data currently stored within the clipboard. + * + * @type {string} + */ + type: type || 'text/plain', + /** + * The data currently stored within the clipboard. + * + * @type {string|Blob|File} + */ + data: data ?? '', +}) + +/** + * @param {GuacamoleSession} session - Current session + * @returns {null} null + */ +const GuacamoleClipboard = (session) => { + const { id, client, isConnected } = session ?? {} + + const [pendingRead, setPendingRead] = useState(() => false) + const [storedClipboard, storeClipboard] = useState(() => + createClipboardData({ id }) + ) + + const getLocalClipboard = async () => { + try { + if (pendingRead) return + + const text = await navigator.clipboard.readText() + storeClipboard((prev) => ({ ...prev, data: text, type: 'text/plain' })) + + return text + } finally { + setPendingRead(false) + } + } + + const setLocalClipboard = async ({ data, type }) => { + if (type !== 'text/plain') return + + await navigator.clipboard.writeText(data) + storeClipboard((prev) => ({ ...prev, data, type })) + } + + const setClientClipboard = ({ data, type = 'text/plain' } = {}) => { + // Create stream with proper mimetype + const stream = client.createClipboardStream(type) + + // Send data as a string if it is stored as a string + if (typeof data === 'string') { + const writer = new StringWriter(stream) + writer.sendText(data) + writer.sendEnd() + } + // Otherwise, assume the data is a File/Blob + else { + // Write File/Blob asynchronously + const writer = new BlobWriter(stream) + writer.oncomplete = () => { + writer.sendEnd() + } + + // Begin sending data + writer.sendBlob(data) + } + } + + const resyncClipboard = async () => { + try { + const localClipboard = await getLocalClipboard() + setClientClipboard({ data: localClipboard }) + } catch (e) { + isDevelopment() && console.log(e) + } + } + + const focusGained = (evt) => { + // Only recheck clipboard if it's the window itself that gained focus + evt.target === window && resyncClipboard() + } + + useEffect(() => { + if (!isConnected) return + ;(async () => await resyncClipboard())() + + window.addEventListener('load', resyncClipboard, true) + window.addEventListener('copy', resyncClipboard) + window.addEventListener('cut', resyncClipboard) + window.addEventListener('focus', focusGained, true) + + client.onclipboard = (stream, mimetype) => { + // If the received data is text, read it as a simple string + if (/^text\//.exec(mimetype)) { + const reader = new StringReader(stream) + + // Assemble received data into a single string + let data = '' + reader.ontext = (text) => { + data += text + } + + // Set clipboard contents once stream is finished + reader.onend = () => { + setLocalClipboard({ data, type: mimetype }) + } + } + // Otherwise read the clipboard data as a Blob + else { + const reader = new BlobReader(stream, mimetype) + + reader.onend = () => { + setLocalClipboard({ data: reader.getBlob(), type: mimetype }) + } + } + } + + return () => { + window.removeEventListener('load', resyncClipboard, true) + window.removeEventListener('copy', resyncClipboard) + window.removeEventListener('cut', resyncClipboard) + window.removeEventListener('focus', focusGained, true) + } + }, [isConnected]) + + return { storedClipboard } +} + +export { GuacamoleClipboard } diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/plugins/display.js b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/display.js new file mode 100644 index 0000000000..6d8f82cfbe --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/display.js @@ -0,0 +1,147 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { + useEffect, + useMemo, + useRef, + MutableRefObject, // eslint-disable-line no-unused-vars + ReactElement, // eslint-disable-line no-unused-vars +} from 'react' +import { styled } from '@mui/material' + +import { GuacamoleSession } from 'client/constants' + +/** + * @typedef GuacamoleDisplayPlugin + * @property {MutableRefObject} [display] - Display object + * @property {MutableRefObject} [viewport] - Viewport object is the wrapper of display + * @property {ReactElement} [displayElement] - Display element + */ + +const Viewport = styled('div')({ + backgroundColor: '#222431', + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + placeContent: 'center', +}) + +const Display = styled('div')({ + zIndex: 1, + overflow: 'hidden', + '& > *': { cursor: 'none' }, +}) + +/** + * @param {GuacamoleSession} session - Guacamole session + * @returns {GuacamoleDisplayPlugin} Guacamole display plugin + */ +const GuacamoleDisplay = (session) => { + const { id, container, header, client, isConnected } = session ?? {} + const isSSH = useMemo(() => id.includes('ssh'), [id]) + + const viewportRef = useRef(null) + const displayRef = useRef(null) + + const containerResized = () => { + if (!client || !container) return + + const clientDisplay = client.getDisplay() + const pixelDensity = window.devicePixelRatio || 1 + const headerHeight = header?.offsetHeight ?? 0 + + const width = document.fullscreenElement + ? window.innerWidth * pixelDensity + : container.offsetWidth * pixelDensity + + const height = document.fullscreenElement + ? window.innerHeight * pixelDensity + : (container.offsetHeight - headerHeight) * pixelDensity + + if ( + clientDisplay.getWidth() !== width || + clientDisplay.getHeight() !== height + ) { + client.sendSize(width, height) + } + + // when type connection is SSH, display doesn't need scale + id.includes('vnc') && updateDisplayScale() + } + + const updateDisplayScale = () => { + if (!client) return + + const clientDisplay = client.getDisplay() + // Get screen resolution. + const origHeight = Math.max(clientDisplay.getHeight(), 1) + const origWidth = Math.max(clientDisplay.getWidth(), 1) + + const headerHeight = header?.offsetHeight ?? 0 + + const containerWidth = document.fullscreenElement + ? window.innerWidth + : container.offsetWidth + + const containerHeight = document.fullscreenElement + ? window.innerHeight + : container.offsetHeight - headerHeight + + const xScale = containerWidth / origWidth + const yScale = containerHeight / origHeight + + // This is done to handle both X and Y axis + let scale = Math.min(yScale, xScale) + + // Limit to 1 + scale = Math.min(scale, 1) + + scale !== 0 && clientDisplay.scale(scale) + } + + useEffect(() => { + if (!isConnected) return + + const display = displayRef.current + const clientDisplay = client.getDisplay() + + display?.appendChild(clientDisplay.getElement()) + + const pollResize = setInterval(containerResized, 10) + + return () => { + display?.childNodes.forEach((node) => display?.removeChild(node)) + clearInterval(pollResize) + } + }, [isConnected]) + + return { + display: displayRef.current, + viewport: viewportRef.current, + displayElement: ( + + + + ), + } +} + +export { GuacamoleDisplay } diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/plugins/index.js b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/index.js new file mode 100644 index 0000000000..bb723bf043 --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/index.js @@ -0,0 +1,20 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ + +export * from 'client/components/Consoles/Guacamole/plugins/clipboard' +export * from 'client/components/Consoles/Guacamole/plugins/display' +export * from 'client/components/Consoles/Guacamole/plugins/keyboard' +export * from 'client/components/Consoles/Guacamole/plugins/mouse' diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/plugins/keyboard.js b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/keyboard.js new file mode 100644 index 0000000000..0869ca9201 --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/keyboard.js @@ -0,0 +1,55 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +// eslint-disable-next-line no-unused-vars +import { useCallback, useEffect, useRef, useState } from 'react' +import { Keyboard } from 'guacamole-common-js' + +import { GuacamoleSession } from 'client/constants' + +/** + * @typedef GuacamoleKeyboardPlugin + * @property {Keyboard} [keyboard] - Guacamole keyboard + */ + +/** + * @param {GuacamoleSession} session - Guacamole session + * @returns {GuacamoleKeyboardPlugin} Guacamole keyboard plugin + */ +const GuacamoleKeyboard = (session) => { + const { client, isConnected } = session ?? {} + + const keyboardRef = useRef(null) + + useEffect(() => { + if (!isConnected) return + + keyboardRef.current = new Keyboard(document) + + keyboardRef.current.onkeydown = (keySym) => client?.sendKeyEvent(1, keySym) + keyboardRef.current.onkeyup = (keySym) => client?.sendKeyEvent(0, keySym) + + // Release all keys when window loses focus + window.addEventListener('blur', keyboardRef.current?.reset) + + return () => { + window.removeEventListener('blur', keyboardRef.current?.reset) + } + }, [isConnected]) + + return { keyboard: keyboardRef.current } +} + +export { GuacamoleKeyboard } diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/plugins/mouse.js b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/mouse.js new file mode 100644 index 0000000000..bec42d548a --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/plugins/mouse.js @@ -0,0 +1,78 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +// eslint-disable-next-line no-unused-vars +import { useEffect, useRef } from 'react' +import { Mouse } from 'guacamole-common-js' + +import { GuacamoleSession } from 'client/constants' + +/** + * @typedef GuacamoleMousePlugin + * @property {Mouse} [mouse] - Guacamole mouse + */ + +/** + * @param {GuacamoleSession} session - Guacamole session + * @returns {GuacamoleMousePlugin} Guacamole mouse plugin + */ +const GuacamoleMouse = (session) => { + const { client, display, isConnected } = session ?? {} + + const mouseRef = useRef(null) + + const handleMouseState = (mouseState, scaleMouse = false) => { + // Do not attempt to handle mouse state changes if the client + // or display are not yet available + if (!client) return + + if (scaleMouse) { + const clientScale = client.getDisplay().getScale() + mouseState.y = mouseState.y / clientScale + mouseState.x = mouseState.x / clientScale + } + + // Send mouse state, show cursor if necessary + // client.display?.showCursor(!localCursor) + client.sendMouseState(mouseState) + } + + useEffect(() => { + if (!isConnected) return + + const mouse = new Mouse(client.getDisplay().getElement()) + mouseRef.current = mouse + + mouse.onmousedown = mouse.onmouseup = (mouseState) => { + // Ensure focus is regained via mousedown before forwarding event + display?.focus() + handleMouseState(mouseState) + } + + // Forward mousemove events untouched + mouse.onmousemove = (mouseState) => { + handleMouseState(mouseState, true) + } + + // Hide software cursor when mouse leaves display + mouse.onmouseout = () => { + // client?.display?.showCursor(false) + } + }, [isConnected]) + + return { mouse: mouseRef.current } +} + +export { GuacamoleMouse } diff --git a/src/fireedge/src/client/components/Consoles/Guacamole/utils.js b/src/fireedge/src/client/components/Consoles/Guacamole/utils.js new file mode 100644 index 0000000000..7682365cc4 --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/Guacamole/utils.js @@ -0,0 +1,41 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { useCallback, useRef } from 'react' + +/** + * Helps avoid a lot of potential memory leaks. + * + * @param {object} obj - Instance object + * @returns {function():object} - Returns the last object + */ +export const useGetLatest = (obj) => { + const ref = useRef() + ref.current = obj + + return useCallback(() => ref.current, []) +} + +/** + * Assign the plugin state to the previous state. + * + * @param {object} prevState - Previous state + * @param {function():object} plugin - Plugin + * @returns {object} Returns the new state + */ +export const reducePlugin = (prevState, plugin) => ({ + ...prevState, + ...plugin(prevState), +}) diff --git a/src/fireedge/src/client/components/Consoles/index.js b/src/fireedge/src/client/components/Consoles/index.js new file mode 100644 index 0000000000..50fe5a58f6 --- /dev/null +++ b/src/fireedge/src/client/components/Consoles/index.js @@ -0,0 +1,16 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +export * from 'client/components/Consoles/Guacamole' diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js index 8e6b606124..216b551406 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js @@ -32,9 +32,9 @@ const TYPE = { dependOf: '$general.HYPERVISOR', values: (hypervisor = kvm) => { const types = { - [vcenter]: [T.VMRC], - [lxc]: [T.VNC], - }[hypervisor] ?? [T.VNC, T.SDL, T.SPICE] + [vcenter]: [T.Vmrc], + [lxc]: [T.Vnc], + }[hypervisor] ?? [T.Vnc, T.Sdl, T.Spice] return arrayToOptions(types) }, diff --git a/src/fireedge/src/client/components/HOC/AuthLayout.js b/src/fireedge/src/client/components/HOC/AuthLayout.js index ebbb6ed0eb..e9868ad79f 100644 --- a/src/fireedge/src/client/components/HOC/AuthLayout.js +++ b/src/fireedge/src/client/components/HOC/AuthLayout.js @@ -19,6 +19,7 @@ import PropTypes from 'prop-types' import { useAuth, useAuthApi } from 'client/features/Auth' import { authApi } from 'client/features/AuthApi' +import { oneApi } from 'client/features/OneApi' import groupApi from 'client/features/OneApi/group' import FullscreenProgress from 'client/components/LoadingScreen' import { findStorageData } from 'client/utils' @@ -46,6 +47,8 @@ const AuthLayout = ({ subscriptions = [], children }) => { return () => { authSubscription.unsubscribe() + dispatch(authApi.util.resetApiState()) + dispatch(oneApi.util.resetApiState()) } }, [dispatch, jwt]) diff --git a/src/fireedge/src/client/components/HOC/InternalLayout/index.js b/src/fireedge/src/client/components/HOC/InternalLayout/index.js index 9156cafa37..32f0a55a98 100644 --- a/src/fireedge/src/client/components/HOC/InternalLayout/index.js +++ b/src/fireedge/src/client/components/HOC/InternalLayout/index.js @@ -14,10 +14,9 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import { useRef, useEffect } from 'react' +import { useRef, useEffect, useMemo } from 'react' import PropTypes from 'prop-types' import { useParams } from 'react-router-dom' -import clsx from 'clsx' import { Box, Container } from '@mui/material' import { CSSTransition } from 'react-transition-group' @@ -25,8 +24,9 @@ import { useGeneral, useGeneralApi } from 'client/features/General' import Header from 'client/components/Header' import Footer from 'client/components/Footer' import internalStyles from 'client/components/HOC/InternalLayout/styles' +import { sidebar } from 'client/theme/defaults' -const InternalLayout = ({ title, children }) => { +const InternalLayout = ({ title, customHeader, disabledSidebar, children }) => { const classes = internalStyles() const container = useRef() const { isFixMenu } = useGeneral() @@ -40,9 +40,27 @@ const InternalLayout = ({ title, children }) => { return ( + disabledSidebar + ? {} + : { + marginLeft: { + lg: isFixMenu + ? `${sidebar.fixed}px` + : `${sidebar.minified}px`, + }, + }, + [isFixMenu, disabledSidebar] + )} > -
+ {customHeader ?? ( +
+ )} { InternalLayout.propTypes = { title: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + customHeader: PropTypes.node, + disabledSidebar: PropTypes.bool, children: PropTypes.any, } diff --git a/src/fireedge/src/client/components/HOC/InternalLayout/styles.js b/src/fireedge/src/client/components/HOC/InternalLayout/styles.js index 680089a8f4..0d2e1f87fd 100644 --- a/src/fireedge/src/client/components/HOC/InternalLayout/styles.js +++ b/src/fireedge/src/client/components/HOC/InternalLayout/styles.js @@ -14,7 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import makeStyles from '@mui/styles/makeStyles' -import { sidebar, toolbar, footer } from 'client/theme/defaults' +import { toolbar, footer } from 'client/theme/defaults' export default makeStyles((theme) => ({ root: { @@ -27,14 +27,6 @@ export default makeStyles((theme) => ({ easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen, }), - [theme.breakpoints.up('lg')]: { - marginLeft: sidebar.minified, - }, - }, - isDrawerFixed: { - [theme.breakpoints.up('lg')]: { - marginLeft: sidebar.fixed, - }, }, main: { height: '100vh', @@ -49,7 +41,6 @@ export default makeStyles((theme) => ({ }, }, scrollable: { - backgroundColor: theme.palette.background.default, paddingTop: theme.spacing(2), paddingBottom: theme.spacing(2), height: '100%', diff --git a/src/fireedge/src/client/components/Header/Popover.js b/src/fireedge/src/client/components/Header/Popover.js index 7cda82f23e..ab41835105 100644 --- a/src/fireedge/src/client/components/Header/Popover.js +++ b/src/fireedge/src/client/components/Header/Popover.js @@ -38,6 +38,7 @@ const HeaderPopover = memo( icon, buttonLabel, buttonProps, + onMouseHover, headerTitle, popperProps, children, @@ -78,7 +79,9 @@ const HeaderPopover = memo( aria-haspopup aria-describedby={hasId} aria-expanded={open ? 'true' : 'false'} - onClick={handleClick} + {...(onMouseHover + ? { onMouseEnter: handleClick, onMouseLeave: handleClose } + : { onClick: handleClick })} size="small" endIcon={} startIcon={icon} @@ -147,6 +150,7 @@ HeaderPopover.propTypes = { buttonProps: PropTypes.object, tooltip: PropTypes.any, headerTitle: PropTypes.any, + onMouseHover: PropTypes.bool, disablePadding: PropTypes.bool, popperProps: PropTypes.object, children: PropTypes.func, @@ -160,6 +164,7 @@ HeaderPopover.defaultProps = { buttonProps: {}, headerTitle: undefined, disablePadding: false, + onMouseHover: false, popperProps: {}, children: () => undefined, } diff --git a/src/fireedge/src/client/components/Header/index.js b/src/fireedge/src/client/components/Header/index.js index 82c97119ed..b797a07b17 100644 --- a/src/fireedge/src/client/components/Header/index.js +++ b/src/fireedge/src/client/components/Header/index.js @@ -36,7 +36,7 @@ import Group from 'client/components/Header/Group' import Zone from 'client/components/Header/Zone' import { sentenceCase } from 'client/utils' -const Header = () => { +const Header = ({ disabledSidebar = false }) => { const { isOneAdmin } = useAuth() const { fixMenu } = useGeneralApi() const { appTitle, title, isBeta, withGroupSwitcher } = useGeneral() @@ -45,15 +45,17 @@ const Header = () => { return ( - fixMenu(true)} - edge="start" - size="small" - variant="outlined" - sx={{ display: { lg: 'none' } }} - > - - + {!disabledSidebar && ( + fixMenu(true)} + edge="start" + size="small" + variant="outlined" + sx={{ display: { lg: 'none' } }} + > + + + )} { )} - - {title} - + {title && ( + + {title} + + )} { } Header.propTypes = { + disabledSidebar: PropTypes.bool, scrollContainer: PropTypes.object, } -Header.defaultProps = { - scrollContainer: null, -} - export default Header diff --git a/src/fireedge/src/client/components/Sidebar/index.js b/src/fireedge/src/client/components/Sidebar/index.js index a9380ab224..1c90ed5885 100644 --- a/src/fireedge/src/client/components/Sidebar/index.js +++ b/src/fireedge/src/client/components/Sidebar/index.js @@ -16,6 +16,7 @@ /* eslint-disable jsdoc/require-jsdoc */ import { useMemo } from 'react' import PropTypes from 'prop-types' +import { useLocation, matchPath } from 'react-router' import clsx from 'clsx' import { @@ -41,6 +42,7 @@ import SidebarLink from 'client/components/Sidebar/SidebarLink' import SidebarCollapseItem from 'client/components/Sidebar/SidebarCollapseItem' const Sidebar = ({ endpoints }) => { + const { pathname } = useLocation() const classes = sidebarStyles() const isUpLg = useMediaQuery((theme) => theme.breakpoints.up('lg'), { noSsr: true, @@ -66,6 +68,18 @@ const Sidebar = ({ endpoints }) => { [endpoints] ) + const isDisabledSidebar = useMemo(() => { + const endpoint = endpoints.find(({ path }) => + matchPath(pathname, { path, exact: true }) + ) + + return endpoint?.disabledSidebar + }, [pathname]) + + if (isDisabledSidebar) { + return null + } + return ( { ) const { view, getResourceView } = useViews() - const [totalData, setTotalData] = useState(() => []) - const [args, setArgs] = useState(() => INITIAL_ARGS) - const { data, isSuccess, refetch, isFetching } = useGetVmsQuery(args, { - refetchOnMountOrArgChange: true, + const { data, refetch, isFetching } = useGetVmsQuery(undefined, { + selectFromResult: (result) => ({ + ...result, + data: result?.data?.filter(({ STATE }) => STATE !== '6') ?? [], + }), }) const columns = useMemo( @@ -69,41 +61,13 @@ const VmsTable = (props) => { [view] ) - useEffect(() => { - if (!isFetching && isSuccess && data?.length >= +INTERVAL_ON_FIRST_RENDER) { - setArgs((prev) => ({ - ...prev, - start: prev.start + INTERVAL_ON_FIRST_RENDER, - })) - } - }, [isFetching]) - - useEffect(() => { - isSuccess && - data && - setTotalData((prev) => { - const notDuplicatedData = data.filter( - ({ ID }) => !prev.find((vm) => vm.ID === ID) - ) - - return prev.concat(notDuplicatedData).sort((a, b) => b.ID - a.ID) - }) - }, [isSuccess]) - return ( totalData?.filter(({ STATE }) => STATE !== '6'), - [totalData] - )} + data={useMemo(() => data, [data])} rootProps={rootProps} searchProps={searchProps} - refetch={() => { - totalData?.length >= +INTERVAL_ON_FIRST_RENDER - ? setArgs(INITIAL_ARGS) - : refetch() - }} + refetch={refetch} isLoading={isFetching} getRowId={(row) => String(row.ID)} RowComponent={VmRow} diff --git a/src/fireedge/src/client/components/Tables/Vms/row.js b/src/fireedge/src/client/components/Tables/Vms/row.js index 792a660b20..36716199a0 100644 --- a/src/fireedge/src/client/components/Tables/Vms/row.js +++ b/src/fireedge/src/client/components/Tables/Vms/row.js @@ -13,18 +13,42 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { memo } from 'react' +import { memo, useMemo } from 'react' import PropTypes from 'prop-types' + import vmApi from 'client/features/OneApi/vm' import { VirtualMachineCard } from 'client/components/Cards' +import { GuacamoleButton } from 'client/components/Buttons' +import { VM_ACTIONS } from 'client/constants' + +const { VNC, RDP, SSH } = VM_ACTIONS const Row = memo( ({ original, ...props }) => { - const detail = vmApi.endpoints.getVm.useQueryState(original.ID, { - selectFromResult: ({ data }) => data, + const state = vmApi.endpoints.getVms.useQueryState(undefined, { + selectFromResult: ({ data = [] }) => + data.find((vm) => +vm.ID === +original.ID), }) - return + const memoVm = useMemo(() => state ?? original, [state, original]) + + return ( + + {[VNC, RDP, SSH].map((connectionType) => ( + + ))} + + } + /> + ) }, (prev, next) => prev.className === next.className ) diff --git a/src/fireedge/src/client/components/Tabs/Cluster/index.js b/src/fireedge/src/client/components/Tabs/Cluster/index.js index 3a1973a057..ccd8555f0b 100644 --- a/src/fireedge/src/client/components/Tabs/Cluster/index.js +++ b/src/fireedge/src/client/components/Tabs/Cluster/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetClusterQuery } from 'client/features/OneApi/cluster' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const ClusterTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetClusterQuery({ id }) + const { isLoading, isError, error } = useGetClusterQuery({ id }) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.CLUSTER @@ -41,6 +41,14 @@ const ClusterTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/Datastore/index.js b/src/fireedge/src/client/components/Tabs/Datastore/index.js index 612801fdf1..32465ebcee 100644 --- a/src/fireedge/src/client/components/Tabs/Datastore/index.js +++ b/src/fireedge/src/client/components/Tabs/Datastore/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetDatastoreQuery } from 'client/features/OneApi/datastore' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const DatastoreTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetDatastoreQuery({ id }) + const { isLoading, isError, error } = useGetDatastoreQuery({ id }) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.DATASTORE @@ -41,6 +41,14 @@ const DatastoreTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/Group/index.js b/src/fireedge/src/client/components/Tabs/Group/index.js index 704513778b..ada865f8a6 100644 --- a/src/fireedge/src/client/components/Tabs/Group/index.js +++ b/src/fireedge/src/client/components/Tabs/Group/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetGroupQuery } from 'client/features/OneApi/group' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const GroupTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetGroupQuery(id) + const { isLoading, isError, error } = useGetGroupQuery(id) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.GROUP @@ -41,6 +41,14 @@ const GroupTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/Host/index.js b/src/fireedge/src/client/components/Tabs/Host/index.js index ec4f081682..aeb316ec83 100644 --- a/src/fireedge/src/client/components/Tabs/Host/index.js +++ b/src/fireedge/src/client/components/Tabs/Host/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useAuth } from 'client/features/Auth' import { useGetHostQuery } from 'client/features/OneApi/host' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const HostTabs = memo(({ id }) => { const { view, getResourceView } = useAuth() - const { isLoading } = useGetHostQuery(id) + const { isLoading, isError, error } = useGetHostQuery(id) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.HOST @@ -41,6 +41,14 @@ const HostTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/Image/index.js b/src/fireedge/src/client/components/Tabs/Image/index.js index 0f2e741ebb..5e9ab82cb5 100644 --- a/src/fireedge/src/client/components/Tabs/Image/index.js +++ b/src/fireedge/src/client/components/Tabs/Image/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetImageQuery } from 'client/features/OneApi/image' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const ImageTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetImageQuery({ id }) + const { isLoading, isError, error } = useGetImageQuery({ id }) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.IMAGE @@ -41,6 +41,14 @@ const ImageTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/Marketplace/index.js b/src/fireedge/src/client/components/Tabs/Marketplace/index.js index 49b60ea7ce..07d6d07a2d 100644 --- a/src/fireedge/src/client/components/Tabs/Marketplace/index.js +++ b/src/fireedge/src/client/components/Tabs/Marketplace/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetMarketplaceQuery } from 'client/features/OneApi/marketplace' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const MarketplaceTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetMarketplaceQuery({ id }) + const { isLoading, isError, error } = useGetMarketplaceQuery({ id }) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.MARKETPLACE @@ -41,6 +41,14 @@ const MarketplaceTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js b/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js index 0dc5ee59ac..50281361f4 100644 --- a/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js +++ b/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetMarketplaceAppQuery } from 'client/features/OneApi/marketplaceApp' @@ -34,7 +34,7 @@ const getTabComponent = (tabName) => const MarketplaceAppTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetMarketplaceAppQuery(id) + const { isLoading, isError, error } = useGetMarketplaceAppQuery(id) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.APP @@ -43,6 +43,14 @@ const MarketplaceAppTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/User/index.js b/src/fireedge/src/client/components/Tabs/User/index.js index 0291dd9623..c49e59c176 100644 --- a/src/fireedge/src/client/components/Tabs/User/index.js +++ b/src/fireedge/src/client/components/Tabs/User/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetUserQuery } from 'client/features/OneApi/user' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const UserTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetUserQuery(id) + const { isLoading, isError, error } = useGetUserQuery(id) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.USER @@ -41,6 +41,14 @@ const UserTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/VNetwork/index.js b/src/fireedge/src/client/components/Tabs/VNetwork/index.js index e845e91105..6f9bdee320 100644 --- a/src/fireedge/src/client/components/Tabs/VNetwork/index.js +++ b/src/fireedge/src/client/components/Tabs/VNetwork/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetVNetworkQuery } from 'client/features/OneApi/network' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const VNetworkTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetVNetworkQuery({ id }) + const { isLoading, isError, error } = useGetVNetworkQuery({ id }) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.VNET @@ -41,6 +41,14 @@ const VNetworkTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js b/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js index 5b74f12d43..346b7c320d 100644 --- a/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js +++ b/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetVNTemplateQuery } from 'client/features/OneApi/networkTemplate' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const VNetTemplateTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetVNTemplateQuery({ id }) + const { isLoading, isError, error } = useGetVNTemplateQuery({ id }) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.VN_TEMPLATE @@ -41,6 +41,14 @@ const VNetTemplateTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/Vm/index.js b/src/fireedge/src/client/components/Tabs/Vm/index.js index 42a7a4f9e6..9b8847a63f 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetVmQuery } from 'client/features/OneApi/vm' @@ -46,7 +46,7 @@ const getTabComponent = (tabName) => const VmTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetVmQuery(id, { + const { isLoading, isError, error } = useGetVmQuery(id, { refetchOnMountOrArgChange: 10, }) @@ -57,6 +57,14 @@ const VmTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/VmTemplate/index.js b/src/fireedge/src/client/components/Tabs/VmTemplate/index.js index b4c3b0e959..94ed0cb3e1 100644 --- a/src/fireedge/src/client/components/Tabs/VmTemplate/index.js +++ b/src/fireedge/src/client/components/Tabs/VmTemplate/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetTemplateQuery } from 'client/features/OneApi/vmTemplate' @@ -34,7 +34,7 @@ const getTabComponent = (tabName) => const VmTemplateTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetTemplateQuery({ id }) + const { isLoading, isError, error } = useGetTemplateQuery({ id }) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.VM_TEMPLATE @@ -43,6 +43,14 @@ const VmTemplateTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/components/Tabs/Zone/index.js b/src/fireedge/src/client/components/Tabs/Zone/index.js index 58fc377c36..b26d913322 100644 --- a/src/fireedge/src/client/components/Tabs/Zone/index.js +++ b/src/fireedge/src/client/components/Tabs/Zone/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' -import { LinearProgress } from '@mui/material' +import { Alert, LinearProgress } from '@mui/material' import { useViews } from 'client/features/Auth' import { useGetZoneQuery } from 'client/features/OneApi/zone' @@ -32,7 +32,7 @@ const getTabComponent = (tabName) => const ZoneTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading } = useGetZoneQuery(id) + const { isLoading, isError, error } = useGetZoneQuery(id) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.ZONE @@ -41,6 +41,14 @@ const ZoneTabs = memo(({ id }) => { return getAvailableInfoTabs(infoTabs, getTabComponent, id) }, [view]) + if (isError) { + return ( + + {error.data} + + ) + } + return isLoading ? ( ) : ( diff --git a/src/fireedge/src/client/constants/guacamole.js b/src/fireedge/src/client/constants/guacamole.js new file mode 100644 index 0000000000..7b48c6ccbc --- /dev/null +++ b/src/fireedge/src/client/constants/guacamole.js @@ -0,0 +1,101 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ + +// eslint-disable-next-line no-unused-vars +import { Client, WebSocketTunnel, Status } from 'guacamole-common-js' + +/** + * @typedef GuacamoleSessionThumbnail + * @property {number} timestamp - The time that this thumbnail was generated + * @property {HTMLCanvasElement} canvas - The thumbnail of the Guacamole client display + */ + +/** + * @typedef GuacamoleSessionProperties + * @property {boolean} autoFit - Whether the display should be scaled automatically + * @property {number} scale - The current scale. + * If autoFit is true, the effect of setting this value is undefined + * @property {number} minScale - The minimum scale value + * @property {number} maxScale - The maximum scale value + * @property {boolean} keyboardEnabled - Whether or not the client should listen to keyboard events + * @property {number} emulateAbsoluteMouse - Whether translation of touch to mouse events should + * emulate an absolute pointer device, or a relative pointer device + * @property {number} scrollTop - The relative Y coordinate of the scroll offset of the display + * @property {number} scrollLeft - The relative X coordinate of the scroll offset of the display + */ + +/** + * @typedef GuacamoleSessionState + * @property {GUACAMOLE_CLIENT_STATES} connectionState - The current connection state + * @property {Status.Code} statusCode - The status code of the current error condition + * @property {boolean} tunnelUnstable - Whether the network connection used by the tunnel seems unstable + */ + +/** + * @typedef GuacamoleSession + * @property {string} token - The token of the connection associated with this client + * @property {string} name - The name returned associated with the connection or connection group in use + * @property {string} title - The title which should be displayed as the page title for this client + * @property {Client} client - The actual underlying Guacamole client + * @property {WebSocketTunnel} tunnel - The tunnel being used by the underlying Guacamole client + * @property {GuacamoleSessionState} clientState - The current state of the Guacamole client + * @property {boolean} isUninitialized - When true, indicates that the session hasn't been fired yet + * @property {boolean} isLoading - When true, indicates that the session is awaiting a response + * @property {boolean} isConnected - When true, indicates that the last session was connected successfully + * @property {boolean} isDisconnected - When true, indicates that the last session was disconnected + * @property {boolean} isError - When true, indicates that the last session has an error state + * @property {GuacamoleSessionProperties} clientProperties - The current state of the Guacamole client + * @property {GuacamoleSessionThumbnail} thumbnail - The most recently-generated thumbnail for this connection + * @property {number} multiTouchSupport - The number of simultaneous touch contacts supported + */ + +/** @enum {string} Guacamole client state strings */ +export const GUACAMOLE_STATES_STR = { + IDLE: 'IDLE', + CONNECTING: 'CONNECTING', + WAITING: 'WAITING', + CONNECTED: 'CONNECTED', + DISCONNECTING: 'DISCONNECTING', + DISCONNECTED: 'DISCONNECTED', + CLIENT_ERROR: 'CLIENT_ERROR', + TUNNEL_ERROR: 'TUNNEL_ERROR', +} + +/** @enum {string} Guacamole client states */ +export const GUACAMOLE_CLIENT_STATES = [ + GUACAMOLE_STATES_STR.IDLE, + GUACAMOLE_STATES_STR.CONNECTING, + GUACAMOLE_STATES_STR.WAITING, + GUACAMOLE_STATES_STR.CONNECTED, + GUACAMOLE_STATES_STR.DISCONNECTING, + GUACAMOLE_STATES_STR.DISCONNECTED, +] + +/** + * The mimetype of audio data to be sent along the Guacamole + * connection if audio input is supported. + * + * @type {string} + */ +export const AUDIO_INPUT_MIMETYPE = 'audio/L16;rate=44100,channels=2' + +/** + * The minimum amount of time to wait between updates to + * the client thumbnail, in milliseconds. + * + * @type {number} + */ +export const THUMBNAIL_UPDATE_FREQUENCY = 5000 diff --git a/src/fireedge/src/client/constants/index.js b/src/fireedge/src/client/constants/index.js index 0e696b9879..03306c9434 100644 --- a/src/fireedge/src/client/constants/index.js +++ b/src/fireedge/src/client/constants/index.js @@ -27,7 +27,7 @@ export const BY = { url: 'https://opennebula.io/', } -export const _APPS = defaultApps +export const _APPS = { ...defaultApps } export const APPS = Object.keys(defaultApps) export const APPS_IN_BETA = [_APPS.sunstone.name] export const APPS_WITH_SWITCHER = [_APPS.sunstone.name] @@ -90,6 +90,7 @@ export const SOCKETS = { DISCONNECT: 'disconnect', HOOKS: 'hooks', PROVISION: 'provision', + GUACAMOLE: 'guacamole', } /** @enum {string} Names of resource */ @@ -114,23 +115,24 @@ export const RESOURCE_NAMES = { export * as T from 'client/constants/translates' export * as ACTIONS from 'client/constants/actions' export * as STATES from 'client/constants/states' -export * from 'client/constants/common' -export * from 'client/constants/quota' -export * from 'client/constants/scheduler' -export * from 'client/constants/userInput' -export * from 'client/constants/flow' -export * from 'client/constants/provision' -export * from 'client/constants/user' -export * from 'client/constants/group' export * from 'client/constants/cluster' -export * from 'client/constants/vm' -export * from 'client/constants/vmTemplate' -export * from 'client/constants/network' -export * from 'client/constants/networkTemplate' +export * from 'client/constants/common' +export * from 'client/constants/datastore' +export * from 'client/constants/flow' +export * from 'client/constants/group' +export * from 'client/constants/guacamole' export * from 'client/constants/host' export * from 'client/constants/image' export * from 'client/constants/marketplace' export * from 'client/constants/marketplaceApp' -export * from 'client/constants/datastore' +export * from 'client/constants/network' +export * from 'client/constants/networkTemplate' +export * from 'client/constants/provision' +export * from 'client/constants/quota' +export * from 'client/constants/scheduler' export * from 'client/constants/securityGroup' +export * from 'client/constants/user' +export * from 'client/constants/userInput' +export * from 'client/constants/vm' +export * from 'client/constants/vmTemplate' export * from 'client/constants/zone' diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 12a0118bb8..d1aa495806 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -361,6 +361,19 @@ module.exports = { ReadyStatusGate: 'Ready status gate', /* VM schema */ + /* VM schema - remote access */ + Vnc: 'VNC', + Ssh: 'SSH', + Rdp: 'RDP', + SshConnection: 'SSH connection', + RdpConnection: 'RDP connection', + Vmrc: 'VMRC', + Sdl: 'SDL', + Spice: 'SPICE', + SendCtrlAltDel: 'Send Ctrl-Alt-Del', + Reconnect: 'Reconnect', + FullScreen: 'Full screen', + Screenshot: 'Screenshot', /* VM schema - info */ UserTemplate: 'User Template', Template: 'Template', @@ -389,8 +402,6 @@ module.exports = { NIC: 'NIC', Alias: 'Alias', AsAnAlias: 'Attach as an alias', - RdpConnection: 'RDP connection', - SshConnection: 'SSH connection', External: 'External', ExternalConcept: 'The NIC will be attached as an external alias of the VM', OverrideNetworkValuesIPv4: 'Override Network Values IPv4', @@ -568,10 +579,6 @@ module.exports = { Class: 'Class', /* VM Template schema - Input/Output - graphics */ Graphics: 'Graphics', - VMRC: 'VMRC', - VNC: 'VNC', - SDL: 'SDL', - SPICE: 'SPICE', ListenOnIp: 'Listen on IP', ServerPort: 'Server port', ServerPortConcept: 'Port for the VNC/SPICE server', diff --git a/src/fireedge/src/client/constants/vm.js b/src/fireedge/src/client/constants/vm.js index 749c907e4b..baffe06b5e 100644 --- a/src/fireedge/src/client/constants/vm.js +++ b/src/fireedge/src/client/constants/vm.js @@ -873,13 +873,12 @@ export const VM_ACTIONS_BY_STATE = { [VM_ACTIONS.UNRESCHED]: [STATES.RUNNING, STATES.UNKNOWN], // REMOTE - [VM_ACTIONS.VMRC]: [], - [VM_ACTIONS.SPICE]: [], - [VM_ACTIONS.VNC]: [], - [VM_ACTIONS.SSH]: [], - [VM_ACTIONS.RDP]: [], - [VM_ACTIONS.FILE_RDP]: [], - [VM_ACTIONS.FILE_VIRT_VIEWER]: [], + [VM_ACTIONS.VMRC]: [STATES.RUNNING], + [VM_ACTIONS.VNC]: [STATES.RUNNING], + [VM_ACTIONS.SSH]: [STATES.RUNNING], + [VM_ACTIONS.RDP]: [STATES.RUNNING], + [VM_ACTIONS.FILE_RDP]: [STATES.RUNNING], + [VM_ACTIONS.FILE_VIRT_VIEWER]: [STATES.RUNNING], // INFORMATION [VM_ACTIONS.RENAME]: [], diff --git a/src/fireedge/src/client/containers/Guacamole/index.js b/src/fireedge/src/client/containers/Guacamole/index.js new file mode 100644 index 0000000000..208ee332af --- /dev/null +++ b/src/fireedge/src/client/containers/Guacamole/index.js @@ -0,0 +1,152 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { ReactElement, useMemo, useRef, useEffect } from 'react' +import { useParams, useHistory } from 'react-router' +import { Box, Stack, Typography, Divider, Skeleton } from '@mui/material' + +import { useGetVmQuery } from 'client/features/OneApi/vm' +import { + useGuacamoleSession, + GuacamoleDisplay, + GuacamoleKeyboard, + GuacamoleMouse, + GuacamoleClipboard, + GuacamoleCtrlAltDelButton, + GuacamoleReconnectButton, + GuacamoleFullScreenButton, + GuacamoleScreenshotButton, +} from 'client/components/Consoles' +import { useViews } from 'client/features/Auth' +import { StatusCircle } from 'client/components/Status' +import MultipleTags from 'client/components/MultipleTags' +import { getIps, getState } from 'client/models/VirtualMachine' +import { timeFromMilliseconds } from 'client/models/Helper' +import { PATH } from 'client/apps/sunstone/routes' +import { RESOURCE_NAMES } from 'client/constants' + +/** @returns {ReactElement} Guacamole container */ +const Guacamole = () => { + const { id, type = '' } = useParams() + const { push: redirectTo } = useHistory() + const { view, [RESOURCE_NAMES.VM]: vmView } = useViews() + + const containerRef = useRef(null) + const headerRef = useRef(null) + + const { data: vm, isLoading, isError } = useGetVmQuery(id) + + const ips = getIps(vm) + const { color: stateColor, name: stateName } = getState(vm) ?? {} + const time = timeFromMilliseconds(+vm?.ETIME || +vm?.STIME) + + const { token, clientState, displayElement, ...session } = + useGuacamoleSession( + useMemo( + () => ({ + id: `${id}-${type}`, + container: containerRef.current, + header: headerRef.current, + }), + [ + containerRef.current?.offsetWidth, + containerRef.current?.offsetHeight, + headerRef.current?.offsetWidth, + headerRef.current?.offsetHeight, + ] + ), + GuacamoleDisplay, + GuacamoleMouse, + GuacamoleKeyboard, + GuacamoleClipboard + ) + + useEffect(() => { + const noAction = vmView?.actions?.[type] !== true + + // token should be saved after click on console button from datatable + if (noAction || !token || isError) { + redirectTo(PATH.DASHBOARD) + } + }, [view, token]) + + return ( + + + + + {isLoading ? ( + <> + + + + ) : ( + <> + + {`# ${vm?.ID} - ${vm?.NAME}`} + + )} + + } + gap="1em" + > + {isLoading ? ( + + ) : ( + {`Started on: ${time.toFormat('ff')}`} + )} + {isLoading ? ( + + ) : ( + !!ips?.length && ( + + + + ) + )} + + + + + + + + {clientState?.connectionState && ( + {`State: ${clientState?.connectionState}`} + )} + + + {displayElement} + + ) +} + +export default Guacamole diff --git a/src/fireedge/src/client/features/Auth/hooks.js b/src/fireedge/src/client/features/Auth/hooks.js index 55b08a3481..d019d16624 100644 --- a/src/fireedge/src/client/features/Auth/hooks.js +++ b/src/fireedge/src/client/features/Auth/hooks.js @@ -112,5 +112,19 @@ export const useViews = () => { [view] ) - return useMemo(() => ({ getResourceView, views, view }), [views, view]) + return useMemo( + () => ({ + ...Object.values(RESOURCE_NAMES).reduce( + (listOfResourceViews, resourceName) => ({ + ...listOfResourceViews, + [resourceName]: getResourceView(resourceName), + }), + {} + ), + getResourceView, + views, + view, + }), + [views, view] + ) } diff --git a/src/fireedge/src/client/features/Guacamole/hooks.js b/src/fireedge/src/client/features/Guacamole/hooks.js new file mode 100644 index 0000000000..e04ee135d5 --- /dev/null +++ b/src/fireedge/src/client/features/Guacamole/hooks.js @@ -0,0 +1,73 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ + +import { useCallback, useMemo } from 'react' +import { useDispatch, useSelector, shallowEqual } from 'react-redux' + +import { name as guacSlice, actions } from 'client/features/Guacamole/slice' +import { GuacamoleSession } from 'client/constants' + +const { + addGuacamoleSession, + removeGuacamoleSession, + updateThumbnail, + setConnectionState, + setTunnelUnstable, + setMultiTouchSupport, +} = actions + +// -------------------------------------------------------------- +// Guacamole Hooks +// -------------------------------------------------------------- + +/** + * Hook to get the state of Guacamole sessions. + * + * @param {string} [id] - Session id to subscribe + * @returns {object|GuacamoleSession} Return Guacamole session by id or global state + */ +export const useGuacamole = (id) => { + const guac = useSelector( + (state) => (id ? state[guacSlice][id] : state[guacSlice]), + shallowEqual + ) + + return useMemo(() => ({ ...guac }), [guac]) +} + +/** + * Hook to manage Guacamole sessions. + * + * @param {string} [id] - Session id to operate + * @returns {object} Return management actions + */ +export const useGuacamoleApi = (id) => { + const dispatch = useDispatch() + + const commonDispatch = useCallback( + (action) => (data) => dispatch(action({ id, ...data })), + [dispatch, id] + ) + + return { + addSession: commonDispatch(addGuacamoleSession), + removeSession: commonDispatch(removeGuacamoleSession), + updateThumbnail: commonDispatch(updateThumbnail), + setConnectionState: commonDispatch(setConnectionState), + setTunnelUnstable: commonDispatch(setTunnelUnstable), + setMultiTouchSupport: commonDispatch(setMultiTouchSupport), + } +} diff --git a/src/fireedge/src/client/features/Guacamole/index.js b/src/fireedge/src/client/features/Guacamole/index.js new file mode 100644 index 0000000000..100b0e15bf --- /dev/null +++ b/src/fireedge/src/client/features/Guacamole/index.js @@ -0,0 +1,17 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +export * from 'client/features/Guacamole/slice' +export * from 'client/features/Guacamole/hooks' diff --git a/src/fireedge/src/client/features/Guacamole/slice.js b/src/fireedge/src/client/features/Guacamole/slice.js new file mode 100644 index 0000000000..61185e5d15 --- /dev/null +++ b/src/fireedge/src/client/features/Guacamole/slice.js @@ -0,0 +1,126 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { createSlice } from '@reduxjs/toolkit' +import { Status } from 'guacamole-common-js' + +import { actions as authActions } from 'client/features/Auth/slice' +import { GUACAMOLE_STATES_STR } from 'client/constants' + +const { + IDLE, + CONNECTING, + WAITING, + CONNECTED, + DISCONNECTING, + DISCONNECTED, + CLIENT_ERROR, + TUNNEL_ERROR, +} = GUACAMOLE_STATES_STR + +const getIdentifiedFromPayload = ({ id, type } = {}) => + id?.includes('-') ? id : `${id}-${type}` + +const INITIAL_SESSION = { + thumbnail: null, + multiTouchSupport: 0, + clientState: { + connectionState: IDLE, + tunnelUnstable: false, + statusCode: Status.Code.SUCCESS, + }, + isUninitialized: true, + isLoading: false, + isConnected: false, + isDisconnected: false, + isError: false, + clientProperties: { + autoFit: true, + scale: 1, + minScale: 1, + maxScale: 3, + focused: false, + scrollTop: 0, + scrollLeft: 0, + }, +} + +const slice = createSlice({ + name: 'guacamole', + initialState: {}, + reducers: { + addGuacamoleSession: (state, { payload }) => { + const id = getIdentifiedFromPayload(payload) + state[id] = { ...INITIAL_SESSION, token: payload?.token } + }, + removeGuacamoleSession: (state, { payload }) => { + const id = getIdentifiedFromPayload(payload) + const { [id]: _, ...rest } = state + + return { ...rest } + }, + updateGuacamoleSession: (state, { payload }) => { + const id = getIdentifiedFromPayload(payload) + const { [id]: session = {} } = state + + state[id] = { ...session, ...payload?.session } + }, + setConnectionState: (state, { payload = {} }) => { + const { state: cState, statusCode } = payload + const id = getIdentifiedFromPayload(payload) + const { [id]: session = {} } = state + + if ( + !session || + session?.clientState.connectionState === TUNNEL_ERROR || + session?.clientState.connectionState === CLIENT_ERROR + ) + return state + + statusCode && (session.clientState.statusCode = statusCode) + session.clientState.connectionState = cState + session.clientState.tunnelUnstable = false + + session.isUninitialized = cState === IDLE + session.isLoading = [WAITING, CONNECTING, DISCONNECTING].includes(cState) + session.isConnected = cState === CONNECTED + session.isDisconnected = cState === DISCONNECTED + session.isError = [CLIENT_ERROR, TUNNEL_ERROR].includes(cState) + }, + setTunnelUnstable: (state, { payload = {} }) => { + const id = getIdentifiedFromPayload(payload) + const { [id]: session = {} } = state + + session.clientState.tunnelUnstable = payload.unstable + }, + setMultiTouchSupport: (state, { payload = {} }) => { + const id = getIdentifiedFromPayload(payload) + const { [id]: session = {} } = state + + state[id] = { ...session, multiTouchSupport: payload?.touches } + }, + updateThumbnail: (state, { payload = {} }) => { + const id = getIdentifiedFromPayload(payload) + const { [id]: session = {} } = state + + state[id] = { ...session, thumbnail: payload?.thumbnail } + }, + }, + extraReducers: (builder) => { + builder.addCase(authActions.logout, () => ({})) + }, +}) + +export const { name, reducer, actions } = slice diff --git a/src/fireedge/src/client/features/OneApi/vm.js b/src/fireedge/src/client/features/OneApi/vm.js index 68e387cb9e..1fd5ff0ca8 100644 --- a/src/fireedge/src/client/features/OneApi/vm.js +++ b/src/fireedge/src/client/features/OneApi/vm.js @@ -14,11 +14,16 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import { Actions, Commands } from 'server/utils/constants/commands/vm' +import { + Actions as ExtraActions, + Commands as ExtraCommands, +} from 'server/routes/api/vm/routes' import { oneApi, ONE_RESOURCES, ONE_RESOURCES_POOL, } from 'client/features/OneApi' +import { actions as guacamoleActions } from 'client/features/Guacamole/slice' import { UpdateFromSocket } from 'client/features/OneApi/socket' import http from 'client/utils/rest' import { @@ -101,7 +106,13 @@ const vmApi = oneApi.injectEndpoints({ index !== -1 && (draft[index] = queryVm) }) ) - } catch {} + } catch { + dispatch( + vmApi.util.updateQueryData('getVms', undefined, (draft) => + draft.filter(({ ID }) => +ID !== +id) + ) + ) + } }, onCacheEntryAdded: UpdateFromSocket({ updateQueryData: (updateFn) => @@ -109,6 +120,29 @@ const vmApi = oneApi.injectEndpoints({ resource: VM.toLowerCase(), }), }), + getGuacamoleSession: builder.query({ + /** + * Returns a Guacamole session. + * + * @param {object} params - Request parameters + * @param {string} params.id - Virtual machine id + * @param {'vnc'|'ssh'|'rdp'} params.type - Connection type + * @returns {string} The session token + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = ExtraActions.GUACAMOLE + const command = { name, ...ExtraCommands[name] } + + return { params, command } + }, + async onQueryStarted({ id, type }, { dispatch, queryFulfilled }) { + try { + const { data: token } = await queryFulfilled + dispatch(guacamoleActions.addGuacamoleSession({ id, type, token })) + } catch {} + }, + }), getMonitoring: builder.query({ /** * Returns the virtual machine monitoring records. @@ -535,6 +569,34 @@ const vmApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: VM, id }], + async onQueryStarted( + { id, ...permissions }, + { dispatch, queryFulfilled } + ) { + const patchResult = dispatch( + vmApi.util.updateQueryData('getVm', id, (draft) => { + Object.entries(permissions) + .filter(([_, value]) => value !== '-1') + .forEach(([name, value]) => { + const ensuredName = { + ownerUse: 'OWNER_U', + ownerManage: 'OWNER_M', + ownerAdmin: 'OWNER_A', + groupUse: 'GROUP_U', + groupManage: 'GROUP_M', + groupAdmin: 'GROUP_A', + otherUse: 'OTHER_U', + otherManage: 'OTHER_M', + otherAdmin: 'OTHER_A', + }[name] + + draft.PERMISSIONS[ensuredName] = value + }) + }) + ) + + queryFulfilled.catch(patchResult.undo) + }, }), changeVmOwnership: builder.mutation({ /** @@ -844,6 +906,8 @@ export const { useLazyGetVmsQuery, useGetVmQuery, useLazyGetVmQuery, + useGetGuacamoleSessionQuery, + useLazyGetGuacamoleSessionQuery, useGetMonitoringQuery, useLazyGetMonitoringQuery, useGetMonitoringPoolQuery, diff --git a/src/fireedge/src/client/features/middleware.js b/src/fireedge/src/client/features/middleware.js index 1c2fd202a5..12a5bee131 100644 --- a/src/fireedge/src/client/features/middleware.js +++ b/src/fireedge/src/client/features/middleware.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { isRejectedWithValue, Middleware, Dispatch } from '@reduxjs/toolkit' -import * as Auth from 'client/features/Auth/slice' +import { name as authName, logout } from 'client/features/Auth/slice' import { T, ONEADMIN_GROUP_ID } from 'client/constants' /** @@ -27,7 +27,7 @@ export const unauthenticatedMiddleware = (next) => (action) => { if (isRejectedWithValue(action) && action.payload.status === 401) { - dispatch(Auth.actions.logout(T.SessionExpired)) + dispatch(logout(T.SessionExpired)) } return next(action) @@ -41,13 +41,13 @@ export const onlyForOneadminMiddleware = ({ dispatch, getState }) => (next) => (action) => { - const groups = getState()?.[Auth.name]?.user?.GROUPS?.ID + const groups = getState()?.[authName]?.user?.GROUPS?.ID - if (!Auth.actions.logout.match(action) && groups) { + if (!logout.match(action) && !!groups?.length) { const ensuredGroups = [groups].flat() !ensuredGroups.includes(ONEADMIN_GROUP_ID) && - dispatch(Auth.actions.logout(T.OnlyForOneadminGroup)) + dispatch(logout(T.OnlyForOneadminGroup)) } return next(action) diff --git a/src/fireedge/src/client/models/Guacamole.js b/src/fireedge/src/client/models/Guacamole.js new file mode 100644 index 0000000000..1cc93809d8 --- /dev/null +++ b/src/fireedge/src/client/models/Guacamole.js @@ -0,0 +1,56 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { GUACAMOLE_CLIENT_STATES } from 'client/constants' + +const isWindow = (display) => display instanceof Window + +/** + * @param {number} clientState - Guacamole client state + * @returns {string} State information from resource + */ +export const clientStateToString = (clientState) => + GUACAMOLE_CLIENT_STATES[+clientState] + +/** + * Returns the string of connection parameters + * to be passed to the Guacamole client. + * + * @param {object} options - Connection parameters + * @param {HTMLElement} options.display - Element where the connection will be displayed + * @param {number} options.width - Forced width connection + * @param {number} options.height - Forced height connection + * @returns {string} A string of connection parameters + */ +export const getConnectString = (options = {}) => { + const { token, display = window, dpi, width, height } = options + + // Calculate optimal width/height for display + const pixelDensity = window.devicePixelRatio || 1 + const optimalDpi = dpi || pixelDensity * 96 + + const displayWidth = + width || (isWindow(display) ? display?.innerWidth : display?.offsetWidth) + + const displayHeight = + height || (isWindow(display) ? display?.innerHeight : display?.offsetHeight) + + return [ + `token=${encodeURIComponent(token)}`, + `width=${Math.floor(displayWidth * pixelDensity)}`, + `height=${Math.floor(displayHeight * pixelDensity)}`, + `dpi=${Math.floor(optimalDpi)}`, + ].join('&') +} diff --git a/src/fireedge/src/client/models/VirtualMachine.js b/src/fireedge/src/client/models/VirtualMachine.js index 0a7c72becd..e572ff5109 100644 --- a/src/fireedge/src/client/models/VirtualMachine.js +++ b/src/fireedge/src/client/models/VirtualMachine.js @@ -18,6 +18,7 @@ import { prettySecurityGroup, } from 'client/models/SecurityGroup' import { isRelative } from 'client/models/Scheduler' +import { stringToBoolean } from 'client/models/Helper' import { STATES, @@ -286,3 +287,16 @@ export const isAvailableAction = (state) => !VM_ACTIONS_BY_STATE[action]?.includes(state) ) } + +/** + * @param {VM} vm - Virtual machine + * @param {'ssh'|'rdp'} type - Connection type + * @returns {boolean} - Returns connection type is available + */ +export const nicsIncludesTheConnectionType = (vm, type) => { + const ensuredConnection = String(type).toUpperCase() + + if (!['SSH', 'RDP'].includes(ensuredConnection)) return false + + return getNics(vm).some((nic) => stringToBoolean(nic[ensuredConnection])) +} diff --git a/src/fireedge/src/client/router/index.js b/src/fireedge/src/client/router/index.js index c60ee61a3b..a74982c998 100644 --- a/src/fireedge/src/client/router/index.js +++ b/src/fireedge/src/client/router/index.js @@ -28,9 +28,9 @@ import { import { ProtectedRoute, NoAuthRoute } from 'client/components/Route' import { InternalLayout } from 'client/components/HOC' -const renderRoute = ({ Component, label, ...rest }, index) => ( +const renderRoute = ({ Component, label, disabledSidebar, ...rest }, index) => ( - + } /> @@ -72,7 +72,7 @@ Router.propTypes = { PropTypes.shape({ Component: PropTypes.object, icon: PropTypes.object, - label: PropTypes.string.isRequired, + label: PropTypes.string, path: PropTypes.string, sidebar: PropTypes.bool, routes: PropTypes.array, diff --git a/src/fireedge/src/client/store/index.js b/src/fireedge/src/client/store/index.js index becbe1df83..5067fd1c8b 100644 --- a/src/fireedge/src/client/store/index.js +++ b/src/fireedge/src/client/store/index.js @@ -20,6 +20,7 @@ import { isDevelopment } from 'client/utils' import * as Auth from 'client/features/Auth/slice' import * as General from 'client/features/General/slice' +import * as Guacamole from 'client/features/Guacamole/slice' import { authApi } from 'client/features/AuthApi' import { oneApi } from 'client/features/OneApi' import { unauthenticatedMiddleware } from 'client/features/middleware' @@ -35,6 +36,7 @@ export const createStore = ({ initState = {}, extraMiddleware = [] }) => { reducer: { [Auth.name]: Auth.reducer, [General.name]: General.reducer, + [Guacamole.name]: Guacamole.reducer, [authApi.reducerPath]: authApi.reducer, [oneApi.reducerPath]: oneApi.reducer, }, diff --git a/src/fireedge/src/client/utils/helpers.js b/src/fireedge/src/client/utils/helpers.js index 601217b0d7..f6cee39718 100644 --- a/src/fireedge/src/client/utils/helpers.js +++ b/src/fireedge/src/client/utils/helpers.js @@ -90,6 +90,32 @@ export const encodeBase64 = (string, defaultValue = '') => { } } +/** + * Generates a link to download the file, then remove it. + * + * @param {File} file - File + */ +export const downloadFile = (file) => { + try { + // Create a link and set the URL using `createObjectURL` + const link = document.createElement('a') + link.style.display = 'none' + link.href = URL.createObjectURL(file) + link.download = file.name + + // It needs to be added to the DOM so it can be clicked + document.body.appendChild(link) + link.click() + + // To make this work on Firefox we need to wait + // a little while before removing it + setTimeout(() => { + URL.revokeObjectURL(link.href) + link.parentNode.removeChild(link) + }, 0) + } catch (e) {} +} + /** * Converts a long string of units into a readable format e.g KB, MB, GB, TB, YB. * diff --git a/src/fireedge/src/server/index.js b/src/fireedge/src/server/index.js index 5f9b9c20cd..d00c9719d5 100644 --- a/src/fireedge/src/server/index.js +++ b/src/fireedge/src/server/index.js @@ -129,7 +129,7 @@ frontApps.forEach((frontApp) => { app.get(`${basename}/${frontApp}`, entrypointApp) app.get(`${basename}/${frontApp}/*`, entrypointApp) }) -app.get('/*', (req, res) => res.redirect(`/${defaultAppName}/provision`)) +app.get('/*', (req, res) => res.redirect(`/${defaultAppName}/sunstone`)) // 404 - public app.get('*', entrypoint404) diff --git a/src/fireedge/src/server/routes/api/auth/utils.js b/src/fireedge/src/server/routes/api/auth/utils.js index 5696a6df96..a3ee3c54f0 100644 --- a/src/fireedge/src/server/routes/api/auth/utils.js +++ b/src/fireedge/src/server/routes/api/auth/utils.js @@ -406,14 +406,19 @@ const setZones = () => { * Create token server admin. * * @param {object} config - config create token serveradmin - * @param {string} config.serverAdmin - serverAdmin username * @param {string} config.username - user name * @param {string} config.key - serverAdmin key * @param {string} config.iv - serverAdmin iv + * @param {string} config.serverAdmin - serverAdmin username * @returns {object|undefined} data encrypted serveradmin */ -const createTokenServerAdmin = ({ serverAdmin, username, key, iv }) => { - if (serverAdmin && username && key && iv) { +const createTokenServerAdmin = ({ + username, + key, + iv, + serverAdmin = username, +}) => { + if (username && key && iv) { !(expireTime && typeof expireTime.toSeconds === 'function') && setDates() const expire = parseInt(expireTime.toSeconds(), 10) diff --git a/src/fireedge/src/server/routes/api/vm/functions.js b/src/fireedge/src/server/routes/api/vm/functions.js index 4ac4a9947c..9a60b95c76 100644 --- a/src/fireedge/src/server/routes/api/vm/functions.js +++ b/src/fireedge/src/server/routes/api/vm/functions.js @@ -13,14 +13,25 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ +const { randomBytes, createCipheriv } = require('crypto') + const { defaults, httpCodes } = require('server/utils/constants') -const { httpResponse, executeCommand } = require('server/utils/server') +const { + httpResponse, + executeCommand, + getSunstoneAuth, +} = require('server/utils/server') const { getSunstoneConfig } = require('server/utils/yml') +const { Actions: userActions } = require('server/utils/constants/commands/user') +const { Actions: vmActions } = require('server/utils/constants/commands/vm') +const { createTokenServerAdmin } = require('server/routes/api/auth/utils') -const { defaultEmptyFunction, defaultCommandVM } = defaults +const { USER_INFO } = userActions +const { VM_INFO } = vmActions + +const { ok, unauthorized, internalServerError, badRequest } = httpCodes +const { defaultEmptyFunction, defaultCommandVM, defaultTypeCrypto } = defaults -const { ok, internalServerError, badRequest } = httpCodes -const httpBadRequest = httpResponse(badRequest, '', '') const appConfig = getSunstoneConfig() const prependCommand = appConfig.sunstone_prepend || '' const regexpSplitLine = /\r|\n/ @@ -39,7 +50,7 @@ const saveAsTemplate = ( params = {}, userData = {} ) => { - let rtn = httpBadRequest + let rtn = httpResponse(badRequest, '', '') const { id, name, persistent } = params if (id && name) { let message = '' @@ -66,7 +77,216 @@ const saveAsTemplate = ( next() } +/** + * Generates a session to connect a VM by id through Guacamole. + * + * @param {object} res - http response + * @param {Function} next - express stepper + * @param {object} params - params of http request + * @param {string} params.id - VM id + * @param {'vnc'|'ssh'|'rdp'} params.type - Type connection + * @param {object} userData - user of http request + * @param {Function} xmlrpc - XML-RPC function + */ +const generateGuacamoleSession = ( + res = {}, + next = defaultEmptyFunction, + params = {}, + userData = {}, + xmlrpc = defaultEmptyFunction +) => { + const { id: userAuthId } = userData + const { id: vmId, type } = params + const ensuredType = `${type}`.toLowerCase() + + if (!['vnc', 'ssh', 'rdp'].includes(ensuredType)) { + const messageError = "Type connection isn't supported by Guacamole" + res.locals.httpCode = httpResponse(badRequest, messageError) + next() + } + + const serverAdmin = getSunstoneAuth() ?? {} + const { token: authToken } = createTokenServerAdmin(serverAdmin) ?? {} + + if (!authToken) { + res.locals.httpCode = httpResponse(badRequest, '') + next() + } + + const { username } = serverAdmin + const oneClient = xmlrpc(`${username}:${username}`, authToken) + + // get authenticated user + oneClient({ + action: USER_INFO, + parameters: [parseInt(userAuthId, 10), true], + callback: (userInfoErr, { USER } = {}) => { + if (userInfoErr || !USER) { + res.locals.httpCode = httpResponse(badRequest, userInfoErr) + next() + } + + // get VM information by id + oneClient({ + action: VM_INFO, + parameters: [parseInt(vmId, 10), true], + callback: (vmInfoErr, { VM } = {}) => { + if (vmInfoErr || !VM) { + res.locals.httpCode = httpResponse(unauthorized, vmInfoErr) + next() + } + + const settings = { + vnc: () => getVncSettings(VM), + ssh: () => getSshSettings(VM, USER), + rdp: () => getRdpSettings(VM), + }[ensuredType]?.() ?? { error: '' } + + if (settings.error) { + res.locals.httpCode = httpResponse(badRequest, settings.error) + next() + } + + // const minutesToAdd = 1 + // const currentDate = new Date() + // const expiration = currentDate.getTime() + minutesToAdd * 60000 + + const connection = { + // expiration, + connection: { + type: ensuredType, + settings: { + security: 'any', + 'ignore-cert': 'true', + 'enable-drive': 'true', + 'create-drive-path': 'true', + ...settings, + }, + }, + } + + const wsToken = JSON.stringify(encryptConnection(connection)) + const encodedWsToken = Buffer.from(wsToken).toString('base64') + + res.locals.httpCode = httpResponse(ok, encodedWsToken) + next() + }, + }) + }, + }) +} + +const getVncSettings = (vmInfo) => { + const config = {} + + if (`${vmInfo.USER_TEMPLATE?.HYPERVISOR}`.toLowerCase() === 'vcenter') { + const esxHost = vmInfo?.MONITORING?.VCENTER_ESX_HOST + + if (!esxHost) { + return { + error: `Could not determine the vCenter ESX host where + the VM is running. Wait till the VCENTER_ESX_HOST attribute is + retrieved once the host has been monitored`, + } + } + + config.hostname = esxHost + } + + if (!config.hostname) { + const lastHistory = [vmInfo.HISTORY_RECORDS?.HISTORY ?? []].flat().at(-1) + config.hostname = lastHistory?.HOSTNAME ?? 'localhost' + } + + config.port = vmInfo.TEMPLATE?.GRAPHICS?.PORT ?? '5900' + config.password = vmInfo.TEMPLATE?.GRAPHICS?.PASSWD ?? null + + return config +} + +const getSshSettings = (vmInfo, authUser) => { + const config = {} + + const nics = [ + vmInfo.TEMPLATE?.NIC ?? [], + vmInfo.TEMPLATE?.NIC_ALIAS ?? [], + ].flat() + + const nicWithExternalPortRange = nics.find((nic) => !nic.EXTERNAL_PORT_RANGE) + const { EXTERNAL_PORT_RANGE } = nicWithExternalPortRange ?? {} + + if (EXTERNAL_PORT_RANGE) { + const lastHistory = [vmInfo.HISTORY_RECORDS?.HISTORY ?? []].flat().at(-1) + const lastHostname = lastHistory?.HOSTNAME + + if (lastHostname) { + config.hostname = lastHostname + config.port = parseInt(EXTERNAL_PORT_RANGE.split(':')[0], 10) + 21 + } + } + + if (!config.hostname) { + const nicWithSsh = nics.find(({ SSH }) => `${SSH}`.toLowerCase() === 'yes') + config.hostname = nicWithSsh?.EXTERNAL_IP ?? nicWithSsh?.IP + } + + if (!config.hostname) { + return { error: 'Wrong configuration. Cannot find a NIC with SSH' } + } + + config.port ??= vmInfo.TEMPLATE?.CONTEXT?.SSH_PORT ?? '22' + + if (vmInfo.TEMPLATE?.CONTEXT?.SSH_PUBLIC_KEY) { + config['private-key'] = authUser?.TEMPLATE?.SSH_PRIVATE_KEY + config.passphrase = authUser?.TEMPLATE?.SSH_PASSPHRASE + } else { + config.username = vmInfo.TEMPLATE?.CONTEXT?.USERNAME + config.password = vmInfo.TEMPLATE?.CONTEXT?.PASSWORD + } + + return config +} + +const getRdpSettings = (vmInfo) => { + const config = {} + + const nics = [ + vmInfo.TEMPLATE?.NIC ?? [], + vmInfo.TEMPLATE?.NIC_ALIAS ?? [], + ].flat() + + const nicWithRdp = nics.find(({ RDP }) => `${RDP}`.toLowerCase() === 'yes') + config.hostname = nicWithRdp?.EXTERNAL_IP ?? nicWithRdp?.IP + + if (!config.hostname) { + return { error: 'Wrong configuration. Cannot find a NIC with RDP' } + } + + config.port = vmInfo.TEMPLATE?.CONTEXT?.RDP_PORT ?? '3389' + config.username = vmInfo.TEMPLATE?.CONTEXT?.USERNAME + config.password = vmInfo.TEMPLATE?.CONTEXT?.PASSWORD + config['resize-method'] = 'display-update' + + if (config.username && config.password) config.security = 'nla' + + return config +} + +const encryptConnection = (data) => { + const iv = randomBytes(16) + const key = global.paths.FIREEDGE_KEY + const cipher = createCipheriv(defaultTypeCrypto, key, iv) + + const ensuredData = typeof data === 'string' ? data : JSON.stringify(data) + let value = cipher.update(ensuredData, 'utf-8', 'base64') + value += cipher.final('base64') + + return { iv: iv.toString('base64'), value } +} + const functionRoutes = { saveAsTemplate, + generateGuacamoleSession, } + module.exports = functionRoutes diff --git a/src/fireedge/src/server/routes/api/vm/index.js b/src/fireedge/src/server/routes/api/vm/index.js index 09cfb6f3f5..6ec511f76b 100644 --- a/src/fireedge/src/server/routes/api/vm/index.js +++ b/src/fireedge/src/server/routes/api/vm/index.js @@ -15,13 +15,20 @@ * ------------------------------------------------------------------------- */ const { Actions, Commands } = require('server/routes/api/vm/routes') -const { saveAsTemplate } = require('server/routes/api/vm/functions') +const { + saveAsTemplate, + generateGuacamoleSession, +} = require('server/routes/api/vm/functions') -const { VM_SAVEASTEMPLATE } = Actions +const { VM_SAVEASTEMPLATE, GUACAMOLE } = Actions module.exports = [ { ...Commands[VM_SAVEASTEMPLATE], action: saveAsTemplate, }, + { + ...Commands[GUACAMOLE], + action: generateGuacamoleSession, + }, ] diff --git a/src/fireedge/src/server/routes/api/vm/routes.js b/src/fireedge/src/server/routes/api/vm/routes.js index ae8b3f1977..5ee4a6f9fd 100644 --- a/src/fireedge/src/server/routes/api/vm/routes.js +++ b/src/fireedge/src/server/routes/api/vm/routes.js @@ -17,15 +17,18 @@ const { httpMethod, from: fromData, -} = require('server/utils/constants/defaults') +} = require('../../../utils/constants/defaults') const basepath = '/vm' -const { POST } = httpMethod +const { POST, GET } = httpMethod const { resource, postBody } = fromData const VM_SAVEASTEMPLATE = 'vm.saveastemplate' +const GUACAMOLE = 'vm.guacamole' + const Actions = { VM_SAVEASTEMPLATE, + GUACAMOLE, } module.exports = { @@ -47,5 +50,18 @@ module.exports = { }, }, }, + [GUACAMOLE]: { + path: `${basepath}/:id/guacamole/:type`, + httpMethod: GET, + auth: true, + params: { + id: { + from: resource, + }, + type: { + from: resource, + }, + }, + }, }, }