diff --git a/.env.example b/.env.example index befe7f5..ee37e39 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,14 @@ -REDIS_HOST= -REDIS_PORT= +REDIS_HOST=redis_db +REDIS_PORT=6379 REDIS_PASSWORD= -POSTGRES_HOST= -POSTGRES_DB= -POSTGRES_USER= +POSTGRES_HOST=localhost +POSTGRES_DB=ems +POSTGRES_USER=ems POSTGRES_PASSWORD= -POSTGRES_PORT= -EMS_PORT= -MONITOR_PORT= -CLICKHOUSE_DB= -CLICKHOUSE_USER= -CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT= +POSTGRES_PORT=5432 +EMS_PORT=5000 +MONITOR_PORT=1234 +CLICKHOUSE_DB=test_db +CLICKHOUSE_USER=test_user +CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 CLICKHOUSE_PASSWORD= \ No newline at end of file diff --git a/client/.env.example b/client/.env.example index 02bf6ee..dc03d37 100644 --- a/client/.env.example +++ b/client/.env.example @@ -1,4 +1,14 @@ +# API авторизации VITE_API_AUTH_URL= + +# API info VITE_API_INFO_URL= + +# API fuel VITE_API_FUEL_URL= -VITE_API_SERVERS_URL= \ No newline at end of file + +# API servers +VITE_API_SERVERS_URL= + +# API EMS +VITE_API_EMS_URL= \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index ec96095..a5644b0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -20,13 +20,14 @@ "@mui/material": "^5.15.20", "@mui/x-charts": "^7.8.0", "@mui/x-data-grid": "^7.7.1", + "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@^3.5.0", "@uidotdev/usehooks": "^2.4.1", "autoprefixer": "^10.4.19", "axios": "^1.7.2", "buffer": "^6.0.3", - "elysia-vite": "^0.2.0", "file-type": "^19.0.0", "ol": "^10.0.0", + "ol-ext": "^4.0.23", "postcss": "^8.4.38", "proj4": "^2.12.0", "react": "^18.2.0", @@ -1889,27 +1890,6 @@ "node": ">=6.9.0" } }, - "node_modules/@elysiajs/html": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@elysiajs/html/-/html-0.7.3.tgz", - "integrity": "sha512-F9WSfGsdym35NOz4WGXx0RbasBekJ8uW2rYufOpVyGTBLnFerGNfVZnOBAVrpsSEHeLL1O5Cxr0BCfF5tGabaA==", - "dependencies": { - "@kitajs/html": "^3.0.2", - "@kitajs/ts-html-plugin": "^1.2.0" - }, - "peerDependencies": { - "@kitajs/html": ">= 3.0.0", - "elysia": ">= 0.7.15" - }, - "peerDependenciesMeta": { - "@kitajs/html": { - "optional": true - }, - "@kitajs/ts-html-plugin": { - "optional": true - } - } - }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -2702,44 +2682,6 @@ "resolved": "https://registry.npmjs.org/@js-preview/pdf/-/pdf-2.0.2.tgz", "integrity": "sha512-g7RsK4k97y+/XvsjZfltEotty+QpfeQGDBAbr9UB0npjVP+2gxRnIhNEPpYFr44XQ6ZNjAIs5nKFppGjJVFJMQ==" }, - "node_modules/@kitajs/html": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@kitajs/html/-/html-3.1.2.tgz", - "integrity": "sha512-igMLn8VCrAyjFuK1OOsCkiiu95EQ+hK/C96moz9+MzX3lsMukZO/AqXRxdhTeB80AtE61pL+lUTuwTkqz/s+rQ==", - "dependencies": { - "csstype": "^3.1.3" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/kitajs/html?sponsor=1" - }, - "peerDependencies": { - "@kitajs/ts-html-plugin": ">=1.3.3" - } - }, - "node_modules/@kitajs/ts-html-plugin": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@kitajs/ts-html-plugin/-/ts-html-plugin-1.3.4.tgz", - "integrity": "sha512-AAht1OvLkQizJ59DM70qBgb0VwdyW9KUtDaH66JrfanMMvSSoM598WspJrVdVbe50olw69H+nnTj0lEfNDVmPQ==", - "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.2", - "yargs": "^17.7.2" - }, - "bin": { - "ts-html-plugin": "dist/cli.js", - "xss-scan": "dist/cli.js" - }, - "funding": { - "url": "https://github.com/kitajs/ts-html-plugin?sponsor=1" - }, - "peerDependencies": { - "@kitajs/html": "^3.1.1", - "typescript": "^5.2.2" - } - }, "node_modules/@mui/base": { "version": "5.0.0-beta.40", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", @@ -3488,11 +3430,6 @@ "win32" ] }, - "node_modules/@sinclair/typebox": { - "version": "0.31.28", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.28.tgz", - "integrity": "sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==" - }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -3740,6 +3677,15 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/ol-ext": { + "name": "@siedlerchr/types-ol-ext", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@siedlerchr/types-ol-ext/-/types-ol-ext-3.5.0.tgz", + "integrity": "sha512-qC6hvHtLBqHSEGxqCkHlc/e0ZhMZNy9MlcY80i6yH8cwSJydOsmVK0BaCZ4WY/s4goFwx5hoZ7CkRy/P4KHLCg==", + "peerDependencies": { + "jspdf": "^2.5.1" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -3756,6 +3702,13 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true, + "peer": true + }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", @@ -4098,6 +4051,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -4106,6 +4060,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4264,6 +4219,18 @@ "node": ">= 4.0.0" } }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "peer": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.19", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", @@ -4393,6 +4360,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4662,6 +4639,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "peer": true, + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -4791,10 +4780,38 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true, + "peer": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4901,53 +4918,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4960,6 +4930,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4970,7 +4941,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/color-parse": { "version": "2.0.2", @@ -5114,12 +5086,16 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" + "node_modules/core-js": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, "node_modules/core-js-compat": { @@ -5258,6 +5234,16 @@ "node": ">=8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, + "peer": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5605,6 +5591,13 @@ "url": "https://bevry.me/fund" } }, + "node_modules/dompurify": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz", + "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==", + "optional": true, + "peer": true + }, "node_modules/earcut": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz", @@ -5657,48 +5650,6 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/elysia": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/elysia/-/elysia-0.7.31.tgz", - "integrity": "sha512-mDqG2yYDCeX1GSbLZOU2ucdG8jO3SGApdoh3Xcc8tGUkEFvKd5W/YpjsNk8C7uLYfzgotSFmc7VbOkhmgbV4Zw==", - "dependencies": { - "@sinclair/typebox": "^0.31.17", - "cookie": "^0.6.0", - "eventemitter3": "^5.0.1", - "fast-querystring": "^1.1.2", - "memoirist": "0.1.4", - "openapi-types": "^12.1.3" - }, - "peerDependencies": { - "@sinclair/typebox": ">= 0.31.0", - "openapi-types": ">= 12.0.0", - "typescript": ">= 5.0.0" - }, - "peerDependenciesMeta": { - "@sinclair/typebox": { - "optional": true - }, - "openapi-types": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/elysia-vite": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/elysia-vite/-/elysia-vite-0.2.0.tgz", - "integrity": "sha512-8TvqbmVzrNcmj6qTsO89Z62NMduLvvtLKWg/49tixT7btqEtw9ZeMIQR6UFmXwv1jGaNnWj3HfLqH/5B9WtzuQ==", - "dependencies": { - "@elysiajs/html": "^0.7.3", - "elysia": "^0.7.17" - }, - "peerDependencies": { - "@elysiajs/html": "^0.7.0", - "elysia": "^0.7.0" - } - }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -6085,11 +6036,6 @@ "node": ">=0.10.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -6138,11 +6084,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6189,14 +6130,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fast-querystring": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", - "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", - "dependencies": { - "fast-decode-uri-component": "^1.0.1" - } - }, "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -6221,6 +6154,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "peer": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6494,14 +6433,6 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -6699,6 +6630,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -6812,6 +6744,20 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, + "peer": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -7102,6 +7048,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -7520,6 +7467,24 @@ "node": ">=0.10.0" } }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7649,11 +7614,6 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/memoirist": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/memoirist/-/memoirist-0.1.4.tgz", - "integrity": "sha512-D6GbPSqO2nUVOmm7VZjJc5tC60pkOVUPzLwkKl1vCiYP+2b1cG8N9q1O3P0JmNM68u8vsgefPbxRUCSGxSXD+g==" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8015,6 +7975,14 @@ "url": "https://opencollective.com/openlayers" } }, + "node_modules/ol-ext": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/ol-ext/-/ol-ext-4.0.23.tgz", + "integrity": "sha512-g1TBIEC9je2SoPx0qz8w4emCIRcOhz87eYxeszoLSWm6HMAbCQcymjmEIqpOHxL+R9BVKs4ar5KJShobv6510g==", + "peerDependencies": { + "ol": ">= 5.3.0" + } + }, "node_modules/on-headers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", @@ -8048,11 +8016,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8275,6 +8238,13 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "optional": true, + "peer": true + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -8637,6 +8607,16 @@ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "peer": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8944,14 +8924,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -9008,6 +8980,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -9457,6 +9439,16 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -9645,6 +9637,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -9764,6 +9757,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9782,6 +9776,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/swr": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", @@ -9894,6 +9898,16 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, + "peer": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -9996,11 +10010,6 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" - }, "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -10108,6 +10117,7 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10300,6 +10310,16 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, + "peer": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -10969,14 +10989,6 @@ "node": ">=0.4" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -10995,49 +11007,6 @@ "node": ">= 14" } }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/client/package.json b/client/package.json index c2cf18b..7f5923e 100644 --- a/client/package.json +++ b/client/package.json @@ -23,13 +23,14 @@ "@mui/material": "^5.15.20", "@mui/x-charts": "^7.8.0", "@mui/x-data-grid": "^7.7.1", + "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@^3.5.0", "@uidotdev/usehooks": "^2.4.1", "autoprefixer": "^10.4.19", "axios": "^1.7.2", "buffer": "^6.0.3", - "elysia-vite": "^0.2.0", "file-type": "^19.0.0", "ol": "^10.0.0", + "ol-ext": "^4.0.23", "postcss": "^8.4.38", "proj4": "^2.12.0", "react": "^18.2.0", diff --git a/client/src/components/map/MapComponent.tsx b/client/src/components/map/MapComponent.tsx index 7b62d69..56b56ec 100644 --- a/client/src/components/map/MapComponent.tsx +++ b/client/src/components/map/MapComponent.tsx @@ -1,21 +1,30 @@ -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import GeoJSON from 'ol/format/GeoJSON' import 'ol/ol.css' import Map from 'ol/Map' import View from 'ol/View' import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction' -import { OSM, Vector as VectorSource, XYZ } from 'ol/source' +import { ImageStatic, OSM, Vector as VectorSource, XYZ } from 'ol/source' import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer' import { Divider, IconButton, Slider, Stack, Select as MUISelect, MenuItem, Box } from '@mui/material' -import { Adjust, Api, CircleOutlined, OpenWith, RectangleOutlined, Rule, Straighten, Timeline, Undo, Warning } from '@mui/icons-material' +import { Add, Adjust, Api, CircleOutlined, OpenWith, RectangleOutlined, Straighten, Timeline, Undo, Warning } from '@mui/icons-material' import { Type } from 'ol/geom/Geometry' -import { click, noModifierKeys, shiftKeyOnly } from 'ol/events/condition' +import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition' import Feature from 'ol/Feature' import { SatelliteMapsProvider } from '../../interfaces/map' -import { containsExtent } from 'ol/extent' +import { boundingExtent, containsExtent, Extent, getBottomLeft, getBottomRight, getCenter, getHeight, getTopLeft, getTopRight, getWidth } from 'ol/extent' import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles' import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources' -import { mapCenter, mapExtent } from './MapConstants' +import { mapCenter } from './MapConstants' +import ImageLayer from 'ol/layer/Image' +import VectorImageLayer from 'ol/layer/VectorImage' +import { LineString, MultiPoint, Point, Polygon, SimpleGeometry } from 'ol/geom' +import { fromExtent } from 'ol/geom/Polygon' +import Collection from 'ol/Collection' +import { Coordinate, distance, rotate } from 'ol/coordinate' +import { Stroke, Fill, Circle as CircleStyle, Style } from 'ol/style' +import { addCoordinateTransforms, addProjection, get, getTransform, Projection, transform } from 'ol/proj' +import proj4 from 'proj4' const MapComponent = () => { const mapElement = useRef(null) @@ -42,10 +51,13 @@ const MapComponent = () => { }, })) + const overlayLayer = useRef(null) + const overlayLayerSource = useRef(new VectorSource()) + const drawingLayer = useRef(null) const drawingLayerSource = useRef(new VectorSource()) - const regionsLayer = useRef(new VectorLayer({ + const regionsLayer = useRef(new VectorImageLayer({ source: regionsLayerSource, style: regionsLayerStyle })) @@ -56,6 +68,8 @@ const MapComponent = () => { source: new OSM(), })) + const imageLayer = useRef>(new ImageLayer()) + const addInteractions = () => { if (currentTool) { draw.current = new Draw({ @@ -114,20 +128,474 @@ const MapComponent = () => { } } + const style = new Style({ + geometry: function (feature) { + const modifyGeometry = feature.get('modifyGeometry'); + return modifyGeometry ? modifyGeometry.geometry : feature.getGeometry(); + }, + fill: new Fill({ + color: 'rgba(255, 255, 255, 0.2)', + }), + stroke: new Stroke({ + color: '#ffcc33', + width: 2, + }), + image: new CircleStyle({ + radius: 7, + fill: new Fill({ + color: '#ffcc33', + }), + }), + }); + + function calculateCenter(geometry: SimpleGeometry) { + let center, coordinates, minRadius; + const type = geometry.getType(); + if (type === 'Polygon') { + let x = 0; + let y = 0; + let i = 0; + coordinates = (geometry as Polygon).getCoordinates()[0].slice(1); + coordinates.forEach(function (coordinate) { + x += coordinate[0]; + y += coordinate[1]; + i++; + }); + center = [x / i, y / i]; + } else if (type === 'LineString') { + center = (geometry as LineString).getCoordinateAt(0.5); + coordinates = geometry.getCoordinates(); + } else { + center = getCenter(geometry.getExtent()); + } + let sqDistances; + if (coordinates) { + sqDistances = coordinates.map(function (coordinate: Coordinate) { + const dx = coordinate[0] - center[0]; + const dy = coordinate[1] - center[1]; + return dx * dx + dy * dy; + }); + minRadius = Math.sqrt(Math.max.apply(Math, sqDistances)) / 3; + } else { + minRadius = + Math.max( + getWidth(geometry.getExtent()), + getHeight(geometry.getExtent()), + ) / 3; + } + return { + center: center, + coordinates: coordinates, + minRadius: minRadius, + sqDistances: sqDistances, + }; + } + + function rotateProjection(projection, angle, extent) { + function rotateCoordinate(coordinate, angle, anchor) { + var coord = rotate( + [coordinate[0] - anchor[0], coordinate[1] - anchor[1]], + angle + ); + return [coord[0] + anchor[0], coord[1] + anchor[1]]; + } + + function rotateTransform(coordinate: Coordinate) { + return rotateCoordinate(coordinate, angle, getCenter(extent)); + } + + function normalTransform(coordinate: Coordinate) { + return rotateCoordinate(coordinate, -angle, getCenter(extent)); + } + + var normalProjection = get(projection); + + var rotatedProjection = new Projection({ + code: + normalProjection.getCode() + + ":" + + angle.toString() + + ":" + + extent.toString(), + units: normalProjection.getUnits(), + extent: extent + }); + addProjection(rotatedProjection); + + addCoordinateTransforms( + "EPSG:4326", + rotatedProjection, + function (coordinate) { + return rotateTransform(transform(coordinate, "EPSG:4326", projection)); + }, + function (coordinate) { + return transform(normalTransform(coordinate), projection, "EPSG:4326"); + } + ); + + addCoordinateTransforms( + "EPSG:3857", + rotatedProjection, + function (coordinate) { + return rotateTransform(transform(coordinate, "EPSG:3857", projection)); + }, + function (coordinate) { + return transform(normalTransform(coordinate), projection, "EPSG:3857"); + } + ); + + // also set up transforms with any projections defined using proj4 + if (typeof proj4 !== "undefined") { + var projCodes = Object.keys(proj4.defs); + projCodes.forEach(function (code) { + var proj4Projection = get(code); + if (!getTransform(proj4Projection, rotatedProjection)) { + addCoordinateTransforms( + proj4Projection, + rotatedProjection, + function (coordinate) { + return rotateTransform( + transform(coordinate, proj4Projection, projection) + ); + }, + function (coordinate) { + return transform( + normalTransform(coordinate), + projection, + proj4Projection + ); + } + ); + } + }); + } + + return rotatedProjection; + } + + const handleImageDrop = useCallback((event: any) => { + event.preventDefault(); + event.stopPropagation(); + + const files = event.dataTransfer.files; + if (files.length > 0) { + const file = files[0]; + + if (file.type.startsWith('image/')) { + const reader = new FileReader(); + + reader.onload = () => { + const imageUrl = reader.result as string; + const img = new Image(); + img.src = imageUrl; + img.onload = () => { + if (map.current) { + const view = map.current.getView(); + const center = view.getCenter() || [0, 0]; + + const width = img.naturalWidth; + const height = img.naturalHeight; + const resolution = view.getResolution() || 0; + + const extent = [ + center[0] - (width * resolution) / 20, + center[1] - (height * resolution) / 20, + center[0] + (width * resolution) / 20, + center[1] + (height * resolution) / 20, + ]; + + // Create a polygon feature with the same extent as the image + const polygonFeature = new Feature({ + geometry: fromExtent(extent), + }); + + // Add the polygon feature to the drawing layer source + overlayLayerSource.current?.addFeature(polygonFeature); + + // Set up the initial image layer with the extent + const imageSource = new ImageStatic({ + url: imageUrl, + imageExtent: extent, + }); + imageLayer.current.setSource(imageSource); + + //map.current.addLayer(imageLayer.current); + + // Add interactions for translation and scaling + const translate = new Translate({ + layers: [imageLayer.current], + features: new Collection([polygonFeature]), + }); + + const defaultStyle = new Modify({ source: overlayLayerSource.current }) + .getOverlay() + .getStyleFunction(); + + const modify = new Modify({ + insertVertexCondition: never, + source: overlayLayerSource.current, + condition: function (event) { + return primaryAction(event) && !platformModifierKeyOnly(event); + }, + deleteCondition: never, + features: new Collection([polygonFeature]), + style: function (feature) { + feature.get('features').forEach(function (modifyFeature: Feature) { + const modifyGeometry = modifyFeature.get('modifyGeometry') + if (modifyGeometry) { + const point = (feature.getGeometry() as Point).getCoordinates() + let modifyPoint = modifyGeometry.point + if (!modifyPoint) { + // save the initial geometry and vertex position + modifyPoint = point; + modifyGeometry.point = modifyPoint; + modifyGeometry.geometry0 = modifyGeometry.geometry; + // get anchor and minimum radius of vertices to be used + const result = calculateCenter(modifyGeometry.geometry0); + modifyGeometry.center = result.center; + modifyGeometry.minRadius = result.minRadius; + } + const center = modifyGeometry.center; + const minRadius = modifyGeometry.minRadius; + let dx, dy; + dx = modifyPoint[0] - center[0]; + dy = modifyPoint[1] - center[1]; + const initialRadius = Math.sqrt(dx * dx + dy * dy); + if (initialRadius > minRadius) { + const initialAngle = Math.atan2(dy, dx); + dx = point[0] - center[0]; + dy = point[1] - center[1]; + const currentRadius = Math.sqrt(dx * dx + dy * dy); + if (currentRadius > 0) { + const currentAngle = Math.atan2(dy, dx); + const geometry = modifyGeometry.geometry0.clone(); + geometry.scale(currentRadius / initialRadius, undefined, center); + geometry.rotate(currentAngle - initialAngle, center); + modifyGeometry.geometry = geometry; + } + } + } + }) + const res = map?.current?.getView()?.getResolution() + if (typeof res === 'number' && feature && defaultStyle) { + return defaultStyle(feature, res) + } + } + }); + + const calculateCentroid = (bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) => { + const x = (bottomLeft[0] + topLeft[0] + topRight[0] + bottomRight[0]) / 4; + const y = (bottomLeft[1] + topLeft[1] + topRight[1] + bottomRight[1]) / 4; + return [x, y]; + } + + const calculateRotationAngle = (bottomLeft: Coordinate, bottomRight: Coordinate) => { + // Calculate the difference in x and y coordinates between bottom right and bottom left + const deltaX = bottomRight[0] - bottomLeft[0]; + const deltaY = bottomRight[1] - bottomLeft[1]; + + // Calculate the angle using atan2 + const angle = Math.atan2(deltaY, deltaX); + + return angle; + } + + const calculateExtent = (bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) => { + const width = distance(bottomLeft, bottomRight); + const height = distance(bottomLeft, topLeft); + + // Calculate the centroid of the polygon + const [centerX, centerY] = calculateCentroid(bottomLeft, topLeft, topRight, bottomRight); + + // Define the extent based on the center and dimensions + const extent = [ + centerX - width / 2, // minX + centerY - height / 2, // minY + centerX + width / 2, // maxX + centerY + height / 2 // maxY + ]; + + return extent; + } + + // Function to update the image layer with a new source when extent changes + const updateImageSource = () => { + const newExtent = polygonFeature.getGeometry()?.getExtent(); + + const bottomLeft = polygonFeature.getGeometry()?.getCoordinates()[0][0] + const topLeft = polygonFeature.getGeometry()?.getCoordinates()[0][1] + const topRight = polygonFeature.getGeometry()?.getCoordinates()[0][2] + const bottomRight = polygonFeature.getGeometry()?.getCoordinates()[0][3] + + if (newExtent && bottomLeft && bottomRight && topRight && topLeft) { + const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight) + + const newImageSource = new ImageStatic({ + url: imageUrl, + imageExtent: originalExtent, + projection: rotateProjection('EPSG:3857', -calculateRotationAngle(bottomLeft, bottomRight), originalExtent) + }); + imageLayer.current.setSource(newImageSource); + } + }; + + translate.on('translateend', updateImageSource); + //modify.on('modifyend', updateImageSource); + + modify.on('modifystart', function (event) { + event.features.forEach(function (feature) { + feature.set( + 'modifyGeometry', + { geometry: feature.getGeometry()?.clone() }, + true, + ); + }); + }); + + modify.on('modifyend', function (event) { + event.features.forEach(function (feature) { + const modifyGeometry = feature.get('modifyGeometry'); + if (modifyGeometry) { + feature.setGeometry(modifyGeometry.geometry); + feature.unset('modifyGeometry', true); + } + }) + updateImageSource() + }) + + map.current.addInteraction(translate); + map.current.addInteraction(modify); + } + }; + }; + reader.readAsDataURL(file); + } + } + }, []) + + function regionsInit() { + map.current?.on('click', function (e) { + if (selectedRegion.current !== null) { + selectedRegion.current = null + } + + if (map.current) { + map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) { + if (layer === regionsLayer.current) { + selectedRegion.current = feature as Feature + // Zoom to the selected feature + zoomToFeature(selectedRegion.current) + + return true + } else return false + }); + } + }) + + // Show current selected region + map.current?.on('pointermove', function (e) { + if (selectedRegion.current !== null) { + selectedRegion.current.setStyle(undefined) + selectedRegion.current = null + } + + if (map.current) { + map.current.forEachFeatureAtPixel(e.pixel, function (feature, layer) { + if (layer === regionsLayer.current) { + selectedRegion.current = feature as Feature + selectedRegion.current.setStyle(selectStyle) + + if (feature.get('district')) { + setStatusText(feature.get('district')) + } + + return true + } else return false + }) + } + }) + + // Hide regions layer when fully visible + map.current?.on('moveend', function () { + const viewExtent = map.current?.getView().calculateExtent(map.current.getSize()) + const features = regionsLayer.current.getSource()?.getFeatures() + + let isViewCovered = false + + features?.forEach((feature: Feature) => { + const featureExtent = feature?.getGeometry()?.getExtent() + if (viewExtent && featureExtent) { + if (containsExtent(featureExtent, viewExtent)) { + isViewCovered = true + } + } + }) + + regionsLayer.current.setVisible(!isViewCovered) + }) + } + useEffect(() => { drawingLayer.current = new VectorLayer({ source: drawingLayerSource.current, - style: drawingLayerStyle, + style: drawingLayerStyle + }) + + overlayLayer.current = new VectorLayer({ + source: overlayLayerSource.current, + style: function (feature) { + const styles = [style] + const modifyGeometry = feature.get('modifyGeometry') + const geometry = modifyGeometry ? modifyGeometry.geometry : feature.getGeometry() + const result = calculateCenter(geometry) + const center = result.center + if (center) { + styles.push( + new Style({ + geometry: new Point(center), + image: new CircleStyle({ + radius: 4, + fill: new Fill({ + color: '#ff3333' + }) + }) + }) + ) + const coordinates = result.coordinates + if (coordinates) { + const minRadius = result.minRadius + const sqDistances = result.sqDistances + const rsq = minRadius * minRadius + if (Array.isArray(sqDistances)) { + const points = coordinates.filter(function (coordinate, index) { + return sqDistances[index] > rsq + }) + styles.push( + new Style({ + geometry: new MultiPoint(points), + image: new CircleStyle({ + radius: 4, + fill: new Fill({ + color: '#33cc33' + }) + }) + }) + ) + } + } + } + return styles + }, }) map.current = new Map({ - layers: [baseLayer.current, satLayer.current, regionsLayer.current, drawingLayer.current], + layers: [baseLayer.current, satLayer.current, regionsLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current], target: mapElement.current as HTMLDivElement, view: new View({ center: mapCenter, zoom: 2, maxZoom: 21, - extent: mapExtent, + //extent: mapExtent, }), }) @@ -148,65 +616,22 @@ const MapComponent = () => { loadFeatures() - // Show current selected region - map.current.on('pointermove', function (e) { - if (selectedRegion.current !== null) { - selectedRegion.current.setStyle(undefined) - selectedRegion.current = null - } + regionsInit() - if (map.current) { - map.current.forEachFeatureAtPixel(e.pixel, function (f) { - selectedRegion.current = f as Feature - selectedRegion.current.setStyle(selectStyle) - - if (f.get('district')) { - setStatusText(f.get('district')) - } - - return true - }) - } - }) - - map.current.on('click', function (e) { - if (selectedRegion.current !== null) { - selectedRegion.current = null - } - - if (map.current) { - map.current.forEachFeatureAtPixel(e.pixel, function (f) { - selectedRegion.current = f as Feature - // Zoom to the selected feature - zoomToFeature(selectedRegion.current) - - return true - }); - } - - }) - - // Hide regions layer when fully visible - map.current.on('moveend', function () { - const viewExtent = map.current?.getView().calculateExtent(map.current.getSize()) - const features = regionsLayer.current.getSource()?.getFeatures() - - let isViewCovered = false - - features?.forEach((feature: Feature) => { - const featureExtent = feature?.getGeometry()?.getExtent() - if (viewExtent && featureExtent) { - if (containsExtent(featureExtent, viewExtent)) { - isViewCovered = true - } - } + if (mapElement.current) { + mapElement.current.addEventListener('dragover', (e) => { + e.preventDefault() }) - regionsLayer.current.setVisible(!isViewCovered) - }) + mapElement.current.addEventListener('drop', handleImageDrop) + } return () => { map?.current?.setTarget(undefined) + + if (mapElement.current) { + mapElement.current.removeEventListener('drop', handleImageDrop) + } } }, []) @@ -249,6 +674,11 @@ const MapComponent = () => { return ( + }> + + + + }> diff --git a/client/src/pages/MonitorPage.tsx b/client/src/pages/MonitorPage.tsx index 02bbb62..03e6b14 100644 --- a/client/src/pages/MonitorPage.tsx +++ b/client/src/pages/MonitorPage.tsx @@ -1,9 +1,48 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' +import { Card, Stack } from '@mui/material'; -function MonitorPage() { +function CardComponent({ + url, + is_alive +}: { url: any, is_alive: any }) { return ( -
Monitor
+ + +

