alpha
This commit is contained in:
Generated
+335
-1
@@ -8,9 +8,12 @@
|
||||
"name": "seltd-web",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.6",
|
||||
"jspdf": "^2.5.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"recharts": "^2.15.1"
|
||||
"recharts": "^2.15.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
@@ -1273,6 +1276,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||
@@ -1294,6 +1304,37 @@
|
||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"bin": {
|
||||
"atob": "bin/atob.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.5.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.38",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
|
||||
@@ -1342,6 +1383,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==",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"bin": {
|
||||
"btoa": "bin/btoa.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001799",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
||||
@@ -1363,6 +1416,39 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
|
||||
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||
"license": "MIT",
|
||||
"optional": 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/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"crc-32": "~1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@@ -1372,6 +1458,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
@@ -1379,6 +1474,57 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.49.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz",
|
||||
"integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
|
||||
"integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
@@ -1540,6 +1686,13 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "2.5.9",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.9.tgz",
|
||||
"integrity": "sha512-i6mvVmWN4xo9LrhCOZrDgSs9noW6nOahbrmzjRbPF36YPyj5Ue5lgok0MHDWkG7xzpWFO2OYttXdzM7rJxHvNA==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.376",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz",
|
||||
@@ -1632,6 +1785,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
|
||||
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/frac": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
|
||||
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -1657,6 +1825,20 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
@@ -1698,6 +1880,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
|
||||
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"atob": "^2.1.2",
|
||||
"btoa": "^1.2.1",
|
||||
"fflate": "^0.8.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvg": "^3.0.6",
|
||||
"core-js": "^3.6.0",
|
||||
"dompurify": "^2.5.4",
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
@@ -1771,6 +1971,13 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1838,6 +2045,16 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
@@ -1945,6 +2162,23 @@
|
||||
"decimal.js-light": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.62.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
|
||||
@@ -2019,6 +2253,48 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"frac": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
@@ -2073,6 +2349,25 @@
|
||||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"license": "MIT",
|
||||
"optional": 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",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "36.9.2",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
|
||||
@@ -2171,6 +2466,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
|
||||
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"cfb": "~1.2.1",
|
||||
"codepage": "~1.15.0",
|
||||
"crc-32": "~1.2.1",
|
||||
"ssf": "~0.11.2",
|
||||
"wmf": "~1.0.1",
|
||||
"word": "~0.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"xlsx": "bin/xlsx.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@
|
||||
"preview": "vite preview --host 0.0.0.0 --port 4173"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.6",
|
||||
"jspdf": "^2.5.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -19,4 +20,4 @@
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+138
-13
@@ -42,6 +42,16 @@ const defaultWidgetDraft = {
|
||||
max: 100,
|
||||
};
|
||||
|
||||
const PERMISSION_OPTIONS = [
|
||||
{ id: "view_dashboard", label: "Dashboard anzeigen" },
|
||||
{ id: "view_trends", label: "Trends anzeigen" },
|
||||
{ id: "edit_aliases", label: "Aliase bearbeiten" },
|
||||
{ id: "manage_sources", label: "Quellen verwalten" },
|
||||
{ id: "manage_users", label: "Benutzer verwalten" },
|
||||
{ id: "manage_roles", label: "Rollen verwalten" },
|
||||
{ id: "view_debug", label: "Debug anzeigen" },
|
||||
];
|
||||
|
||||
const pointColors = [
|
||||
"#d62839",
|
||||
"#2563eb",
|
||||
@@ -127,6 +137,16 @@ function formatValue(value) {
|
||||
return Number.isFinite(Number(value)) ? Number(value).toFixed(2) : "--";
|
||||
}
|
||||
|
||||
function formatCollectorValue(value) {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "kein Wert";
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
return Number.isFinite(Number(value)) ? Number(value).toLocaleString("de-DE") : String(value);
|
||||
}
|
||||
|
||||
function formatTimestampLabel(timestamp) {
|
||||
return timestamp ? new Date(timestamp).toLocaleString("de-DE") : "Aktueller Wert";
|
||||
}
|
||||
@@ -471,6 +491,19 @@ function PointLibrary({ metadata, filterText, onFilterTextChange, onAddPoint })
|
||||
);
|
||||
}
|
||||
|
||||
function hasPermission(user, permission) {
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.role === "admin") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Array.isArray(user.permissions) && user.permissions.includes(permission);
|
||||
}
|
||||
|
||||
|
||||
export default function App() {
|
||||
const [token, setToken] = useState(() => localStorage.getItem("seltd-token") || "");
|
||||
const [activeView, setActiveView] = useState("dashboard");
|
||||
@@ -516,7 +549,8 @@ export default function App() {
|
||||
const [selectedSourceId, setSelectedSourceId] = useState("");
|
||||
const [isCreatingSource, setIsCreatingSource] = useState(false);
|
||||
const [sourceDraft, setSourceDraft] = useState(defaultSourceDraft);
|
||||
const [manualDatapointDraft, setManualDatapointDraft] = useState({ alias: "", address: "", pointIndex: "", unit: "°C", dataType: getDefaultPointDataType(defaultSourceDraft.protocol), kind: "analog" });
|
||||
const emptyManualDatapointDraft = (protocol = defaultSourceDraft.protocol) => ({ alias: "", address: "", pointIndex: "", unit: "°C", dataType: getDefaultPointDataType(protocol), kind: "analog", scale: "1", writeMode: "", condition: "" });
|
||||
const [manualDatapointDraft, setManualDatapointDraft] = useState(emptyManualDatapointDraft());
|
||||
const [scanResults, setScanResults] = useState({ opcua: null, bacnet: null });
|
||||
const [debugSnapshot, setDebugSnapshot] = useState(null);
|
||||
|
||||
@@ -1170,7 +1204,7 @@ export default function App() {
|
||||
const startNewSource = () => {
|
||||
setIsCreatingSource(true);
|
||||
setSelectedSourceId("");
|
||||
setManualDatapointDraft({ alias: "", address: "", pointIndex: "", unit: "°C", dataType: getDefaultPointDataType(defaultSourceDraft.protocol), kind: "analog" });
|
||||
setManualDatapointDraft(emptyManualDatapointDraft(defaultSourceDraft.protocol));
|
||||
setSourceDraft({
|
||||
...defaultSourceDraft,
|
||||
port: getDefaultProtocolPort(defaultSourceDraft.protocol),
|
||||
@@ -1283,7 +1317,7 @@ export default function App() {
|
||||
});
|
||||
await refreshCollectorData();
|
||||
await refreshCollectorDebug().catch(() => undefined);
|
||||
setManualDatapointDraft({ alias: "", address: "", pointIndex: "", unit: "°C", dataType: getDefaultPointDataType(sourceDraft.protocol), kind: "analog" });
|
||||
setManualDatapointDraft(emptyManualDatapointDraft(sourceDraft.protocol));
|
||||
setSuccessMessage("Datenpunkt angelegt.");
|
||||
} catch (error) {
|
||||
setBootError(error.message);
|
||||
@@ -1328,13 +1362,20 @@ export default function App() {
|
||||
};
|
||||
|
||||
const runOpcuaScan = async () => {
|
||||
const activeSourceId = selectedSourceId || sourceDraft.id;
|
||||
if (!activeSourceId) {
|
||||
setBootError("Bitte zuerst eine Quelle speichern oder auswählen.");
|
||||
return;
|
||||
}
|
||||
resetFlash();
|
||||
try {
|
||||
const result = await api.scanOpcua({
|
||||
host: sourceDraft.host,
|
||||
port: sourceDraft.port || "4840",
|
||||
sourceId: activeSourceId,
|
||||
});
|
||||
setScanResults((current) => ({ ...current, opcua: result }));
|
||||
await refreshCollectorData();
|
||||
await refreshCollectorDebug().catch(() => undefined);
|
||||
setSuccessMessage("OPC-UA-Scan abgeschlossen.");
|
||||
} catch (error) {
|
||||
@@ -1343,6 +1384,11 @@ export default function App() {
|
||||
};
|
||||
|
||||
const runBacnetScan = async () => {
|
||||
const activeSourceId = selectedSourceId || sourceDraft.id;
|
||||
if (!activeSourceId) {
|
||||
setBootError("Bitte zuerst eine Quelle speichern oder auswählen.");
|
||||
return;
|
||||
}
|
||||
resetFlash();
|
||||
try {
|
||||
const hosts = sourceDraft.host
|
||||
@@ -1353,8 +1399,10 @@ export default function App() {
|
||||
hosts,
|
||||
port: sourceDraft.port || "47808",
|
||||
deviceId: sourceDraft.deviceId,
|
||||
sourceId: activeSourceId,
|
||||
});
|
||||
setScanResults((current) => ({ ...current, bacnet: result }));
|
||||
await refreshCollectorData();
|
||||
await refreshCollectorDebug().catch(() => undefined);
|
||||
setSuccessMessage("BACnet-Scan abgeschlossen.");
|
||||
} catch (error) {
|
||||
@@ -1362,6 +1410,29 @@ export default function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateCollectorPoint = async (datapoint, changes) => {
|
||||
resetFlash();
|
||||
try {
|
||||
await api.updateCollectorDatapoint(datapoint.id, changes);
|
||||
await refreshCollectorData();
|
||||
await refreshCollectorDebug().catch(() => undefined);
|
||||
setSuccessMessage("Datenpunkt aktualisiert.");
|
||||
} catch (error) {
|
||||
setBootError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const readCollectorPoint = async (datapoint) => {
|
||||
resetFlash();
|
||||
try {
|
||||
await api.readCollectorDatapoint(datapoint.id);
|
||||
await refreshCollectorData();
|
||||
setSuccessMessage("Live-Wert gelesen.");
|
||||
} catch (error) {
|
||||
setBootError(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const createUser = async () => {
|
||||
resetFlash();
|
||||
try {
|
||||
@@ -2062,7 +2133,7 @@ export default function App() {
|
||||
</label>
|
||||
<label>
|
||||
<span>Wertnummer</span>
|
||||
<input type="number" min="1" max="200" value={manualDatapointDraft.pointIndex} onChange={(event) => setManualDatapointDraft((current) => ({ ...current, pointIndex: event.target.value }))} />
|
||||
<input type="number" min="1" max="2000" value={manualDatapointDraft.pointIndex} onChange={(event) => setManualDatapointDraft((current) => ({ ...current, pointIndex: event.target.value }))} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Typ</span>
|
||||
@@ -2079,6 +2150,22 @@ export default function App() {
|
||||
<span>Einheit</span>
|
||||
<UnitField value={manualDatapointDraft.unit} onChange={(nextValue) => setManualDatapointDraft((current) => ({ ...current, unit: nextValue }))} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Faktor</span>
|
||||
<input type="number" step="0.0001" value={manualDatapointDraft.scale} onChange={(event) => setManualDatapointDraft((current) => ({ ...current, scale: event.target.value }))} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Schreibmodus</span>
|
||||
<select value={manualDatapointDraft.writeMode} onChange={(event) => setManualDatapointDraft((current) => ({ ...current, writeMode: event.target.value }))}>
|
||||
<option value="">Default der Quelle</option>
|
||||
<option value="interval">Jeden Poll</option>
|
||||
<option value="cov">Nur Änderung (COV)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="span-two">
|
||||
<span>Bedingung / Notiz</span>
|
||||
<input value={manualDatapointDraft.condition} placeholder="z.B. nur wenn Anlage läuft" onChange={(event) => setManualDatapointDraft((current) => ({ ...current, condition: event.target.value }))} />
|
||||
</label>
|
||||
</div>
|
||||
<button className="ghost-button compact-button" type="button" onClick={saveManualCollectorDatapoint}>Datenpunkt anlegen</button>
|
||||
</div>
|
||||
@@ -2090,7 +2177,7 @@ export default function App() {
|
||||
<button className="ghost-button compact-button" type="button" onClick={runOpcuaScan}>Scan mit Host/Port oben</button>
|
||||
</div>
|
||||
<small className="muted">Verwendet Host und Port aus der Quelle oberhalb des Speicher-Buttons.</small>
|
||||
{scanResults.opcua ? <small className="muted">{scanResults.opcua.reachable ? String(scanResults.opcua.endpoints.length) + " Endpunkt(e) gefunden" : "Host nicht erreichbar"}</small> : null}
|
||||
{scanResults.opcua ? <small className="muted">{scanResults.opcua.reachable ? `${scanResults.opcua.nodes?.length || 0} Node(s), ${scanResults.opcua.endpoints?.length || 0} Endpunkt(e)` : "Host nicht erreichbar"}</small> : null}
|
||||
</div>
|
||||
|
||||
<div className="protocol-block">
|
||||
@@ -2099,26 +2186,64 @@ export default function App() {
|
||||
<button className="ghost-button compact-button" type="button" onClick={runBacnetScan}>Scan mit Host/Port oben</button>
|
||||
</div>
|
||||
<small className="muted">Für mehrere Hosts einfach im Feld oben Komma, Leerzeichen oder Semikolon verwenden.</small>
|
||||
{scanResults.bacnet ? <small className="muted">{scanResults.bacnet.devices?.length || 0} Gerät(e) geprüft</small> : null}
|
||||
{scanResults.bacnet ? <small className="muted">{scanResults.bacnet.devices?.length || 0} Gerät(e), {scanResults.bacnet.points?.length || 0} Objekt(e)</small> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(scanResults.opcua?.nodes?.length || scanResults.bacnet?.points?.length) ? (
|
||||
<div className="datapoint-preview">
|
||||
<div className="panel-header compact-header nested-gap">
|
||||
<div>
|
||||
<h3>Scan-Ergebnis</h3>
|
||||
<p>{scanResults.opcua?.nodes?.length || scanResults.bacnet?.points?.length || 0} gefundene Datenpunkte oder Objekte.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="scan-result-list">
|
||||
{(scanResults.opcua?.nodes || []).filter((node) => node.hasValue).slice(0, 120).map((node) => (
|
||||
<div key={node.nodeId} className="scan-result-row">
|
||||
<div>
|
||||
<strong>{node.displayName || node.browseName}</strong>
|
||||
<span>{node.path || node.nodeId}</span>
|
||||
</div>
|
||||
<span>{formatCollectorValue(node.currentValue)}</span>
|
||||
</div>
|
||||
))}
|
||||
{(scanResults.bacnet?.points || []).slice(0, 120).map((point) => (
|
||||
<div key={point.id} className="scan-result-row">
|
||||
<div>
|
||||
<strong>{point.alias || point.name}</strong>
|
||||
<span>{point.host} · {point.dataType}{point.objectInstance}</span>
|
||||
</div>
|
||||
<span>{formatCollectorValue(point.currentValue)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{collectorSourcePoints.length ? (
|
||||
<div className="datapoint-preview">
|
||||
<div className="panel-header compact-header nested-gap">
|
||||
<div>
|
||||
<h3>Datenpunkte</h3>
|
||||
<p>{collectorSourcePoints.length} importierte oder erkannte Punkte.</p>
|
||||
<p>{collectorSourcePoints.length} importierte, gescannte oder manuelle Punkte. Aktivierte Punkte werden geloggt.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={collectorSourcePoints.length <= 2 ? "selection-list compact" : "selection-list"}>
|
||||
{collectorSourcePoints.slice(0, 24).map((item) => (
|
||||
<div key={item.id} className="selection-card source-card preview-card">
|
||||
<div>
|
||||
<div className="datapoint-table">
|
||||
{collectorSourcePoints.slice(0, 2000).map((item) => (
|
||||
<div key={item.id} className={item.enabled === false ? "datapoint-row inactive" : "datapoint-row active"}>
|
||||
<div className="datapoint-main">
|
||||
<strong>{item.alias || item.name}</strong>
|
||||
<span>{item.address || item.nodeId || item.dataType || item.protocol}</span>
|
||||
<span>Wert {item.pointIndex} · {item.address || item.nodeId || item.dataType || item.protocol} · {item.writeMode === "cov" ? "COV" : "Intervall"}</span>
|
||||
{item.condition ? <small>{item.condition}</small> : null}
|
||||
</div>
|
||||
<span className="datapoint-value">{formatCollectorValue(item.currentValue)}{item.unit ? " " + item.unit : ""}</span>
|
||||
<div className="button-row wrap-tight">
|
||||
<button className="ghost-button compact-button" type="button" onClick={() => readCollectorPoint(item)}>Wert lesen</button>
|
||||
<button className={item.enabled === false ? "primary-button compact-button" : "ghost-button compact-button"} type="button" onClick={() => updateCollectorPoint(item, { enabled: item.enabled === false })}>
|
||||
{item.enabled === false ? "Logging an" : "Logging aus"}
|
||||
</button>
|
||||
</div>
|
||||
<span>{item.unit || "°C"}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -83,6 +83,8 @@ export const api = {
|
||||
updateCollectorSource: (id, body) => collectorRequest(`/sources/${id}`, { method: "PUT", body }),
|
||||
deleteCollectorSource: (id) => collectorRequest(`/sources/${id}`, { method: "DELETE" }),
|
||||
createCollectorDatapoint: (body) => collectorRequest("/datapoints/manual", { method: "POST", body }),
|
||||
updateCollectorDatapoint: (id, body) => collectorRequest(`/datapoints/${encodeURIComponent(id)}`, { method: "PATCH", body }),
|
||||
readCollectorDatapoint: (id) => collectorRequest(`/datapoints/${encodeURIComponent(id)}/read`, { method: "POST" }),
|
||||
getCollectorDebug: () => collectorRequest("/debug"),
|
||||
importModbusCsv: (body) => collectorRequest("/imports/modbus-csv", { method: "POST", body }),
|
||||
importKnxXml: (body) => collectorRequest("/imports/knx-xml", { method: "POST", body }),
|
||||
|
||||
@@ -873,3 +873,49 @@ span {
|
||||
|
||||
|
||||
|
||||
|
||||
.scan-result-list,
|
||||
.datapoint-table {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
max-height: 420px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.scan-result-row,
|
||||
.datapoint-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 0.45rem 0.55rem;
|
||||
background: color-mix(in srgb, var(--surface) 92%, transparent);
|
||||
}
|
||||
|
||||
.datapoint-row.inactive {
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.datapoint-main {
|
||||
display: grid;
|
||||
gap: 0.12rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.datapoint-main span,
|
||||
.scan-result-row span,
|
||||
.datapoint-main small {
|
||||
color: var(--muted);
|
||||
font-size: 0.72rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.datapoint-value {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user