{url}

+

{JSON.stringify(is_alive)}

+
+
) } -export default MonitorPage \ No newline at end of file +export default function MonitorPage() { + const [servers, setServers] = useState([]) + + useEffect(() => { + const eventSource = new EventSource(`${import.meta.env.VITE_API_MONITOR_URL}/watch`); + + eventSource.onmessage = (event) => { + const data = JSON.parse(event.data); + setServers(data) + } + + eventSource.onerror = (error) => { + console.error('Error with SSE connection:', error) + eventSource.close() + } + + return () => { + eventSource.close() + }; + }, []) + + return ( +
+ + {servers.length > 0 && servers.map((server: any) => ( + + ))} + +
+ ) +} \ No newline at end of file diff --git a/client/yarn.lock b/client/yarn.lock index c3960ab..d79b1a6 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -936,7 +936,7 @@ resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.24.7" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -977,14 +977,6 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" -"@elysiajs/html@^0.7.3": - version "0.7.3" - resolved "https://registry.npmjs.org/@elysiajs/html/-/html-0.7.3.tgz" - integrity sha512-F9WSfGsdym35NOz4WGXx0RbasBekJ8uW2rYufOpVyGTBLnFerGNfVZnOBAVrpsSEHeLL1O5Cxr0BCfF5tGabaA== - dependencies: - "@kitajs/html" "^3.0.2" - "@kitajs/ts-html-plugin" "^1.2.0" - "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz" @@ -1252,22 +1244,6 @@ resolved "https://registry.npmjs.org/@js-preview/pdf/-/pdf-2.0.2.tgz" integrity sha512-g7RsK4k97y+/XvsjZfltEotty+QpfeQGDBAbr9UB0npjVP+2gxRnIhNEPpYFr44XQ6ZNjAIs5nKFppGjJVFJMQ== -"@kitajs/html@^3.0.2", "@kitajs/html@^3.1.1": - version "3.1.2" - resolved "https://registry.npmjs.org/@kitajs/html/-/html-3.1.2.tgz" - integrity sha512-igMLn8VCrAyjFuK1OOsCkiiu95EQ+hK/C96moz9+MzX3lsMukZO/AqXRxdhTeB80AtE61pL+lUTuwTkqz/s+rQ== - dependencies: - csstype "^3.1.3" - -"@kitajs/ts-html-plugin@^1.2.0", "@kitajs/ts-html-plugin@>=1.3.3": - version "1.3.4" - resolved "https://registry.npmjs.org/@kitajs/ts-html-plugin/-/ts-html-plugin-1.3.4.tgz" - integrity sha512-AAht1OvLkQizJ59DM70qBgb0VwdyW9KUtDaH66JrfanMMvSSoM598WspJrVdVbe50olw69H+nnTj0lEfNDVmPQ== - dependencies: - chalk "^4.1.2" - tslib "^2.6.2" - yargs "^17.7.2" - "@mui/base@^5.0.0-beta.40", "@mui/base@5.0.0-beta.40": version "5.0.0-beta.40" resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz" @@ -1544,11 +1520,6 @@ resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz" integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== -"@sinclair/typebox@^0.31.17": - version "0.31.28" - resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.28.tgz" - integrity sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ== - "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" resolved "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz" @@ -1617,6 +1588,11 @@ dependencies: undici-types "~5.26.4" +"@types/ol-ext@npm:@siedlerchr/types-ol-ext@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@siedlerchr/types-ol-ext/-/types-ol-ext-3.5.0.tgz" + integrity sha512-qC6hvHtLBqHSEGxqCkHlc/e0ZhMZNy9MlcY80i6yH8cwSJydOsmVK0BaCZ4WY/s4goFwx5hoZ7CkRy/P4KHLCg== + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz" @@ -1632,6 +1608,11 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== +"@types/raf@^3.4.0": + version "3.4.3" + resolved "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz" + integrity sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw== + "@types/react-dom@^18.2.22": version "18.3.0" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz" @@ -1941,6 +1922,11 @@ at-least-node@^1.0.0: resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + autoprefixer@^10.4.19: version "10.4.19" resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz" @@ -2007,6 +1993,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" @@ -2157,6 +2148,11 @@ browserslist@^4.22.2, browserslist@^4.23.0, "browserslist@>= 4.21.0": node-releases "^2.0.14" update-browserslist-db "^1.0.16" +btoa@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz" + integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" @@ -2229,6 +2225,20 @@ caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001629: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001632.tgz" integrity sha512-udx3o7yHJfUxMLkGohMlVHCvFvWmirKh9JAH/d7WOLPetlH+LTL5cocMZ0t7oZx/mdlOWXti97xLZWc8uURRHg== +canvg@^3.0.6: + version "3.0.10" + resolved "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz" + integrity sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/raf" "^3.4.0" + core-js "^3.8.3" + raf "^3.4.1" + regenerator-runtime "^0.13.7" + rgbcolor "^1.0.1" + stackblur-canvas "^2.0.0" + svg-pathdata "^6.0.3" + chalk-template@0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz" @@ -2305,15 +2315,6 @@ clipboardy@3.0.0: execa "^5.1.1" is-wsl "^2.2.0" -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - clsx@^2.1.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" @@ -2440,11 +2441,6 @@ convert-source-map@^2.0.0: resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== - core-js-compat@^3.31.0, core-js-compat@^3.36.1: version "3.37.1" resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz" @@ -2452,6 +2448,11 @@ core-js-compat@^3.31.0, core-js-compat@^3.36.1: dependencies: browserslist "^4.23.0" +core-js@^3.6.0, core-js@^3.8.3: + version "3.38.1" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz" + integrity sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" @@ -2535,6 +2536,13 @@ crypto-random-string@^2.0.0: resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-line-break@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz" + integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w== + dependencies: + utrie "^1.0.2" + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" @@ -2753,6 +2761,11 @@ domain-browser@^4.22.0: resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz" integrity sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA== +dompurify@^2.2.0: + version "2.5.6" + resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz" + integrity sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ== + earcut@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/earcut/-/earcut-3.0.0.tgz" @@ -2788,26 +2801,6 @@ elliptic@^6.5.3, elliptic@^6.5.5: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -elysia-vite@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/elysia-vite/-/elysia-vite-0.2.0.tgz" - integrity sha512-8TvqbmVzrNcmj6qTsO89Z62NMduLvvtLKWg/49tixT7btqEtw9ZeMIQR6UFmXwv1jGaNnWj3HfLqH/5B9WtzuQ== - dependencies: - "@elysiajs/html" "^0.7.3" - elysia "^0.7.17" - -elysia@^0.7.17, "elysia@>= 0.7.15": - version "0.7.31" - resolved "https://registry.npmjs.org/elysia/-/elysia-0.7.31.tgz" - integrity sha512-mDqG2yYDCeX1GSbLZOU2ucdG8jO3SGApdoh3Xcc8tGUkEFvKd5W/YpjsNk8C7uLYfzgotSFmc7VbOkhmgbV4Zw== - dependencies: - "@sinclair/typebox" "^0.31.17" - cookie "^0.6.0" - eventemitter3 "^5.0.1" - fast-querystring "^1.1.2" - memoirist "0.1.4" - openapi-types "^12.1.3" - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" @@ -2943,7 +2936,7 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" -escalade@^3.1.1, escalade@^3.1.2: +escalade@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== @@ -3068,11 +3061,6 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - events@^3.0.0: version "3.3.0" resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" @@ -3101,11 +3089,6 @@ execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -fast-decode-uri-component@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz" - integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -3132,13 +3115,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-querystring@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz" - integrity sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg== - dependencies: - fast-decode-uri-component "^1.0.1" - fast-url-parser@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz" @@ -3153,6 +3129,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fflate@^0.4.8: + version "0.4.8" + resolved "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -3298,11 +3279,6 @@ geotiff@^2.0.7: xml-utils "^1.0.2" zstddec "^0.1.0" -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" @@ -3504,6 +3480,14 @@ hoist-non-react-statics@^3.3.1: dependencies: react-is "^16.7.0" +html2canvas@^1.0.0-rc.5: + version "1.4.1" + resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz" + integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA== + dependencies: + css-line-break "^2.1.0" + text-segmentation "^1.0.3" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz" @@ -3899,6 +3883,21 @@ jsonpointer@^5.0.0: resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +jspdf@^2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz" + integrity sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA== + dependencies: + "@babel/runtime" "^7.14.0" + atob "^2.1.2" + btoa "^1.2.1" + fflate "^0.4.8" + optionalDependencies: + canvg "^3.0.6" + core-js "^3.6.0" + dompurify "^2.2.0" + html2canvas "^1.0.0-rc.5" + keyv@^4.5.3: version "4.5.4" resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" @@ -4008,11 +4007,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -memoirist@0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/memoirist/-/memoirist-0.1.4.tgz" - integrity sha512-D6GbPSqO2nUVOmm7VZjJc5tC60pkOVUPzLwkKl1vCiYP+2b1cG8N9q1O3P0JmNM68u8vsgefPbxRUCSGxSXD+g== - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -4262,7 +4256,12 @@ object.assign@^4.1.4, object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -ol@^10.0.0: +ol-ext@^4.0.23: + version "4.0.23" + resolved "https://registry.npmjs.org/ol-ext/-/ol-ext-4.0.23.tgz" + integrity sha512-g1TBIEC9je2SoPx0qz8w4emCIRcOhz87eYxeszoLSWm6HMAbCQcymjmEIqpOHxL+R9BVKs4ar5KJShobv6510g== + +ol@^10.0.0, "ol@>= 5.3.0": version "10.0.0" resolved "https://registry.npmjs.org/ol/-/ol-10.0.0.tgz" integrity sha512-Gzfh61cQAxseCWL97VpGwbF91R2D69y3ABUewTl2H1Hjy6ipCtnoKshgO+n3WBrjsbsyS8QnkfmiJZNQGQNeOA== @@ -4293,11 +4292,6 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -openapi-types@^12.1.3: - version "12.1.3" - resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz" - integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== - optionator@^0.9.3: version "0.9.4" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" @@ -4444,6 +4438,11 @@ peek-readable@^5.0.0: resolved "https://registry.npmjs.org/peek-readable/-/peek-readable-5.1.0.tgz" integrity sha512-Tq2I+yoz6Xq3S09E2PyjzOy/oYuNg5v7wyjmrw7OQYSKc7QnDs63q4RXFXraMoI6LZyiEOJ/wDEYzGDPhWwNPA== +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" @@ -4635,6 +4634,13 @@ quickselect@^2.0.0: resolved "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz" integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" @@ -4787,6 +4793,11 @@ regenerate@^1.4.2: resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@^0.13.7: + version "0.13.11" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.14.0: version "0.14.1" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" @@ -4843,11 +4854,6 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" @@ -4884,6 +4890,11 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz" + integrity sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw== + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" @@ -5149,6 +5160,11 @@ sourcemap-codec@^1.4.8: resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +stackblur-canvas@^2.0.0: + version "2.7.0" + resolved "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz" + integrity sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ== + stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz" @@ -5190,16 +5206,7 @@ string_decoder@~1.1.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.2.3: +string-width@^4.1.0: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5358,6 +5365,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svg-pathdata@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz" + integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw== + swr@^2.2.5: version "2.2.5" resolved "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz" @@ -5419,6 +5431,13 @@ terser@^5.17.4, terser@^5.4.0: commander "^2.20.0" source-map-support "~0.5.20" +text-segmentation@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz" + integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw== + dependencies: + utrie "^1.0.2" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -5482,11 +5501,6 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -tslib@^2.6.2: - version "2.6.3" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== - tty-browserify@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz" @@ -5558,7 +5572,7 @@ typed-array-length@^1.0.6: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" -typescript@^5.2.2, "typescript@>= 5.0.0", typescript@>=4.2.0: +typescript@^5.2.2, typescript@>=4.2.0: version "5.4.5" resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== @@ -5670,6 +5684,13 @@ util@^0.12.4, util@^0.12.5: is-typed-array "^1.1.3" which-typed-array "^1.1.2" +utrie@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz" + integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw== + dependencies: + base64-arraybuffer "^1.0.2" + vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" @@ -5942,15 +5963,6 @@ workbox-window@^7.1.0, workbox-window@7.1.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" @@ -5975,11 +5987,6 @@ xtend@^4.0.2: resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" @@ -5995,24 +6002,6 @@ yaml@^2.3.4: resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz" integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" diff --git a/monitor/.env.example b/monitor/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/monitor/index.ts b/monitor/index.ts index d24aa5f..68633bb 100644 --- a/monitor/index.ts +++ b/monitor/index.ts @@ -1,5 +1,6 @@ import { serve } from 'bun'; import { Database } from 'bun:sqlite'; +import { EventEmitter } from 'events'; const db = new Database('./data/servers.db'); @@ -8,41 +9,157 @@ db.run(` CREATE TABLE IF NOT EXISTS servers ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, - ip TEXT NOT NULL, - ping_rate INTEGER NOT NULL + hostname TEXT NOT NULL, + port INTEGER NOT NULL, + ping_rate INTEGER NOT NULL, + status TEXT DEFAULT 'unknown', + monitoring INTEGER DEFAULT 1 ) `); -// Function to check server availability -async function checkServer(server: { name: string; ip: string; ping_rate: number; }) { - try { - const response = await Bun.spawn(['ping', '-c', '1', server.ip]); - if (response.exitCode === 0) { - console.log(`[${new Date().toISOString()}] ${server.name} (${server.ip}) is up.`); - } else { - console.error(`[${new Date().toISOString()}] ${server.name} (${server.ip}) is down!`); +// In-memory state to store server details +const serverStates: { + [id: number]: { + id: number; + name: string; + hostname: string; + port: number; + ping_rate: number; + status: 'up' | 'down' | 'unknown'; + monitoring: number; + } +} = {}; + +// Event emitter to handle server updates +const serverMonitor = new EventEmitter(); +const clients: Set = new Set() + +class ServerState { + id: number | undefined; + name: string | undefined; + hostname: string | undefined; + port: number | undefined; + ping_rate: number | undefined; + status: 'up' | 'down' | 'unknown' | undefined; + monitoring: number | undefined; +} + +// Function to initialize server states from the database +async function initializeServerStates() { + const servers = db.query('SELECT * FROM servers').as(ServerState).all(); + servers.forEach((server: ServerState) => { + if (server.id && server.name && server.hostname && server.port && server.ping_rate && server.status) { + serverStates[server.id] = { + id: server.id, + name: server.name, + hostname: server.hostname, + port: server.port, + ping_rate: server.ping_rate, + status: server.status, + monitoring: server.monitoring || 0 + }; } - } catch (error) { - console.error(`[${new Date().toISOString()}] Error pinging ${server.name} (${server.ip}):`, error); - } + }); } -// Function to monitor servers based on their individual ping rates -async function monitorServer(server: any) { +// Start monitoring for a specific server by its ID +async function monitorServer(serverId: number) { + if (!serverStates[serverId]) return + while (true) { - await checkServer(server); - await new Promise(resolve => setTimeout(resolve, server.ping_rate)); + if (serverStates[serverId]) { + await checkServer(serverId); + await new Promise(resolve => setTimeout(resolve, serverStates[serverId].ping_rate * 1000)); + } else { + console.log("Stopped monitoring ", serverId) + return false + } } } -// Start monitoring all servers -async function startMonitoring() { - const servers = db.query(`SELECT * FROM servers`).all(); - servers.forEach(server => monitorServer(server)); +// Function to check the server availability +async function checkServer(serverId: number) { + const server = serverStates[serverId]; + if (!server) return; + if (server.monitoring === 0) return + + try { + const checkSocket = await Bun.connect({ + hostname: server.hostname, + port: server.port, + socket: { + data(socket, data) { }, + open(socket) { + console.log(`[${new Date().toISOString()}] ${server.name} (${server.hostname}:${server.port}) is up.`); + + if (serverStates[serverId].status !== 'up') { + db.run('UPDATE servers SET status = ? WHERE id = ?', ['up', serverId]); + serverStates[serverId].status = 'up'; + notifyClients() + } + socket.end(); + }, + connectError(socket, error) { + console.error(`[${new Date().toISOString()}] ${server.name} (${server.hostname}:${server.port}) is down!`); + + if (serverStates[serverId].status !== 'down') { + db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); + serverStates[serverId].status = 'down'; + notifyClients() + } + }, + error(socket, error) { + console.error(`[${new Date().toISOString()}] Error connecting to ${server.name} (${server.hostname}:${server.port}):`, error); + + if (serverStates[serverId].status !== 'down') { + db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); + serverStates[serverId].status = 'down'; + notifyClients() + } + }, + timeout(socket) { + console.error(`[${new Date().toISOString()}] Connection to ${server.name} (${server.hostname}:${server.port}) timed out!`); + + if (serverStates[serverId].status !== 'down') { + db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); + serverStates[serverId].status = 'down'; + notifyClients() + } + } + } + }) + } catch (error) { + console.error(`[${new Date().toISOString()}] Unexpected error checking ${server.name} (${server.hostname}:${server.port}):`, error); + + if (serverStates[serverId].status !== 'down') { + db.run('UPDATE servers SET status = ? WHERE id = ?', ['down', serverId]); + serverStates[serverId].status = 'down'; + } + } } -// Start monitoring in the background -startMonitoring(); +// Notify all connected clients of server state changes +function notifyClients() { + const data = JSON.stringify(Object.values(serverStates)); + clients.forEach(client => client.enqueue(`data: ${data}\n\n`)); +} + +// Start monitoring all servers from the initial state +async function startMonitoring() { + Object.keys(serverStates).forEach(serverId => { + monitorServer(parseInt(serverId, 10)); + }) + console.log('Monitoring started for existing servers.'); +} + +// Initialize states and start monitoring +initializeServerStates().then(startMonitoring); + +// Event listener for adding new servers +serverMonitor.on('newServer', (serverId) => { + if (!serverStates[serverId]) return; + monitorServer(serverId); +}); // API Server to manage servers const server = serve({ @@ -52,46 +169,122 @@ const server = serve({ const pathname = url.pathname; const method = req.method; + if (pathname === '/watch' && method === 'GET') { + const headers = { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*' + }; + const client = new Response( + new ReadableStream({ + start(controller) { + clients.add(controller); + controller.enqueue(`data: ${JSON.stringify(Object.values(serverStates))}\n\n`) + }, + cancel(controller) { + clients.delete(controller); + } + }), + { headers } + ); + return client; + } + if (pathname === '/servers' && method === 'GET') { - const servers = db.query(`SELECT * FROM servers`).all(); - return new Response(JSON.stringify(servers), { - headers: { 'Content-Type': 'application/json' }, + return new Response(JSON.stringify(Object.values(serverStates)), { + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, + status: 200 }); } if (pathname === '/server' && method === 'POST') { const data = await req.json(); - const { name, ip, ping_rate } = data; + const { name, hostname, port, ping_rate } = data; - if (!name || !ip || !ping_rate) { + if (!name || !hostname || !port || !ping_rate) { return new Response('Missing fields', { status: 400 }); } - db.run( - `INSERT INTO servers (name, ip, ping_rate) VALUES (?, ?, ?)`, - name, ip, ping_rate - ); - return new Response('Server added', { status: 201 }); + db.run('INSERT INTO servers (name, hostname, port, ping_rate) VALUES (?, ?, ?, ?)', name, hostname, port, ping_rate); + const newServer = db.query('SELECT * FROM servers WHERE id = last_insert_rowid()').as(ServerState).get(); + + if (newServer && newServer.id && newServer.name && newServer.hostname && newServer.port && newServer.ping_rate && newServer.status && newServer.monitoring) { + serverStates[newServer.id] = { + id: newServer.id, + name: newServer.name, + hostname: newServer.hostname, + port: newServer.port, + ping_rate: newServer.ping_rate, + status: newServer.status || 'unknown', + monitoring: newServer.monitoring + }; + } + + if (newServer && newServer.id) { + serverMonitor.emit('newServer', newServer.id); + return new Response('Server added', { status: 201, headers: { 'Access-Control-Allow-Origin': '*' } }); + } } if (pathname.startsWith('/server/') && method === 'PUT') { - const id = pathname.split('/').pop(); - const data = await req.json(); - const { name, ip, ping_rate } = data; + const { searchParams } = new URL(req.url) + const id = pathname.split('/').pop() - if (!id || !name || !ip || !ping_rate) { - return new Response('Missing fields', { status: 400 }); + const name = searchParams.get('name') + const hostname = searchParams.get('hostname') + const port = searchParams.get('port') + const ping_rate = searchParams.get('ping_rate') + const status = searchParams.get('status') + const monitoring = searchParams.get('monitoring') + + if (!id) { + return new Response('Invalid ID', { status: 400, headers: { 'Access-Control-Allow-Origin': '*' } }); } + // Fetch the current state of the server + const currentServer = serverStates[parseInt(id)]; + if (!currentServer) { + return new Response('Server not found', { status: 404, headers: { 'Access-Control-Allow-Origin': '*' } }); + } + + // Prepare the updated server data + const updatedServer: any = { + name: name || currentServer.name, + hostname: hostname || currentServer.hostname, + port: port || currentServer.port, + ping_rate: ping_rate || currentServer.ping_rate, + status: status || currentServer.status, + monitoring: monitoring || currentServer.monitoring + }; + + // Update the database db.run( - `UPDATE servers SET name = ?, ip = ?, ping_rate = ? WHERE id = ?`, - name, ip, ping_rate, id + `UPDATE servers SET name = ?, hostname = ?, port = ?, ping_rate = ?, status = ?, monitoring = ? WHERE id = ?`, + updatedServer.name, updatedServer.hostname, updatedServer.port, updatedServer.ping_rate, updatedServer.status, updatedServer.monitoring, id ); - return new Response('Server updated', { status: 200 }); + + // Update the in-memory state + serverStates[parseInt(id)] = { + ...serverStates[parseInt(id)], // Retain existing fields + ...updatedServer // Apply updates + }; + + return new Response('Server updated', { status: 200, headers: { 'Access-Control-Allow-Origin': '*' } }); } - return new Response('Not Found', { status: 404 }); - }, + if (pathname.startsWith('/server/') && method === 'DELETE') { + const id = pathname.split('/').pop(); + if (id) { + db.run('DELETE FROM servers WHERE id = ?', [parseInt(id)]) + delete serverStates[parseInt(id)] + + return new Response('Server deleted', { status: 200, headers: { 'Access-Control-Allow-Origin': '*' } }) + } + } + + return new Response('Not Found', { status: 404, headers: { 'Access-Control-Allow-Origin': '*' } }); + } }); console.log(`API server and monitoring running on http://localhost:${server.port}`); diff --git a/readme.md b/readme.md index c9b227f..1374119 100644 --- a/readme.md +++ b/readme.md @@ -1,26 +1,46 @@ -Установка библиотек для питона -pip install -r requierements.txt -html с использованием vuejs -в файле /backend_fastapi/.env добавить адрес базы данных -SQL_URL = "mssql+aioodbc://username:password@host/database?driver=ODBC+Driver+17+for+SQL+Server" +[client](#client) -# Инструкция по запуску фронтенда +Рекомендуемый способ запуска всех сервисов через docker-compose.yml: +```bash +docker compose up -d --build +``` -Требуется создать `.env` в корне `frontend_reactjs` с путями до API эндпоинтов (см. в .env.example). +# client + +Переменные окружения (.env) описаны в .env.example ## Docker -1. Запустить билд контейнера: +Есть два варианта запуска в Docker: + +1. Рекомендованный способ сборки через `docker-compose.yml`, находящийся в корне репозитория: ```bash docker compose up -d --build --no-deps client ``` +2. Сборка отдельного контейнера без docker compose, из Dockerfile: + +```bash +cd client +``` +```bash +docker build -t client . +``` + +```bash +docker run -d --name client -p 5173:5173 --restart always client +``` + ## Локально -⚠ Рекомендуется Node LTS версии ^20.15.1 +⚠ Рекомендуется [Node](https://nodejs.org/en/download/package-manager) LTS версии ^20.15.1 -В `frontend_reactjs`: +```bash +cd client +``` + +В `client`: 1. Установить зависимости: ```bash npm install @@ -33,4 +53,6 @@ npm run dev или в production-ready [serve](https://www.npmjs.com/package/serve): ```bash npm serve -``` \ No newline at end of file +``` + +# monitor