diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index 1e02aba..244f087 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -31,7 +31,8 @@ func (r *clusterResolver) Partitions(ctx context.Context, obj *schema.Cluster) ( // StartTime is the resolver for the startTime field. func (r *jobResolver) StartTime(ctx context.Context, obj *schema.Job) (*time.Time, error) { - panic(fmt.Errorf("not implemented: StartTime - startTime")) + timestamp := time.Unix(obj.StartTime, 0) + return ×tamp, nil } // Tags is the resolver for the tags field. diff --git a/internal/metricdata/cc-metric-store.go b/internal/metricdata/cc-metric-store.go index 557e1d2..b76ed5b 100644 --- a/internal/metricdata/cc-metric-store.go +++ b/internal/metricdata/cc-metric-store.go @@ -850,7 +850,7 @@ func (ccms *CCMetricStore) LoadNodeListData( if len(nodes) > page.ItemsPerPage { start := (page.Page - 1) * page.ItemsPerPage end := start + page.ItemsPerPage - if end > len(nodes) { + if end >= len(nodes) { end = len(nodes) hasNextPage = false } else { diff --git a/internal/metricdata/prometheus.go b/internal/metricdata/prometheus.go index fa49764..e0add3a 100644 --- a/internal/metricdata/prometheus.go +++ b/internal/metricdata/prometheus.go @@ -539,7 +539,7 @@ func (pdb *PrometheusDataRepository) LoadNodeListData( if len(nodes) > page.ItemsPerPage { start := (page.Page - 1) * page.ItemsPerPage end := start + page.ItemsPerPage - if end > len(nodes) { + if end >= len(nodes) { end = len(nodes) hasNextPage = false } else { diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index 5386553..32e1c15 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -161,7 +161,7 @@ func setupNodeRoute(i InfoType, r *http.Request) InfoType { i["hostname"] = vars["hostname"] i["id"] = fmt.Sprintf("%s (%s)", vars["cluster"], vars["hostname"]) from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to") - if from != "" || to != "" { + if from != "" && to != "" { i["from"] = from i["to"] = to } diff --git a/web/frontend/package-lock.json b/web/frontend/package-lock.json index 0b31f40..51e358d 100644 --- a/web/frontend/package-lock.json +++ b/web/frontend/package-lock.json @@ -9,32 +9,31 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@sveltestrap/sveltestrap": "^6.2.7", - "@urql/svelte": "^4.2.2", - "chart.js": "^4.4.6", - "date-fns": "^2.30.0", - "graphql": "^16.9.0", - "mathjs": "^12.4.3", - "svelte-chartjs": "^3.1.5", - "uplot": "^1.6.31", - "wonka": "^6.3.4" + "@rollup/plugin-replace": "^6.0.2", + "@sveltestrap/sveltestrap": "^7.1.0", + "@urql/svelte": "^4.2.3", + "chart.js": "^4.4.9", + "date-fns": "^4.1.0", + "graphql": "^16.11.0", + "mathjs": "^14.5.2", + "uplot": "^1.6.32", + "wonka": "^6.3.5" }, "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.8", - "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-commonjs": "^28.0.3", + "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", "@timohausmann/quadtree-js": "^1.2.6", - "rollup": "^4.27.4", + "rollup": "^4.41.1", "rollup-plugin-css-only": "^4.5.2", "rollup-plugin-svelte": "^7.2.2", - "svelte": "^4.2.19" + "svelte": "^5.33.14" } }, "node_modules/@0no-co/graphql.web": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.0.11.tgz", - "integrity": "sha512-xuSJ9WXwTmtngWkbdEoopMo6F8NLtjy84UNAMsAr5C3/2SgAL/dEU10TMqTIsipqPQ8HA/7WzeqQ9DEQxSvPPA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.1.2.tgz", + "integrity": "sha512-N2NGsU5FLBhT8NZ+3l2YrzZSHITjNXNuDhC4iDiikv0IujaJ0Xc6xIxQZ/Ek3Cb+rgPjnLHYyJm11tInuJn+cw==", "license": "MIT", "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" @@ -59,21 +58,18 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -146,21 +142,22 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.8", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", - "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==", + "version": "28.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", + "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", "dev": true, "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", - "glob": "^8.0.3", + "fdir": "^6.2.0", "is-reference": "1.2.1", - "magic-string": "^0.30.3" + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0 || 14 >= 14.17" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" @@ -172,9 +169,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", - "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", "dev": true, "license": "MIT", "dependencies": { @@ -197,9 +194,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", - "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -241,9 +238,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -263,9 +260,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", - "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", + "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", "cpu": [ "arm" ], @@ -277,9 +274,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", - "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", + "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", "cpu": [ "arm64" ], @@ -291,9 +288,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", - "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", + "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", "cpu": [ "arm64" ], @@ -305,9 +302,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", - "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", + "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", "cpu": [ "x64" ], @@ -319,9 +316,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", - "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", + "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", "cpu": [ "arm64" ], @@ -333,9 +330,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", - "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", + "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", "cpu": [ "x64" ], @@ -347,9 +344,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", - "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", + "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", "cpu": [ "arm" ], @@ -361,9 +358,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", - "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", + "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", "cpu": [ "arm" ], @@ -375,9 +372,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", - "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", + "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", "cpu": [ "arm64" ], @@ -389,9 +386,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", - "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", + "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", "cpu": [ "arm64" ], @@ -402,10 +399,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", + "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", - "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", + "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", "cpu": [ "ppc64" ], @@ -417,9 +428,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", - "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", + "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", + "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", "cpu": [ "riscv64" ], @@ -431,9 +456,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", - "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", + "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", "cpu": [ "s390x" ], @@ -445,9 +470,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", - "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", + "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", "cpu": [ "x64" ], @@ -459,9 +484,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", - "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", + "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", "cpu": [ "x64" ], @@ -473,9 +498,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", - "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", + "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", "cpu": [ "arm64" ], @@ -487,9 +512,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", - "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", + "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", "cpu": [ "ia32" ], @@ -501,9 +526,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", - "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", + "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", "cpu": [ "x64" ], @@ -514,10 +539,19 @@ "win32" ] }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, "node_modules/@sveltestrap/sveltestrap": { - "version": "6.2.7", - "resolved": "https://registry.npmjs.org/@sveltestrap/sveltestrap/-/sveltestrap-6.2.7.tgz", - "integrity": "sha512-WwLLfAFUb42BGuRrf3Vbct30bQMzlEMMipN/MfxhjuLTmLQeW9muVJfPyvjtWS+mY+RjkSCoHvAp/ZobP1NLlQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@sveltestrap/sveltestrap/-/sveltestrap-7.1.0.tgz", + "integrity": "sha512-TpIx25kqLV+z+VD3yfqYayOI1IaCeWFbT0uqM6NfA4vQgDs9PjFwmjkU4YEAlV/ngs9e7xPmaRWE7lkrg4Miow==", "license": "MIT", "dependencies": { "@popperjs/core": "^2.11.8" @@ -534,9 +568,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/resolve": { @@ -547,9 +581,9 @@ "license": "MIT" }, "node_modules/@urql/core": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.0.8.tgz", - "integrity": "sha512-1GOnUw7/a9bzkcM0+U8U5MmxW2A7FE5YquuEmcJzTtW5tIs2EoS4F2ITpuKBjRBbyRjZgO860nWFPo1m4JImGA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.1.1.tgz", + "integrity": "sha512-aGh024z5v2oINGD/In6rAtVKTm4VmQ2TxKQBAtk2ZSME5dunZFcjltw4p5ENQg+5CBhZ3FHMzl0Oa+rwqiWqlg==", "license": "MIT", "dependencies": { "@0no-co/graphql.web": "^1.0.5", @@ -557,12 +591,12 @@ } }, "node_modules/@urql/svelte": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-4.2.2.tgz", - "integrity": "sha512-6ntLGsWcnNtaMZVmFpePfFTSpYxYpznCAqnuvLDjt7Oa7YqHcFiyPnz7IIsiPD9VE6hZSi0+RwmRk5BMba/teQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-4.2.3.tgz", + "integrity": "sha512-v3eArfymhdjaM5VQFp3QZxq9veYPadmDfX7ueid/kD4DlRplIycPakJ2FrKigh46SXa5mWqJ3QWuWyRKVu61sw==", "license": "MIT", "dependencies": { - "@urql/core": "^5.0.0", + "@urql/core": "^5.1.1", "wonka": "^6.3.2" }, "peerDependencies": { @@ -571,9 +605,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -600,23 +634,6 @@ "node": ">= 0.4" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -625,9 +642,9 @@ "license": "MIT" }, "node_modules/chart.js": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz", - "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -636,26 +653,13 @@ "pnpm": ">=8" } }, - "node_modules/code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" - } - }, - "node_modules/code-red/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "engines": { + "node": ">=6" } }, "node_modules/commander": { @@ -685,39 +689,20 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", "license": "MIT" }, "node_modules/deepmerge": { @@ -736,31 +721,54 @@ "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==", "license": "MIT" }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.7.tgz", + "integrity": "sha512-0ZxW6guTF/AeKeKi7he93lmgv7Hx7giD1tBrOeVqkqsZGQJd2/kfnL7LdIsr9FT/AtkBK9XeDTov+gxprBqdEg==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, - "node_modules/fraction.js": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", - "integrity": "sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==", + "node_modules/fdir": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "dev": true, "license": "MIT", - "engines": { - "node": "*" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" + "node_modules/fraction.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.2.2.tgz", + "integrity": "sha512-uXBDv5knpYmv/2gLzWQ5mBHGBRk9wcKTeWu6GLTUEQfjCxO09uM/mHDrojlL+Q1mVGIIFo149Gba7od1XPgSzQ==", + "license": "MIT", + "engines": { + "node": ">= 12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } }, "node_modules/fsevents": { "version": "2.3.3", @@ -787,31 +795,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/graphql": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", - "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -830,29 +817,10 @@ "node": ">= 0.4" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -895,29 +863,29 @@ "license": "MIT" }, "node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/mathjs": { - "version": "12.4.3", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-12.4.3.tgz", - "integrity": "sha512-oHdGPDbp7gO873xxG90RLq36IuicuKvbpr/bBG5g9c8Obm/VsKVrK9uoRZZHUodohzlnmCEqfDzbR3LH6m+aAQ==", + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.5.2.tgz", + "integrity": "sha512-51U6hp7j4M4Rj+l+q2KbmXAV9EhQVQzUdw1wE67RnUkKKq5ibxdrl9Ky2YkSUEIc2+VU8/IsThZNu6QSHUoyTA==", "license": "Apache-2.0", "dependencies": { - "@babel/runtime": "^7.24.4", - "complex.js": "^2.1.1", + "@babel/runtime": "^7.26.10", + "complex.js": "^2.2.5", "decimal.js": "^10.4.3", "escape-latex": "^1.2.0", - "fraction.js": "4.3.4", + "fraction.js": "^5.2.1", "javascript-natural-sort": "^0.7.1", "seedrandom": "^3.0.5", "tiny-emitter": "^2.1.0", - "typed-function": "^4.1.1" + "typed-function": "^4.2.1" }, "bin": { "mathjs": "bin/cli.js" @@ -926,35 +894,6 @@ "node": ">= 18" } }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0" - }, - "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -962,35 +901,6 @@ "dev": true, "license": "MIT" }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, - "node_modules/periscopic/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/periscopic/node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, "node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", @@ -1013,34 +923,31 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", "dev": true, "license": "MIT", "engines": { @@ -1048,13 +955,13 @@ } }, "node_modules/rollup": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", - "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", + "version": "4.41.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", + "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", "devOptional": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -1064,24 +971,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.27.4", - "@rollup/rollup-android-arm64": "4.27.4", - "@rollup/rollup-darwin-arm64": "4.27.4", - "@rollup/rollup-darwin-x64": "4.27.4", - "@rollup/rollup-freebsd-arm64": "4.27.4", - "@rollup/rollup-freebsd-x64": "4.27.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", - "@rollup/rollup-linux-arm-musleabihf": "4.27.4", - "@rollup/rollup-linux-arm64-gnu": "4.27.4", - "@rollup/rollup-linux-arm64-musl": "4.27.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", - "@rollup/rollup-linux-riscv64-gnu": "4.27.4", - "@rollup/rollup-linux-s390x-gnu": "4.27.4", - "@rollup/rollup-linux-x64-gnu": "4.27.4", - "@rollup/rollup-linux-x64-musl": "4.27.4", - "@rollup/rollup-win32-arm64-msvc": "4.27.4", - "@rollup/rollup-win32-ia32-msvc": "4.27.4", - "@rollup/rollup-win32-x64-msvc": "4.27.4", + "@rollup/rollup-android-arm-eabi": "4.41.1", + "@rollup/rollup-android-arm64": "4.41.1", + "@rollup/rollup-darwin-arm64": "4.41.1", + "@rollup/rollup-darwin-x64": "4.41.1", + "@rollup/rollup-freebsd-arm64": "4.41.1", + "@rollup/rollup-freebsd-x64": "4.41.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", + "@rollup/rollup-linux-arm-musleabihf": "4.41.1", + "@rollup/rollup-linux-arm64-gnu": "4.41.1", + "@rollup/rollup-linux-arm64-musl": "4.41.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-gnu": "4.41.1", + "@rollup/rollup-linux-riscv64-musl": "4.41.1", + "@rollup/rollup-linux-s390x-gnu": "4.41.1", + "@rollup/rollup-linux-x64-gnu": "4.41.1", + "@rollup/rollup-linux-x64-musl": "4.41.1", + "@rollup/rollup-win32-arm64-msvc": "4.41.1", + "@rollup/rollup-win32-ia32-msvc": "4.41.1", + "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" } }, @@ -1146,6 +1055,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1200,15 +1116,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -1234,47 +1141,28 @@ } }, "node_modules/svelte": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", - "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", + "version": "5.33.14", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.14.tgz", + "integrity": "sha512-kRlbhIlMTijbFmVDQFDeKXPLlX1/ovXwV0I162wRqQhRcygaqDIcu1d/Ese3H2uI+yt3uT8E7ndgDthQv5v5BA==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/estree": "^1.0.1", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^4.0.0", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^1.4.6", + "is-reference": "^3.0.3", "locate-character": "^3.0.0", - "magic-string": "^0.30.4", - "periscopic": "^3.1.0" + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" }, "engines": { - "node": ">=16" - } - }, - "node_modules/svelte-chartjs": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/svelte-chartjs/-/svelte-chartjs-3.1.5.tgz", - "integrity": "sha512-ka2zh7v5FiwfAX1oMflZ0HkNkgjHjFqANgRyC+vNYXfxtx2ku68Zo+2KgbKeBH2nS1ThDqkIACPzGxy4T0UaoA==", - "license": "MIT", - "peerDependencies": { - "chart.js": "^3.5.0 || ^4.0.0", - "svelte": "^4.0.0" - } - }, - "node_modules/svelte/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "node": ">=18" } }, "node_modules/svelte/node_modules/is-reference": { @@ -1287,14 +1175,14 @@ } }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.41.0.tgz", + "integrity": "sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -1321,23 +1209,22 @@ } }, "node_modules/uplot": { - "version": "1.6.31", - "resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.31.tgz", - "integrity": "sha512-sQZqSwVCbJGnFB4IQjQYopzj5CoTZJ4Br1fG/xdONimqgHmsacvCjNesdGDypNKFbrhLGIeshYhy89FxPF+H+w==", + "version": "1.6.32", + "resolved": "https://registry.npmjs.org/uplot/-/uplot-1.6.32.tgz", + "integrity": "sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw==", "license": "MIT" }, "node_modules/wonka": { - "version": "6.3.4", - "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.4.tgz", - "integrity": "sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz", + "integrity": "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==", "license": "MIT" }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "license": "MIT" } } } diff --git a/web/frontend/package.json b/web/frontend/package.json index 389ffe6..b10e271 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -7,25 +7,24 @@ "dev": "rollup -c -w" }, "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.8", - "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-commonjs": "^28.0.3", + "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-terser": "^0.4.4", "@timohausmann/quadtree-js": "^1.2.6", - "rollup": "^4.27.4", + "rollup": "^4.41.1", "rollup-plugin-css-only": "^4.5.2", "rollup-plugin-svelte": "^7.2.2", - "svelte": "^4.2.19" + "svelte": "^5.33.14" }, "dependencies": { - "@rollup/plugin-replace": "^5.0.7", - "@sveltestrap/sveltestrap": "^6.2.7", - "@urql/svelte": "^4.2.2", - "chart.js": "^4.4.6", - "date-fns": "^2.30.0", - "graphql": "^16.9.0", - "mathjs": "^12.4.3", - "svelte-chartjs": "^3.1.5", - "uplot": "^1.6.31", - "wonka": "^6.3.4" + "@rollup/plugin-replace": "^6.0.2", + "@sveltestrap/sveltestrap": "^7.1.0", + "@urql/svelte": "^4.2.3", + "chart.js": "^4.4.9", + "date-fns": "^4.1.0", + "graphql": "^16.11.0", + "mathjs": "^14.5.2", + "uplot": "^1.6.32", + "wonka": "^6.3.5" } } diff --git a/web/frontend/rollup.config.mjs b/web/frontend/rollup.config.mjs index 0e15105..a7b4732 100644 --- a/web/frontend/rollup.config.mjs +++ b/web/frontend/rollup.config.mjs @@ -6,13 +6,20 @@ import terser from '@rollup/plugin-terser'; import css from 'rollup-plugin-css-only'; const production = !process.env.ROLLUP_WATCH; -// const production = false const plugins = [ svelte({ compilerOptions: { - // enable run-time checks when not in production - dev: !production + // Enable run-time checks when not in production + dev: !production, + // Enable Svelte 5-specific features + hydratable: true, // If using server-side rendering + immutable: true, // Optimize updates for immutable data + // As of sveltestrap 7.1.0, filtered warnings would appear for imported sveltestrap components + warningFilter: (warning) => ( + warning.code !== 'element_invalid_self_closing_tag' && + warning.code !== 'a11y_interactive_supports_focus' + ) } }), @@ -23,7 +30,7 @@ const plugins = [ // https://github.com/rollup/plugins/tree/master/packages/commonjs resolve({ browser: true, - dedupe: ['svelte'] + dedupe: ['svelte', '@sveltejs/kit'] // Ensure deduplication for Svelte 5 }), commonjs(), @@ -32,8 +39,10 @@ const plugins = [ production && terser(), replace({ - "process.env.NODE_ENV": JSON.stringify("development"), - preventAssignment: true + preventAssignment: true, + values: { + "process.env.NODE_ENV": JSON.stringify(production ? "production" : "development"), + } }) ]; diff --git a/web/frontend/src/Analysis.root.svelte b/web/frontend/src/Analysis.root.svelte index 8b327a2..83c6754 100644 --- a/web/frontend/src/Analysis.root.svelte +++ b/web/frontend/src/Analysis.root.svelte @@ -37,14 +37,12 @@ import ScatterPlot from "./generic/plots/Scatter.svelte"; import RooflineHeatmap from "./generic/plots/RooflineHeatmap.svelte"; - const { query: initq } = init(); - - export let filterPresets; + /* Svelte 5 Props */ + let { filterPresets } = $props(); // By default, look at the jobs of the last 6 hours: if (filterPresets?.startTime == null) { if (filterPresets == null) filterPresets = {}; - let now = new Date(Date.now()); let hourAgo = new Date(now); hourAgo.setHours(hourAgo.getHours() - 6); @@ -54,27 +52,10 @@ }; } - let cluster; - let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the - let jobFilters = []; - let rooflineMaxY; - let colWidth1, colWidth2; - let numBins = 50; - let maxY = -1; - - const initialized = getContext("initialized"); - const globalMetrics = getContext("globalMetrics"); + /* Const Init */ + const { query: initq } = init(); + const client = getContextClient(); const ccconfig = getContext("cc-config"); - - let metricsInHistograms = ccconfig.analysis_view_histogramMetrics, - metricsInScatterplots = ccconfig.analysis_view_scatterPlotMetrics; - - $: metrics = [ - ...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]), - ]; - - $: clusterName = cluster?.name ? cluster.name : cluster; - const sortOptions = [ { key: "totalWalltime", label: "Walltime" }, { key: "totalNodeHours", label: "Node Hours" }, @@ -86,7 +67,22 @@ { key: "project", label: "Project ID" }, ]; - let sortSelection = + /* Var Init */ + let metricUnits = {}; + let metricScopes = {}; + let numBins = 50; + + /* State Init */ + let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the + let cluster = $state(filterPresets?.cluster); + let rooflineMaxY = $state(0); + let maxY = $state(-1); + let colWidth1 = $state(0); + let colWidth2 = $state(0); + let jobFilters = $state([]); + let metricsInHistograms = $state(ccconfig.analysis_view_histogramMetrics) + let metricsInScatterplots = $state(ccconfig.analysis_view_scatterPlotMetrics) + let sortSelection = $state( sortOptions.find( (option) => option.key == @@ -94,17 +90,20 @@ ) || sortOptions.find( (option) => option.key == ccconfig.analysis_view_selectedTopCategory, - ); - let groupSelection = - groupOptions.find( - (option) => - option.key == - ccconfig[`analysis_view_selectedTopEntity:${filterPresets.cluster}`], - ) || - groupOptions.find( - (option) => option.key == ccconfig.analysis_view_selectedTopEntity, - ); + ) + ); + let groupSelection = $state( + groupOptions.find( + (option) => + option.key == + ccconfig[`analysis_view_selectedTopEntity:${filterPresets.cluster}`], + ) || + groupOptions.find( + (option) => option.key == ccconfig.analysis_view_selectedTopEntity, + ) + ); + /* Init Function */ getContext("on-init")(({ data }) => { if (data != null) { cluster = data.clusters.find((c) => c.name == filterPresets.cluster); @@ -121,120 +120,145 @@ } }); - const client = getContextClient(); + /* Derived Vars */ + const clusterName = $derived(cluster?.name ? cluster.name : cluster); + const availableMetrics = $derived(loadAvailable($initq?.data?.globalMetrics, clusterName)); + const metrics = $derived( + [...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()])] + ); - $: statsQuery = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!) { - stats: jobsStatistics(filter: $jobFilters) { - totalJobs - shortJobs - totalWalltime - totalNodeHours - totalCoreHours - totalAccHours - histDuration { - count - value - } - histNumCores { - count - value + let statsQuery = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!) { + stats: jobsStatistics(filter: $jobFilters) { + totalJobs + shortJobs + totalWalltime + totalNodeHours + totalCoreHours + totalAccHours + histDuration { + count + value + } + histNumCores { + count + value + } } } - } - `, - variables: { jobFilters }, - }); + `, + variables: { jobFilters }, + }) + ); - $: topQuery = queryStore({ - client: client, - query: gql` - query ( - $jobFilters: [JobFilter!]! - $paging: PageRequest! - $sortBy: SortByAggregate! - $groupBy: Aggregate! - ) { - topList: jobsStatistics( - filter: $jobFilters - page: $paging - sortBy: $sortBy - groupBy: $groupBy + let topQuery = $derived( + queryStore({ + client: client, + query: gql` + query ( + $jobFilters: [JobFilter!]! + $paging: PageRequest! + $sortBy: SortByAggregate! + $groupBy: Aggregate! ) { - id - name - totalWalltime - totalNodeHours - totalCoreHours - totalAccHours + topList: jobsStatistics( + filter: $jobFilters + page: $paging + sortBy: $sortBy + groupBy: $groupBy + ) { + id + name + totalWalltime + totalNodeHours + totalCoreHours + totalAccHours + } } - } - `, - variables: { - jobFilters, - paging: { itemsPerPage: 10, page: 1 }, - sortBy: sortSelection.key.toUpperCase(), - groupBy: groupSelection.key.toUpperCase(), - }, - }); + `, + variables: { + jobFilters, + paging: { itemsPerPage: 10, page: 1 }, + sortBy: sortSelection.key.toUpperCase(), + groupBy: groupSelection.key.toUpperCase(), + }, + }) + ); // Note: Different footprints than those saved in DB per Job -> Caused by Legacy Naming - $: footprintsQuery = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) { - footprints: jobsFootprints(filter: $jobFilters, metrics: $metrics) { - timeWeights { - nodeHours - accHours - coreHours - } - metrics { - metric - data + let footprintsQuery = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) { + footprints: jobsFootprints(filter: $jobFilters, metrics: $metrics) { + timeWeights { + nodeHours + accHours + coreHours + } + metrics { + metric + data + } } } - } - `, - variables: { jobFilters, metrics }, - }); - - $: rooflineQuery = queryStore({ - client: client, - query: gql` - query ( - $jobFilters: [JobFilter!]! - $rows: Int! - $cols: Int! - $minX: Float! - $minY: Float! - $maxX: Float! - $maxY: Float! - ) { - rooflineHeatmap( - filter: $jobFilters - rows: $rows - cols: $cols - minX: $minX - minY: $minY - maxX: $maxX - maxY: $maxY - ) - } - `, - variables: { - jobFilters, - rows: 50, - cols: 50, - minX: 0.01, - minY: 1, - maxX: 1000, - maxY, - }, + `, + variables: { jobFilters, metrics }, + }) + ); + + let rooflineQuery = $derived( + queryStore({ + client: client, + query: gql` + query ( + $jobFilters: [JobFilter!]! + $rows: Int! + $cols: Int! + $minX: Float! + $minY: Float! + $maxX: Float! + $maxY: Float! + ) { + rooflineHeatmap( + filter: $jobFilters + rows: $rows + cols: $cols + minX: $minX + minY: $minY + maxX: $maxX + maxY: $maxY + ) + } + `, + variables: { + jobFilters, + rows: 50, + cols: 50, + minX: 0.01, + minY: 1, + maxX: 1000, + maxY, + }, + }) + ); + + /* Reactive Effects */ + $effect(() => { + loadUnitsAndScopes(availableMetrics.length, availableMetrics); + }); + $effect(() => { + updateEntityConfiguration(groupSelection.key); + }); + $effect(() => { + updateCategoryConfiguration(sortSelection.key); }); + /* Functions */ const updateConfigurationMutation = ({ name, value }) => { return mutationStore({ client: client, @@ -287,22 +311,26 @@ } } - let availableMetrics = []; - let metricUnits = {}; - let metricScopes = {}; - function loadMetrics(isInitialized) { - if (!isInitialized) return - availableMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster.name))] - for (let sm of availableMetrics) { - metricUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "") - metricScopes[sm.name] = sm?.scope + function loadAvailable(globals, name) { + const availableMetrics = new Set(); + if (globals && globals.length > 0) { + for (let gm of globals) { + if (gm.availability.find((av) => av.cluster == name)) { + availableMetrics.add({name: gm.name, scope: gm.scope, unit: gm.unit}); + }; + } + } + return [...availableMetrics] + }; + + function loadUnitsAndScopes(length, available) { + for (let am of available) { + metricUnits[am.name] = (am?.unit?.prefix ? am.unit.prefix : "") + (am?.unit?.base ? am.unit.base : "") + metricScopes[am.name] = am?.scope } } - $: loadMetrics($initialized) - $: updateEntityConfiguration(groupSelection.key); - $: updateCategoryConfiguration(sortSelection.key); - + /* On Mount */ onMount(() => filterComponent.updateFilters()); @@ -329,7 +357,7 @@ {filterPresets} disableClusterSelection={true} startTimeQuickSelect={true} - on:update-filters={({ detail }) => { + applyFilters={(detail) => { jobFilters = detail.filters; }} /> @@ -392,6 +420,7 @@ {$topQuery.error.message} {:else} {#if isAdmin} - + Admin Options @@ -31,7 +34,7 @@ {/if} {#if isSupport || isAdmin} - + Support Options diff --git a/web/frontend/src/Header.svelte b/web/frontend/src/Header.svelte index cf3e058..2473542 100644 --- a/web/frontend/src/Header.svelte +++ b/web/frontend/src/Header.svelte @@ -5,6 +5,7 @@ - `username String`: Empty string if auth. is disabled, otherwise the username as string - `authlevel Number`: The current users authentication level - `clusters [String]`: List of cluster names + - `subClusters [String]`: List of subCluster names - `roles [Number]`: Enum containing available roles --> @@ -23,24 +24,21 @@ import NavbarLinks from "./header/NavbarLinks.svelte"; import NavbarTools from "./header/NavbarTools.svelte"; - export let username; - export let authlevel; - export let clusters; - export let subClusters; - export let roles; - - let isOpen = false; - let screenSize; + /* Svelte 5 Props */ + let { username, authlevel, clusters, subClusters, roles } = $props(); + /* Const Init */ const jobsTitle = new Map(); jobsTitle.set(2, "Job Search"); jobsTitle.set(3, "Managed Jobs"); jobsTitle.set(4, "Jobs"); jobsTitle.set(5, "Jobs"); + const usersTitle = new Map(); usersTitle.set(3, "Managed Users"); usersTitle.set(4, "Users"); usersTitle.set(5, "Users"); + const projectsTitle = new Map(); projectsTitle.set(3, "Managed Projects"); projectsTitle.set(4, "Projects"); @@ -120,29 +118,41 @@ menu: "Info", }, ]; + + /* State Init */ + let isOpen = $state(false); + let screenSize = $state(0); + + /* Derived Vars */ + let showMax = $derived(screenSize >= 1500); + let showMid = $derived(screenSize < 1500 && screenSize >= 1300); + let showSml = $derived(screenSize < 1300 && screenSize >= 768); + let showBrg = $derived(screenSize < 768); + ClusterCockpit Logo - (isOpen = !isOpen)} /> + (isOpen = !isOpen)} /> (isOpen = detail.isOpen)} + onupdate={({ detail }) => (isOpen = detail.isOpen)} > - + \ No newline at end of file diff --git a/web/frontend/src/Job.root.svelte b/web/frontend/src/Job.root.svelte index 92d8bb2..427c9ae 100644 --- a/web/frontend/src/Job.root.svelte +++ b/web/frontend/src/Job.root.svelte @@ -56,8 +56,8 @@ selectedScopes = [], plots = {}; - let availableMetrics = new Set(), - missingMetrics = [], + let totalMetrics = 0; + let missingMetrics = [], missingHosts = [], somethingMissing = false; @@ -294,7 +294,7 @@ {#if $initq?.data} {/if} @@ -428,12 +428,16 @@ {#if $initq?.data} + selectedMetrics = [...newMetrics] + } /> {/if} diff --git a/web/frontend/src/Jobs.root.svelte b/web/frontend/src/Jobs.root.svelte index 1e7f96d..e5ae4e5 100644 --- a/web/frontend/src/Jobs.root.svelte +++ b/web/frontend/src/Jobs.root.svelte @@ -8,7 +8,7 @@ --> @@ -85,25 +101,25 @@ - - + { + {filterPresets} + showFilter={!showCompare} + matchedJobs={showCompare? matchedCompareJobs: matchedListJobs} + applyFilters={(detail) => { selectedCluster = detail.filters[0]?.cluster ? detail.filters[0].cluster.eq : null; @@ -122,29 +138,31 @@ {presetProject} bind:authlevel bind:roles - on:set-filter={({ detail }) => filterComponent.updateFilters(detail)} + setFilter={(filter) => filterComponent.updateFilters(filter)} /> {/if} - + {#if !showCompare} - { + { jobList.refreshJobs() jobList.refreshAllMetrics() }} /> {/if} - - - - {#if !showCompare && selectedJobs.length != 0} - {#if type == "USER"} @@ -148,9 +165,13 @@ {/if} @@ -159,9 +180,14 @@ @@ -169,9 +195,13 @@ @@ -179,9 +209,13 @@ @@ -189,9 +223,13 @@ diff --git a/web/frontend/src/Node.root.svelte b/web/frontend/src/Node.root.svelte index 60ab404..62e4847 100644 --- a/web/frontend/src/Node.root.svelte +++ b/web/frontend/src/Node.root.svelte @@ -9,7 +9,9 @@ --> @@ -146,7 +154,14 @@ - + { + from = newFrom; + to = newTo; + }} + /> @@ -172,7 +187,7 @@ { + onRefresh={() => { const diff = Date.now() - to; from = new Date(from.getTime() + diff); to = new Date(to.getTime() + diff); diff --git a/web/frontend/src/Status.root.svelte b/web/frontend/src/Status.root.svelte index 86170d1..613bd7f 100644 --- a/web/frontend/src/Status.root.svelte +++ b/web/frontend/src/Status.root.svelte @@ -42,15 +42,14 @@ import Refresher from "./generic/helper/Refresher.svelte"; import HistogramSelection from "./generic/select/HistogramSelection.svelte"; + /* Svelte 5 Props */ + let { cluster } = $props(); + + /* Const Init */ const { query: initq } = init(); const ccconfig = getContext("cc-config"); - - export let cluster; - - let plotWidths = []; - let colWidth; - let from = new Date(Date.now() - 5 * 60 * 1000), - to = new Date(Date.now()); + const client = getContextClient(); + const paging = { itemsPerPage: 10, page: 1 }; // Top 10 const topOptions = [ { key: "totalJobs", label: "Jobs" }, { key: "totalNodes", label: "Nodes" }, @@ -58,7 +57,26 @@ { key: "totalAccs", label: "Accelerators" }, ]; - let topProjectSelection = + /* State Init */ + let from = $state(new Date(Date.now() - 5 * 60 * 1000)); + let to = $state(new Date(Date.now())); + let isHistogramSelectionOpen = $state(false); + let colWidth = $state(0); + let plotWidths = $state([]); + // Bar Gauges + let allocatedNodes = $state({}); + let flopRate = $state({}); + let flopRateUnitPrefix = $state({}); + let flopRateUnitBase = $state({}); + let memBwRate = $state({}); + let memBwRateUnitPrefix = $state({}); + let memBwRateUnitBase = $state({}); + + let selectedHistograms = $state(cluster + ? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] ) + : ccconfig['user_view_histogramMetrics'] || []); + + let topProjectSelection = $state( topOptions.find( (option) => option.key == @@ -66,8 +84,10 @@ ) || topOptions.find( (option) => option.key == ccconfig.status_view_selectedTopProjectCategory, - ); - let topUserSelection = + ) + ); + + let topUserSelection = $state( topOptions.find( (option) => option.key == @@ -75,16 +95,12 @@ ) || topOptions.find( (option) => option.key == ccconfig.status_view_selectedTopUserCategory, - ); + ) + ); - let isHistogramSelectionOpen = false; - $: selectedHistograms = cluster - ? ccconfig[`user_view_histogramMetrics:${cluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] ) - : ccconfig['user_view_histogramMetrics'] || []; - - const client = getContextClient(); + /* Derived */ // Note: nodeMetrics are requested on configured $timestep resolution - $: mainQuery = queryStore({ + const mainQuery = $derived(queryStore({ client: client, query: gql` query ( @@ -162,10 +178,9 @@ filter: [{ state: ["running"] }, { cluster: { eq: cluster } }], selectedHistograms: selectedHistograms, }, - }); + })); - const paging = { itemsPerPage: 10, page: 1 }; // Top 10 - $: topUserQuery = queryStore({ + const topUserQuery = $derived(queryStore({ client: client, query: gql` query ( @@ -193,9 +208,9 @@ paging, sortBy: topUserSelection.key.toUpperCase(), }, - }); + })); - $: topProjectQuery = queryStore({ + const topProjectQuery = $derived(queryStore({ client: client, query: gql` query ( @@ -222,8 +237,46 @@ paging, sortBy: topProjectSelection.key.toUpperCase(), }, + })); + + /* Effects */ + $effect(() => { + if ($initq.data && $mainQuery.data) { + let subClusters = $initq.data.clusters.find( + (c) => c.name == cluster, + ).subClusters; + for (let subCluster of subClusters) { + allocatedNodes[subCluster.name] = + $mainQuery.data.allocatedNodes.find( + ({ name }) => name == subCluster.name, + )?.count || 0; + flopRate[subCluster.name] = + Math.floor( + sumUp($mainQuery.data.nodeMetrics, subCluster.name, "flops_any") * + 100, + ) / 100; + flopRateUnitPrefix[subCluster.name] = subCluster.flopRateSimd.unit.prefix; + flopRateUnitBase[subCluster.name] = subCluster.flopRateSimd.unit.base; + memBwRate[subCluster.name] = + Math.floor( + sumUp($mainQuery.data.nodeMetrics, subCluster.name, "mem_bw") * 100, + ) / 100; + memBwRateUnitPrefix[subCluster.name] = + subCluster.memoryBandwidth.unit.prefix; + memBwRateUnitBase[subCluster.name] = subCluster.memoryBandwidth.unit.base; + } + } }); + $effect(() => { + updateTopUserConfiguration(topUserSelection.key); + }); + + $effect(() => { + updateTopProjectConfiguration(topProjectSelection.key); + }); + + /* Const Functions */ const sumUp = (data, subcluster, metric) => data.reduce( (sum, node) => @@ -239,39 +292,6 @@ 0, ); - let allocatedNodes = {}, - flopRate = {}, - flopRateUnitPrefix = {}, - flopRateUnitBase = {}, - memBwRate = {}, - memBwRateUnitPrefix = {}, - memBwRateUnitBase = {}; - $: if ($initq.data && $mainQuery.data) { - let subClusters = $initq.data.clusters.find( - (c) => c.name == cluster, - ).subClusters; - for (let subCluster of subClusters) { - allocatedNodes[subCluster.name] = - $mainQuery.data.allocatedNodes.find( - ({ name }) => name == subCluster.name, - )?.count || 0; - flopRate[subCluster.name] = - Math.floor( - sumUp($mainQuery.data.nodeMetrics, subCluster.name, "flops_any") * - 100, - ) / 100; - flopRateUnitPrefix[subCluster.name] = subCluster.flopRateSimd.unit.prefix; - flopRateUnitBase[subCluster.name] = subCluster.flopRateSimd.unit.base; - memBwRate[subCluster.name] = - Math.floor( - sumUp($mainQuery.data.nodeMetrics, subCluster.name, "mem_bw") * 100, - ) / 100; - memBwRateUnitPrefix[subCluster.name] = - subCluster.memoryBandwidth.unit.prefix; - memBwRateUnitBase[subCluster.name] = subCluster.memoryBandwidth.unit.base; - } - } - const updateConfigurationMutation = ({ name, value }) => { return mutationStore({ client: client, @@ -284,20 +304,17 @@ }); }; + /* Functions */ function updateTopUserConfiguration(select) { if (ccconfig[`status_view_selectedTopUserCategory:${cluster}`] != select) { updateConfigurationMutation({ name: `status_view_selectedTopUserCategory:${cluster}`, value: JSON.stringify(select), }).subscribe((res) => { - if (res.fetching === false && !res.error) { - // console.log(`status_view_selectedTopUserCategory:${cluster}` + ' -> Updated!') - } else if (res.fetching === false && res.error) { + if (res.fetching === false && res.error) { throw res.error; } }); - } else { - // console.log('No Mutation Required: Top User') } } @@ -309,19 +326,12 @@ name: `status_view_selectedTopProjectCategory:${cluster}`, value: JSON.stringify(select), }).subscribe((res) => { - if (res.fetching === false && !res.error) { - // console.log(`status_view_selectedTopProjectCategory:${cluster}` + ' -> Updated!') - } else if (res.fetching === false && res.error) { + if (res.fetching === false && res.error) { throw res.error; } }); - } else { - // console.log('No Mutation Required: Top Project') } } - - $: updateTopUserConfiguration(topUserSelection.key); - $: updateTopProjectConfiguration(topProjectSelection.key); @@ -334,7 +344,7 @@ @@ -342,7 +352,7 @@ { + onRefresh={() => { from = new Date(Date.now() - 5 * 60 * 1000); to = new Date(Date.now()); }} @@ -483,6 +493,7 @@ {$topUserQuery.error.message} {:else} {$topProjectQuery.error.message} {:else} { + selectedHistograms = [...newSelection]; + }} /> diff --git a/web/frontend/src/Systems.root.svelte b/web/frontend/src/Systems.root.svelte index 1589cac..4510b60 100644 --- a/web/frontend/src/Systems.root.svelte +++ b/web/frontend/src/Systems.root.svelte @@ -28,72 +28,74 @@ import TimeSelection from "./generic/select/TimeSelection.svelte"; import Refresher from "./generic/helper/Refresher.svelte"; - export let displayType; - export let cluster = null; - export let subCluster = null; - export let from = null; - export let to = null; - - const { query: initq } = init(); - - console.assert( - displayType == "OVERVIEW" || displayType == "LIST", - "Invalid nodes displayType provided!", - ); - - if (from == null || to == null) { - to = new Date(Date.now()); - from = new Date(to.getTime()); - from.setHours(from.getHours() - 12); - } - - const initialized = getContext("initialized"); - const ccconfig = getContext("cc-config"); - const globalMetrics = getContext("globalMetrics"); - const displayNodeOverview = (displayType === 'OVERVIEW') - - const resampleConfig = getContext("resampling") || null; - const resampleResolutions = resampleConfig ? [...resampleConfig.resolutions] : []; - const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0; - let selectedResolution = resampleConfig ? resampleDefault : 0; - - let hostnameFilter = ""; - let pendingHostnameFilter = ""; - let selectedMetric = ccconfig.system_view_selectedMetric || ""; - let selectedMetrics = ( - ccconfig[`node_list_selectedMetrics:${cluster}:${subCluster}`] || - ccconfig[`node_list_selectedMetrics:${cluster}`] - ) || [ccconfig.system_view_selectedMetric]; - let isMetricsSelectionOpen = false; - /* Note 1: "Sorting" as use-case ignored for now, probably default to alphanumerical on hostnames of cluster (handled in frontend at the moment) Note 2: Add Idle State Filter (== No allocated Jobs) [Frontend?] : Cannot be handled by CCMS, requires secondary job query and refiltering of visible nodes */ - let systemMetrics = []; - let systemUnits = {}; + /* Scelte 5 Props */ + let { + displayType, + cluster = null, + subCluster = null, + fromPreset = null, + toPreset = null, + } = $props(); - function loadMetrics(isInitialized) { - if (!isInitialized) return - systemMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))] - for (let sm of systemMetrics) { - systemUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "") - } - if (!selectedMetric) selectedMetric = systemMetrics[0].name - } + /* Const Init */ + const { query: initq } = init(); + const displayNodeOverview = (displayType === 'OVERVIEW'); + const ccconfig = getContext("cc-config"); + const initialized = getContext("initialized"); + const globalMetrics = getContext("globalMetrics"); + const resampleConfig = getContext("resampling") || null; - $: loadMetrics($initialized) + const resampleResolutions = resampleConfig ? [...resampleConfig.resolutions] : []; + const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0; + const nowDate = new Date(Date.now()); - $: if (displayNodeOverview) { - selectedMetrics = [selectedMetric] - } + /* State Init */ + let to = $state(toPreset || new Date(Date.now())); + let from = $state(fromPreset || new Date(nowDate.setHours(nowDate.getHours() - 4))); + let selectedResolution = $state(resampleConfig ? resampleDefault : 0); + let hostnameFilter = $state(""); + let pendingHostnameFilter = $state(""); + let isMetricsSelectionOpen = $state(false); + let selectedMetric = $state(ccconfig.system_view_selectedMetric || ""); + let selectedMetrics = $state(( + ccconfig[`node_list_selectedMetrics:${cluster}:${subCluster}`] || + ccconfig[`node_list_selectedMetrics:${cluster}`] + ) || [ccconfig.system_view_selectedMetric]); - $: { // Wait after input for some time to prevent too many requests - setTimeout(function () { + /* Derived States */ + const systemMetrics = $derived($initialized ? [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster))] : []); + const presetSystemUnits = $derived(loadUnits(systemMetrics)); + + /* Effects */ + $effect(() => { + // OnMount: Ping Var, without this, OVERVIEW metric select is empty (reason tbd) + systemMetrics + }); + + /* Functions */ + function loadUnits(systemMetrics) { + let pendingUnits = {}; + if (systemMetrics.length > 0) { + for (let sm of systemMetrics) { + pendingUnits[sm.name] = (sm?.unit?.prefix ? sm.unit.prefix : "") + (sm?.unit?.base ? sm.unit.base : "") + }; + }; + return {...pendingUnits}; + }; + + // Wait after input for some time to prevent too many requests + let timeoutId = null; + function updateHostnameFilter() { + if (timeoutId != null) clearTimeout(timeoutId); + timeoutId = setTimeout(function () { hostnameFilter = pendingHostnameFilter; }, 500); - } + }; @@ -108,7 +110,7 @@ @@ -139,12 +141,20 @@ placeholder="Filter hostname ..." type="text" bind:value={pendingHostnameFilter} + oninput={updateHostnameFilter} /> - + { + from = newFrom; + to = newTo; + }} + /> {#if displayNodeOverview} @@ -153,26 +163,28 @@ Metric - {#each systemMetrics as metric} + {#each systemMetrics as metric (metric.name)} {metric.name} {presetSystemUnits[metric.name] ? "("+presetSystemUnits[metric.name]+")" : ""} + {:else} + {/each} {/if} - - - { - const diff = Date.now() - to; - from = new Date(from.getTime() + diff); - to = new Date(to.getTime() + diff); - }} - /> - {/if} + + + { + const diff = Date.now() - to; + from = new Date(from.getTime() + diff); + to = new Date(to.getTime() + diff); + }} + /> + @@ -185,20 +197,22 @@ {:else} {#if displayNodeOverview} - + {:else} - + {/if} {/if} - { - selectedMetrics = [...detail] - }} -/> +{#if !displayNodeOverview} + + selectedMetrics = [...newMetrics] + } + /> +{/if} diff --git a/web/frontend/src/Tags.root.svelte b/web/frontend/src/Tags.root.svelte index 03311b4..e1be6a9 100644 --- a/web/frontend/src/Tags.root.svelte +++ b/web/frontend/src/Tags.root.svelte @@ -24,15 +24,22 @@ init, } from "./generic/utils.js"; - export let username; - export let isAdmin; - export let tagmap; + /* Svelte 5 Props */ + let { + username, + isAdmin, + presetTagmap, + } = $props(); + /* Const Init */ const {} = init(); const client = getContextClient(); - let pendingChange = "none"; + /* State Init */ + let pendingChange = $state("none"); + let tagmap = $state(presetTagmap) + /* Functions */ const removeTagMutation = ({ tagIds }) => { return mutationStore({ client: client, @@ -96,7 +103,7 @@ diff --git a/web/frontend/src/User.root.svelte b/web/frontend/src/User.root.svelte index 0fad6cc..965290e 100644 --- a/web/frontend/src/User.root.svelte +++ b/web/frontend/src/User.root.svelte @@ -42,72 +42,102 @@ import TextFilter from "./generic/helper/TextFilter.svelte" import Refresher from "./generic/helper/Refresher.svelte"; + /* Svelte 5 Props */ + let { user, filterPresets } = $props(); + + /* Const Init */ const { query: initq } = init(); - const ccconfig = getContext("cc-config"); - - export let user; - export let filterPresets; - - let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the - let jobList; - let jobFilters = []; - let matchedListJobs = 0; - let sorting = { field: "startTime", type: "col", order: "DESC" }, - isSortingOpen = false; - let metrics = ccconfig.plot_list_selectedMetrics, - isMetricsSelectionOpen = false; - let isHistogramSelectionOpen = false; - let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null; - let showFootprint = filterPresets.cluster - ? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`] - : !!ccconfig.plot_list_showFootprint; - - let numDurationBins = "1h"; - let numMetricBins = 10; - let durationBinOptions = ["1m","10m","1h","6h","12h"]; - let metricBinOptions = [10, 20, 50, 100]; - - $: selectedHistograms = selectedCluster - ? ccconfig[`user_view_histogramMetrics:${selectedCluster}`] || ( ccconfig['user_view_histogramMetrics'] || [] ) - : ccconfig['user_view_histogramMetrics'] || []; - const client = getContextClient(); - $: stats = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) { - jobsStatistics(filter: $jobFilters, metrics: $selectedHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) { - totalJobs - shortJobs - totalWalltime - totalCoreHours - histDuration { - count - value - } - histNumNodes { - count - value - } - histMetrics { - metric - unit - stat - data { - min - max + const durationBinOptions = ["1m","10m","1h","6h","12h"]; + const metricBinOptions = [10, 20, 50, 100]; + + /* State Init */ + // List & Control Vars + let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the + let jobFilters = $state([]); + let jobList = $state(null); + let matchedListJobs = $state(0); + let isSortingOpen = $state(false); + let isMetricsSelectionOpen = $state(false); + let sorting = $state({ field: "startTime", type: "col", order: "DESC" }); + let selectedCluster = $state(filterPresets?.cluster ? filterPresets.cluster : null); + let selectedHistogramsBuffer = $state({ all: (ccconfig['user_view_histogramMetrics'] || []) }) + let metrics = $state(filterPresets.cluster + ? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] || + ccconfig.plot_list_selectedMetrics + : ccconfig.plot_list_selectedMetrics + ); + let showFootprint = $state(filterPresets.cluster + ? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`] + : !!ccconfig.plot_list_showFootprint + ); + + // Histogram Vars + let isHistogramSelectionOpen = $state(false); + let numDurationBins = $state("1h"); + let numMetricBins = $state(10); + + // Compare Vars (TODO) + // let jobCompare = $state(null); + // let showCompare = $state(false); + // let selectedJobs = $state([]); + // let filterBuffer = $state([]); + // let matchedCompareJobs = $state(0); + + /* Derived Vars */ + let selectedHistograms = $derived(selectedCluster ? selectedHistogramsBuffer[selectedCluster] : selectedHistogramsBuffer['all']); + let stats = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!, $selectedHistograms: [String!], $numDurationBins: String, $numMetricBins: Int) { + jobsStatistics(filter: $jobFilters, metrics: $selectedHistograms, numDurationBins: $numDurationBins , numMetricBins: $numMetricBins ) { + totalJobs + shortJobs + totalWalltime + totalCoreHours + histDuration { count - bin + value + } + histNumNodes { + count + value + } + histMetrics { + metric + unit + stat + data { + min + max + count + bin + } } } } - } - `, - variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins }, + `, + variables: { jobFilters, selectedHistograms, numDurationBins, numMetricBins }, + }) + ); + + /* Effect */ + $effect(() => { + if (!selectedHistogramsBuffer[selectedCluster]) { + selectedHistogramsBuffer[selectedCluster] = ccconfig[`user_view_histogramMetrics:${selectedCluster}`]; + }; }); - onMount(() => filterComponent.updateFilters()); + /* On Mount */ + onMount(() => { + filterComponent.updateFilters(); + // Why? -> `$derived(ccconfig[$cluster])` only loads array from last Backend-Query if $cluster changed reactively (without reload) + if (filterPresets?.cluster) { + selectedHistogramsBuffer[filterPresets.cluster] = ccconfig[`user_view_histogramMetrics:${filterPresets.cluster}`]; + }; + }); @@ -129,13 +159,13 @@ - @@ -143,11 +173,11 @@ { + applyFilters={(detail) => { jobFilters = [...detail.filters, { user: { eq: user.username } }]; selectedCluster = jobFilters[0]?.cluster ? jobFilters[0].cluster.eq @@ -173,11 +203,11 @@ filterComponent.updateFilters(detail)} + setFilter={(filter) => filterComponent.updateFilters(filter)} /> - { + { jobList.refreshJobs() jobList.refreshAllMetrics() }} /> @@ -269,7 +299,7 @@ outline color="secondary" class="w-100" - on:click={() => (isHistogramSelectionOpen = true)} + onclick={() => (isHistogramSelectionOpen = true)} > Select Histograms @@ -344,19 +374,31 @@ - + + sorting = {...newSort} + } +/> + metrics = [...newMetrics] + } + footprintSelect /> { + selectedHistogramsBuffer[selectedCluster || 'all'] = [...newSelection]; + }} /> diff --git a/web/frontend/src/analysis.entrypoint.js b/web/frontend/src/analysis.entrypoint.js index 07c63f5..97e4739 100644 --- a/web/frontend/src/analysis.entrypoint.js +++ b/web/frontend/src/analysis.entrypoint.js @@ -1,9 +1,10 @@ +import { mount } from 'svelte'; import {} from './header.entrypoint.js' import Analysis from './Analysis.root.svelte' filterPresets.cluster = cluster -new Analysis({ +mount(Analysis, { target: document.getElementById('svelte-app'), props: { filterPresets: filterPresets, diff --git a/web/frontend/src/analysis/PlotSelection.svelte b/web/frontend/src/analysis/PlotSelection.svelte index 6a5e089..ef3f4a7 100644 --- a/web/frontend/src/analysis/PlotSelection.svelte +++ b/web/frontend/src/analysis/PlotSelection.svelte @@ -21,10 +21,14 @@ } from "@sveltestrap/sveltestrap"; import { gql, getContextClient, mutationStore } from "@urql/svelte"; - export let availableMetrics; - export let metricsInHistograms; - export let metricsInScatterplots; + /* Svelte 5 Props */ + let { + availableMetrics, + metricsInHistograms = $bindable(), + metricsInScatterplots = $bindable(), + } = $props(); + /* Const Init */ const client = getContextClient(); const updateConfigurationMutation = ({ name, value }) => { return mutationStore({ @@ -38,11 +42,13 @@ }); }; - let isHistogramConfigOpen = false, - isScatterPlotConfigOpen = false; - let selectedMetric1 = null, - selectedMetric2 = null; + /* State Init */ + let isHistogramConfigOpen = $state(false); + let isScatterPlotConfigOpen = $state(false); + let selectedMetric1 = $state(null); + let selectedMetric2 = $state(null); + /* Functions */ function updateConfiguration(data) { updateConfigurationMutation({ name: data.name, @@ -55,12 +61,12 @@ } - - @@ -78,7 +84,7 @@ type="checkbox" bind:group={metricsInHistograms} value={metric} - on:change={() => + onchange={() => updateConfiguration({ name: "analysis_view_histogramMetrics", value: metricsInHistograms, @@ -91,7 +97,7 @@ - @@ -112,7 +118,7 @@ style="float: right;" outline color="danger" - on:click={() => { + onclick={() => { metricsInScatterplots = metricsInScatterplots.filter( (p) => pair != p, ); @@ -146,7 +152,7 @@ diff --git a/web/frontend/src/config.entrypoint.js b/web/frontend/src/config.entrypoint.js index d2949f2..f9d8e45 100644 --- a/web/frontend/src/config.entrypoint.js +++ b/web/frontend/src/config.entrypoint.js @@ -1,7 +1,8 @@ +import { mount } from 'svelte'; import {} from './header.entrypoint.js' import Config from './Config.root.svelte' -new Config({ +mount(Config, { target: document.getElementById('svelte-app'), props: { isAdmin: isAdmin, diff --git a/web/frontend/src/config/AdminSettings.svelte b/web/frontend/src/config/AdminSettings.svelte index dd53df4..4ccbb36 100644 --- a/web/frontend/src/config/AdminSettings.svelte +++ b/web/frontend/src/config/AdminSettings.svelte @@ -12,13 +12,17 @@ import Options from "./admin/Options.svelte"; import NoticeEdit from "./admin/NoticeEdit.svelte"; - export let ncontent; - - let users = []; - let roles = []; + /* Svelte 5 Props */ + let { ncontent } = $props(); + /* Const Init*/ const ccconfig = getContext("cc-config"); + /* State Init */ + let users = $state([]); + let roles = $state([]); + + /* Functions */ function getUserList() { fetch("/config/users/?via-ldap=false¬-just-user=true") .then((res) => res.json()) @@ -40,21 +44,22 @@ getValidRoles(); } + /* on Mount */ onMount(() => initAdmin()); - + getUserList()} /> - + getUserList()} bind:users /> - + getUserList()} /> - + getUserList()} /> diff --git a/web/frontend/src/config/UserSettings.svelte b/web/frontend/src/config/UserSettings.svelte index 1b59e31..2df1152 100644 --- a/web/frontend/src/config/UserSettings.svelte +++ b/web/frontend/src/config/UserSettings.svelte @@ -12,17 +12,26 @@ import PlotRenderOptions from "./user/PlotRenderOptions.svelte"; import PlotColorScheme from "./user/PlotColorScheme.svelte"; - export let username - export let isApi + /* Svelte 5 Props */ + let { + username, + isApi + } = $props(); + /* Const Init */ const ccconfig = getContext("cc-config"); - let message = { msg: "", target: "", color: "#d63384" }; - let displayMessage = false; - let cbmode = ccconfig?.plot_general_colorblindMode || false; + + /* State Init */ + let message = $state({ msg: "", target: "", color: "#d63384" }); + let displayMessage = $state(false); + let cbmode = $state(ccconfig?.plot_general_colorblindMode || false); - async function handleSettingSubmit(event) { - const selector = event.detail.selector - const target = event.detail.target + /* Functions */ + async function handleSettingSubmit(event, setting) { + event.preventDefault(); + + const selector = setting.selector + const target = setting.target let form = document.querySelector(selector); let formData = new FormData(form); try { @@ -53,6 +62,6 @@ } - handleSettingSubmit(e)}/> - handleSettingSubmit(e)}/> - handleSettingSubmit(e)}/> + handleSettingSubmit(e, newSetting)}/> + handleSettingSubmit(e, newSetting)}/> + handleSettingSubmit(e, newSetting)}/> diff --git a/web/frontend/src/config/admin/AddUser.svelte b/web/frontend/src/config/admin/AddUser.svelte index 6c20d7a..99c6a84 100644 --- a/web/frontend/src/config/admin/AddUser.svelte +++ b/web/frontend/src/config/admin/AddUser.svelte @@ -10,17 +10,19 @@ @@ -59,7 +57,8 @@ method="post" action="/config/users/" class="card-body" - on:submit|preventDefault={handleUserSubmit} + autocomplete="off" + onsubmit={(e) => handleUserSubmit(e)} > Create User
@@ -70,6 +69,7 @@ id="username" name="username" aria-describedby="usernameHelp" + autocomplete="username" />
Must be unique.
@@ -81,6 +81,7 @@ id="password" name="password" aria-describedby="passwordHelp" + autocomplete="new-password" />
Only API users are allowed to have a blank password. Users with a blank @@ -109,6 +110,7 @@ id="name" name="name" aria-describedby="nameHelp" + autocomplete="name" />
Optional, can be blank.
@@ -120,6 +122,7 @@ id="email" name="email" aria-describedby="emailHelp" + autocomplete="email" />
Optional, can be blank.
@@ -153,13 +156,13 @@ {/each}

- - {#if displayMessage}

- {message.msg} -
{/if} + + {#if displayMessage} + {message.msg} + {/if}

diff --git a/web/frontend/src/config/admin/EditProject.svelte b/web/frontend/src/config/admin/EditProject.svelte index 3c87d46..215fe51 100644 --- a/web/frontend/src/config/admin/EditProject.svelte +++ b/web/frontend/src/config/admin/EditProject.svelte @@ -7,15 +7,19 @@ @@ -108,19 +110,17 @@ placeholder="project-id" id="project-id" /> - - handleAddProject(e)}>Add handleRemoveProject(e)}>Remove

diff --git a/web/frontend/src/config/admin/EditRole.svelte b/web/frontend/src/config/admin/EditRole.svelte index b8d12bd..6618535 100644 --- a/web/frontend/src/config/admin/EditRole.svelte +++ b/web/frontend/src/config/admin/EditRole.svelte @@ -10,17 +10,19 @@ @@ -113,19 +113,17 @@ > {/each} - - handleAddRole(e)}>Add handleRemoveRole(e)}>Remove

diff --git a/web/frontend/src/config/admin/NoticeEdit.svelte b/web/frontend/src/config/admin/NoticeEdit.svelte index 325800b..29f0f61 100644 --- a/web/frontend/src/config/admin/NoticeEdit.svelte +++ b/web/frontend/src/config/admin/NoticeEdit.svelte @@ -6,14 +6,18 @@ import { Col, Card, CardTitle, CardBody } from "@sveltestrap/sveltestrap"; import { fade } from "svelte/transition"; - export let ncontent; + /* Svelte 5 Props */ + let { ncontent } = $props(); - let message = { msg: "", color: "#d63384" }; - let displayMessage = false; + /* State Init */ + let message = $state({ msg: "", color: "#d63384" }); + let displayMessage = $state(false); + + /* Functions */ + async function handleEditNotice(event) { + event.preventDefault(); - async function handleEditNotice() { const content = document.querySelector("#notice-content").value; - let formData = new FormData(); formData.append("new-content", content); @@ -56,14 +60,11 @@ value={ncontent} id="notice-content" /> - - - handleEditNotice(e)}>Edit Notice

diff --git a/web/frontend/src/config/admin/Options.svelte b/web/frontend/src/config/admin/Options.svelte index 3808834..2af1307 100644 --- a/web/frontend/src/config/admin/Options.svelte +++ b/web/frontend/src/config/admin/Options.svelte @@ -6,10 +6,13 @@ import { getContext, onMount } from "svelte"; import { Col, Card, CardBody, CardTitle } from "@sveltestrap/sveltestrap"; - let scrambled; - + /*Const Init */ const resampleConfig = getContext("resampling"); + /* State Init */ + let scrambled = $state(false); + + /* on Mount */ onMount(() => { scrambled = window.localStorage.getItem("cc-scramble-names") != null; }); @@ -33,7 +36,7 @@ type="checkbox" id="scramble-names-checkbox" style="margin-right: 1em;" - on:click={handleScramble} + onclick={() => handleScramble()} bind:checked={scrambled} /> Active? diff --git a/web/frontend/src/config/admin/ShowUsers.svelte b/web/frontend/src/config/admin/ShowUsers.svelte index d4988e8..fb31693 100644 --- a/web/frontend/src/config/admin/ShowUsers.svelte +++ b/web/frontend/src/config/admin/ShowUsers.svelte @@ -16,23 +16,19 @@ CardTitle, CardBody, } from "@sveltestrap/sveltestrap"; - import { createEventDispatcher } from "svelte"; import ShowUsersRow from "./ShowUsersRow.svelte"; - export let users = []; - - const dispatch = createEventDispatcher(); - function reloadUserList() { - dispatch("reload"); - } + /*Svelte 5 Props */ + let { users = $bindable([]), reloadUser } = $props(); + /* Functions */ function deleteUser(username) { if (confirm("Are you sure?")) { let formData = new FormData(); formData.append("username", username); fetch("/config/users/", { method: "DELETE", body: formData }).then((res) => { if (res.status == 200) { - reloadUserList(); + reloadUser(); } else { confirm(res.statusText); } @@ -40,7 +36,6 @@ } } - $: userList = users; @@ -53,11 +48,11 @@

-
+
@@ -71,13 +66,13 @@ - {#each userList as user} + {#each users as user} diff --git a/web/frontend/src/config/admin/ShowUsersRow.svelte b/web/frontend/src/config/admin/ShowUsersRow.svelte index 25c3710..67801a5 100644 --- a/web/frontend/src/config/admin/ShowUsersRow.svelte +++ b/web/frontend/src/config/admin/ShowUsersRow.svelte @@ -10,9 +10,13 @@ import { Button } from "@sveltestrap/sveltestrap"; import { fetchJwt } from "../../generic/utils.js" - export let user; + /* Svelte 5 Props */ + let { user } = $props(); - let jwt = ""; + /* State Init */ + let jwt = $state(""); + + /* Functions */ function getUserJwt(username) { const p = fetchJwt(username); p.then((content) => { @@ -30,7 +34,7 @@ -
- updateSetting("#line-width-form", "lw")} + onsubmit={(e) => updateSetting(e, { + selector: "#line-width-form", + target: "lw", + })} > - updateSetting("#plots-per-row-form", "ppr")} + onsubmit={(e) => updateSetting(e, { + selector: "#plots-per-row-form", + target: "ppr", + })} > - updateSetting("#backgrounds-form", "bg")} + onsubmit={(e) => updateSetting(e, { + selector: "#backgrounds-form", + target: "bg", + })} >
{#if config.plot_general_colorBackground} - + {:else} - + {/if}
{#if config.plot_general_colorBackground} - + {:else} - + {/if}
@@ -180,8 +180,10 @@ method="post" action="/frontend/configuration/" class="card-body" - on:submit|preventDefault={() => - updateSetting("#colorblindmode-form", "cbm")} + onsubmit={(e) => updateSetting(e, { + selector: "#colorblindmode-form", + target: "cbm", + })} > @@ -90,8 +88,10 @@ method="post" action="/frontend/configuration/" class="card-body" - on:submit|preventDefault={() => - updateSetting("#paging-form", "pag")} + onsubmit={(e) => updateSetting(e, { + selector: "#paging-form", + target: "pag", + })} >
- {#if config.job_list_usePaging} + {#if config?.job_list_usePaging} {:else} @@ -117,7 +117,7 @@
- {#if config.job_list_usePaging} + {#if config?.job_list_usePaging} {:else} @@ -137,7 +137,7 @@ Generate JWT {#if jwt} -

@@ -149,7 +149,7 @@

{/if} {:else} -

diff --git a/web/frontend/src/generic/Filters.svelte b/web/frontend/src/generic/Filters.svelte index ee7f327..c7b49fd 100644 --- a/web/frontend/src/generic/Filters.svelte +++ b/web/frontend/src/generic/Filters.svelte @@ -16,7 +16,6 @@ --> {#if $compareData.fetching} @@ -265,7 +265,7 @@ xticks={jobIds} xinfo={jobClusters} ylabel={'Resource Counts'} - data={comparePlotData['resources'].data} + data={comparePlotData['resources']?.data} {plotSync} forResources /> @@ -281,8 +281,8 @@ xinfo={jobClusters} ylabel={m} metric={m} - yunit={comparePlotData[m].unit} - data={comparePlotData[m].data} + yunit={comparePlotData[m]?.unit} + data={comparePlotData[m]?.data} {plotSync} /> @@ -314,7 +314,7 @@ -

- - diff --git a/web/frontend/src/generic/helper/Refresher.svelte b/web/frontend/src/generic/helper/Refresher.svelte index f5c6406..5378d26 100644 --- a/web/frontend/src/generic/helper/Refresher.svelte +++ b/web/frontend/src/generic/helper/Refresher.svelte @@ -8,35 +8,40 @@ - `refresh`: When fired, the upstream component refreshes its contents --> - + @@ -46,7 +51,7 @@ @@ -282,7 +295,7 @@ @@ -314,7 +327,7 @@ outline style="width:100%;" color="success" - on:click={(e) => ( + onclick={(e) => ( e.preventDefault(), createTag(newTagType, newTagName, newTagScope) )} > @@ -345,11 +358,11 @@ {/if} - + - @@ -387,7 +400,7 @@ @@ -396,7 +409,7 @@ @@ -435,7 +448,7 @@ @@ -444,7 +457,7 @@ @@ -475,7 +488,7 @@ outline style="width:100%;" color="success" - on:click={(e) => ( + onclick={(e) => ( e.preventDefault(), createTag(newTagType, newTagName, newTagScope) )} > diff --git a/web/frontend/src/generic/helper/TextFilter.svelte b/web/frontend/src/generic/helper/TextFilter.svelte index c47c979..7cb9f2d 100644 --- a/web/frontend/src/generic/helper/TextFilter.svelte +++ b/web/frontend/src/generic/helper/TextFilter.svelte @@ -12,21 +12,31 @@ @@ -86,10 +97,10 @@ {#if !presetProject} @@ -102,12 +113,12 @@ termChanged()} - on:keyup={(event) => termChanged(event.key == "Enter" ? 0 : throttle)} - placeholder={presetProject ? `Find ${mode} in ${scrambleNames ? scramble(presetProject) : presetProject} ...` : `Find ${mode} ...`} + onchange={() => termChanged()} + onkeyup={(event) => termChanged(event.key == "Enter" ? 0 : throttle)} + placeholder={presetProject ? `Find in ${scrambleNames ? scramble(presetProject) : presetProject} ...` : `Find ${mode} ...`} /> {#if presetProject} - {/if} diff --git a/web/frontend/src/generic/joblist/JobInfo.svelte b/web/frontend/src/generic/joblist/JobInfo.svelte index f5cb066..5886c61 100644 --- a/web/frontend/src/generic/joblist/JobInfo.svelte +++ b/web/frontend/src/generic/joblist/JobInfo.svelte @@ -12,15 +12,22 @@ import Tag from "../helper/Tag.svelte"; import TagManagement from "../helper/TagManagement.svelte"; - export let job; - export let jobTags = job.tags; - export let showTagedit = false; - export let username = null; - export let authlevel= null; - export let roles = null; - export let isSelected = null; - export let showSelect = false; + /* Svelte 5 Props */ + let { + job, + jobTags = job.tags, + showTagedit = false, + username = null, + authlevel= null, + roles = null, + isSelected = null, + showSelect = false, + } = $props(); + /* State Init */ + let displayCheck = $state(false); + + /* Functions */ function formatDuration(duration) { const hours = Math.floor(duration / 3600); duration -= hours * 3600; @@ -41,9 +48,8 @@ } } - let displayCheck = false; function clipJobId(jid) { - displayCheck = true; + // Navigator clipboard api needs a secure context (https) if (navigator.clipboard && window.isSecureContext) { navigator.clipboard @@ -65,14 +71,11 @@ textArea.remove(); } } - setTimeout(function () { - displayCheck = false; - }, 1000); }
-

+

{job.jobId} @@ -81,7 +84,7 @@ {#if showSelect}

- - diff --git a/web/frontend/src/generic/joblist/Pagination.svelte b/web/frontend/src/generic/joblist/Pagination.svelte index 77f6bc9..c4aab1d 100644 --- a/web/frontend/src/generic/joblist/Pagination.svelte +++ b/web/frontend/src/generic/joblist/Pagination.svelte @@ -11,11 +11,51 @@ - Dispatched once immediately and then each time page or itemsPerPage changes --> + +
- pageReset(e)} onchange={(e) => updateItems(e)} bind:value={itemsPerPage} id="cc-pagination-select" class="cc-pagination-select"> {#each pageSizes as size} {/each} @@ -23,54 +63,23 @@
- { (page - 1) * itemsPerPage } - { Math.min((page - 1) * itemsPerPage + itemsPerPage, totalItems) } of { totalItems } { itemText } + { ((page - 1) * itemsPerPage) + 1 } - { Math.min((page - 1) * itemsPerPage + itemsPerPage, totalItems) } of { totalItems } { itemText }
{#if !backButtonDisabled} - - + + {/if} {#if !nextButtonDisabled} - + {/if}
- -
deleteUser(user.username)}>Delete
{user?.roles ? user.roles.join(", ") : "No Roles"} {#if !jwt} - {:else} diff --git a/web/frontend/src/config/support/SupportOptions.svelte b/web/frontend/src/config/support/SupportOptions.svelte index 7d9ce03..39400a3 100644 --- a/web/frontend/src/config/support/SupportOptions.svelte +++ b/web/frontend/src/config/support/SupportOptions.svelte @@ -3,15 +3,20 @@ --> @@ -350,37 +348,34 @@ - {#each Object.entries(cbmode ? cvdschemes : colorschemes) as [name, rgbrow]} - - - + + - - - {/each} + + + + {/each} + {/key}
{name} - {#if rgbrow.join(",") == config.plot_general_colorscheme} + {#key activeRow} + {#each Object.entries(cbmode ? cvdschemes : colorschemes) as [name, rgbrow]} +
{name} - updateSetting("#colorscheme-form", "cs")} + checked={activeRow == JSON.stringify(rgbrow)} + onclick={(e) => { + activeRow = JSON.stringify(rgbrow) + updateSetting(e, { + selector: "#colorscheme-form", + target: "cs", + }); + }} /> - {:else} - - updateSetting("#colorscheme-form", "cs")} - /> - {/if} - - {#each rgbrow as rgb} - - {/each} -
+ {#each rgbrow as rgb} + + {/each} +
diff --git a/web/frontend/src/config/user/PlotRenderOptions.svelte b/web/frontend/src/config/user/PlotRenderOptions.svelte index 8a3a948..26e6563 100644 --- a/web/frontend/src/config/user/PlotRenderOptions.svelte +++ b/web/frontend/src/config/user/PlotRenderOptions.svelte @@ -19,33 +19,29 @@ CardTitle, } from "@sveltestrap/sveltestrap"; import { fade } from "svelte/transition"; - import { createEventDispatcher } from 'svelte'; - export let config; - export let message; - export let displayMessage; - - const dispatch = createEventDispatcher(); - function updateSetting(selector, target) { - dispatch('update-config', { - selector: selector, - target: target - }); - } + /* Svelte 5 Props */ + let { + config, + message = $bindable(), + displayMessage = $bindable(), + updateSetting + } = $props();
sortBy('meta', 'startTime')}> + sortBy('meta', 'startTime')}> Sort sortBy('meta', 'duration')}> + sortBy('meta', 'duration')}> Sort sortBy('meta', 'cluster')}> + sortBy('meta', 'cluster')}> Sort sortBy('resources', res)}> + sortBy('resources', res)}> {res} sortBy(metric, stat)}> + sortBy(metric, stat)}> {stat.charAt(0).toUpperCase() + stat.slice(1)} { + updatePaging={(detail) => { if (detail.itemsPerPage != itemsPerPage) { updateConfiguration(detail.itemsPerPage.toString(), detail.page); } else { diff --git a/web/frontend/src/generic/PlotGrid.svelte b/web/frontend/src/generic/PlotGrid.svelte index 5152e0d..b5cc30e 100644 --- a/web/frontend/src/generic/PlotGrid.svelte +++ b/web/frontend/src/generic/PlotGrid.svelte @@ -14,6 +14,18 @@ export let itemsPerRow export let items + + /* Migtation Notes + * Requirements + * - Parent Components must be already Migrated + * - TODO: Job.root.svelte, Node.root.svelte + * - DONE: Analysis, Status, User + * + * How-To + * - Define "Plot-Slotcode" as SV5 Snippet with argument "item" in parent (!) + * - Pass new snippet as argument/prop to here + * - @render snippet in items-loop with argument == item + */ diff --git a/web/frontend/src/generic/PlotTable.svelte b/web/frontend/src/generic/PlotTable.svelte deleted file mode 100644 index 4bc0694..0000000 --- a/web/frontend/src/generic/PlotTable.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - {#each rows as row} - - {#each row as item (item)} - - {/each} - - {/each} -
- {#if !isPlaceholder(item) && plotWidth > 0} - - {/if} -
diff --git a/web/frontend/src/generic/filters/Cluster.svelte b/web/frontend/src/generic/filters/Cluster.svelte index f886582..894ce0c 100644 --- a/web/frontend/src/generic/filters/Cluster.svelte +++ b/web/frontend/src/generic/filters/Cluster.svelte @@ -13,7 +13,7 @@ --> (isOpen = !isOpen)}> @@ -45,13 +50,13 @@

Cluster

{#if disableClusterSelection} - + {:else} ((pendingCluster = null), (pendingPartition = null))} + onclick={() => ((pendingCluster = null), (pendingPartition = null))} > Any Cluster @@ -59,7 +64,7 @@ ( + onclick={() => ( (pendingCluster = cluster.name), (pendingPartition = null) )} > @@ -75,14 +80,14 @@ (pendingPartition = null)} + onclick={() => (pendingPartition = null)} > Any Partition - {#each clusters.find((c) => c.name == pendingCluster).partitions as partition} + {#each clusters?.find((c) => c.name == pendingCluster)?.partitions as partition} (pendingPartition = partition)} + onclick={() => (pendingPartition = partition)} > {partition} @@ -93,22 +98,22 @@ - - + {#if !disableClusterSelection} + + {/if} +
diff --git a/web/frontend/src/generic/filters/Duration.svelte b/web/frontend/src/generic/filters/Duration.svelte index 6a22b98..75f2e80 100644 --- a/web/frontend/src/generic/filters/Duration.svelte +++ b/web/frontend/src/generic/filters/Duration.svelte @@ -13,7 +13,6 @@ --> (isOpen = !isOpen)}> @@ -92,7 +111,7 @@ type="number" min="0" class="form-control" - bind:value={pendingMoreThan.hours} + bind:value={moreState.hours} disabled={moreDisabled} />
@@ -107,7 +126,7 @@ min="0" max="59" class="form-control" - bind:value={pendingMoreThan.mins} + bind:value={moreState.mins} disabled={moreDisabled} />
@@ -126,7 +145,7 @@ type="number" min="0" class="form-control" - bind:value={pendingLessThan.hours} + bind:value={lessState.hours} disabled={lessDisabled} />
@@ -141,7 +160,7 @@ min="0" max="59" class="form-control" - bind:value={pendingLessThan.mins} + bind:value={lessState.mins} disabled={lessDisabled} />
@@ -160,7 +179,7 @@ type="number" min="0" class="form-control" - bind:value={pendingFrom.hours} + bind:value={fromState.hours} disabled={betweenDisabled} />
@@ -175,7 +194,7 @@ min="0" max="59" class="form-control" - bind:value={pendingFrom.mins} + bind:value={fromState.mins} disabled={betweenDisabled} />
@@ -192,7 +211,7 @@ type="number" min="0" class="form-control" - bind:value={pendingTo.hours} + bind:value={toState.hours} disabled={betweenDisabled} />
@@ -207,7 +226,7 @@ min="0" max="59" class="form-control" - bind:value={pendingTo.mins} + bind:value={toState.mins} disabled={betweenDisabled} />
@@ -220,39 +239,32 @@ - + diff --git a/web/frontend/src/generic/filters/Energy.svelte b/web/frontend/src/generic/filters/Energy.svelte index 9dd6dd8..536510b 100644 --- a/web/frontend/src/generic/filters/Energy.svelte +++ b/web/frontend/src/generic/filters/Energy.svelte @@ -10,7 +10,6 @@ --> (isOpen = !isOpen)}> Filter based on energy -

Total Job Energy (kWh)

- ( - (energy.from = detail[0]), (energy.to = detail[1]) - )} - min={0.0} - max={energyMaximum} - firstSlider={energy?.from ? energy.from : 0.0} - secondSlider={energy?.to ? energy.to : energyMaximum} - inputFieldFrom={energy?.from ? energy.from : null} - inputFieldTo={energy?.to ? energy.to : null} - /> +
+
Total Job Energy (kWh)
+ { + energyState.from = detail[0]; + energyState.to = detail[1]; + }} + sliderMin={0.0} + sliderMax={1000.0} + fromPreset={energyState?.from? energyState.from : 0.0} + toPreset={energyState?.to? energyState.to : 1000.0} + /> +
diff --git a/web/frontend/src/generic/filters/InfoBox.svelte b/web/frontend/src/generic/filters/InfoBox.svelte index ebd3526..ea9eb3f 100644 --- a/web/frontend/src/generic/filters/InfoBox.svelte +++ b/web/frontend/src/generic/filters/InfoBox.svelte @@ -4,16 +4,22 @@ Properties: - `icon String`: Sveltestrap icon name - `modified Bool?`: Optional if filter is modified [Default: false] + - `onclick Fn()`: Opens Modal on click + - `children Fn()?`: Internal prop, Svelte 5 version of --> - diff --git a/web/frontend/src/generic/filters/JobStates.svelte b/web/frontend/src/generic/filters/JobStates.svelte index d903abc..0e32885 100644 --- a/web/frontend/src/generic/filters/JobStates.svelte +++ b/web/frontend/src/generic/filters/JobStates.svelte @@ -13,7 +13,7 @@ - `const allJobStates [String]`: List of all available job states used in cc-backend --> - (isOpen = !isOpen)}> @@ -71,28 +72,25 @@ - +
diff --git a/web/frontend/src/generic/filters/Resources.svelte b/web/frontend/src/generic/filters/Resources.svelte index 443dda7..02925d4 100644 --- a/web/frontend/src/generic/filters/Resources.svelte +++ b/web/frontend/src/generic/filters/Resources.svelte @@ -2,14 +2,11 @@ @component Filter sub-component for selecting job resources Properties: - - `cluster Object?`: The currently selected cluster config [Default: null] - `isOpen Bool?`: Is this filter component opened [Default: false] + - `activeCluster String?`: The currently selected cluster name [Default: null] - `numNodes Object?`: The currently selected numNodes filter [Default: {from:null, to:null}] - `numHWThreads Object?`: The currently selected numHWThreads filter [Default: {from:null, to:null}] - `numAccelerators Object?`: The currently selected numAccelerators filter [Default: {from:null, to:null}] - - `isNodesModified Bool?`: Is the node filter modified [Default: false] - - `isHwthreadsModified Bool?`: Is the Hwthreads filter modified [Default: false] - - `isAccsModified Bool?`: Is the Accelerator filter modified [Default: false] - `namedNode String?`: The currently selected single named node (= hostname) [Default: null] Events: @@ -17,7 +14,7 @@ --> (isOpen = !isOpen)}> Select number of utilized Resources -
Named Node
-
+
Named Node
+
@@ -164,82 +211,63 @@ {/each}
-
Number of Nodes
- { - pendingNumNodes = { from: detail[0], to: detail[1] }; - isNodesModified = true; - }} - min={minNumNodes} - max={maxNumNodes} - firstSlider={pendingNumNodes.from} - secondSlider={pendingNumNodes.to} - inputFieldFrom={pendingNumNodes.from} - inputFieldTo={pendingNumNodes.to} - /> -
- Number of HWThreads (Use for Single-Node Jobs) -
- { - pendingNumHWThreads = { from: detail[0], to: detail[1] }; - isHwthreadsModified = true; - }} - min={minNumHWThreads} - max={maxNumHWThreads} - firstSlider={pendingNumHWThreads.from} - secondSlider={pendingNumHWThreads.to} - inputFieldFrom={pendingNumHWThreads.from} - inputFieldTo={pendingNumHWThreads.to} - /> - {#if maxNumAccelerators != null && maxNumAccelerators > 1} -
Number of Accelerators
+ +
+
Number of Nodes
{ - pendingNumAccelerators = { from: detail[0], to: detail[1] }; - isAccsModified = true; + changeRange={(detail) => { + nodesState.from = detail[0]; + nodesState.to = detail[1]; }} - min={minNumAccelerators} - max={maxNumAccelerators} - firstSlider={pendingNumAccelerators.from} - secondSlider={pendingNumAccelerators.to} - inputFieldFrom={pendingNumAccelerators.from} - inputFieldTo={pendingNumAccelerators.to} + sliderMin={minNumNodes} + sliderMax={maxNumNodes} + fromPreset={nodesState.from} + toPreset={nodesState.to} /> +
+ +
+
Number of HWThreads (Use for Single-Node Jobs)
+ { + threadState.from = detail[0]; + threadState.to = detail[1]; + }} + sliderMin={1} + sliderMax={maxNumHWThreads} + fromPreset={threadState.from} + toPreset={threadState.to} + /> +
+ {#if maxNumAccelerators != null && maxNumAccelerators > 1} +
+
Number of Accelerators
+ { + accState.from = detail[0]; + accState.to = detail[1]; + }} + sliderMin={0} + sliderMax={maxNumAccelerators} + fromPreset={accState.from} + toPreset={accState.to} + /> +
{/if} - + diff --git a/web/frontend/src/generic/filters/StartTime.svelte b/web/frontend/src/generic/filters/StartTime.svelte index a109fbb..c722baa 100644 --- a/web/frontend/src/generic/filters/StartTime.svelte +++ b/web/frontend/src/generic/filters/StartTime.svelte @@ -12,8 +12,19 @@ - `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component --> + + (isOpen = !isOpen)}> Select Start Time - {#if range !== ""} + {#if rangeSelect !== ""}

Current Range

- + {#each startTimeSelectOptions as { rangeLabel, range }} - {/each} @@ -101,42 +99,41 @@

From

- + - +

To

- + - +
- {#if pendingRange !== ""} + {#if rangeSelect !== ""} - +
diff --git a/web/frontend/src/generic/filters/Stats.svelte b/web/frontend/src/generic/filters/Stats.svelte index 3252d39..fe05466 100644 --- a/web/frontend/src/generic/filters/Stats.svelte +++ b/web/frontend/src/generic/filters/Stats.svelte @@ -2,7 +2,6 @@ @component Filter sub-component for selecting job statistics Properties: - - `isModified Bool?`: Is this filter component modified [Default: false] - `isOpen Bool?`: Is this filter component opened [Default: false] - `stats [Object]?`: The currently selected statistics filter [Default: []] @@ -11,7 +10,6 @@ --> (isOpen = !isOpen)}> - Filter based on statistics + + Filter based on statistics + - {#each statistics as stat} -

{stat.text}

- ( - (stat.from = detail[0]), (stat.to = detail[1]), (stat.enabled = true) - )} - min={0} - max={stat.peak} - firstSlider={stat.from} - secondSlider={stat.to} - inputFieldFrom={stat.from} - inputFieldTo={stat.to} - /> + {#each availableStats as aStat} +
+
{aStat.text}
+ { + aStat.from = detail[0]; + aStat.to = detail[1]; + if (aStat.from == 0 && aStat.to == aStat.peak) { + aStat.enabled = false; + } else { + aStat.enabled = true; + } + }} + sliderMin={0.0} + sliderMax={aStat.peak} + fromPreset={aStat.from} + toPreset={aStat.to} + /> +
{/each}
- +
diff --git a/web/frontend/src/generic/filters/Tags.svelte b/web/frontend/src/generic/filters/Tags.svelte index e42d185..0cbed27 100644 --- a/web/frontend/src/generic/filters/Tags.svelte +++ b/web/frontend/src/generic/filters/Tags.svelte @@ -11,7 +11,7 @@ --> (isOpen = !isOpen)}> @@ -55,7 +55,7 @@ @@ -81,21 +81,25 @@ + - + diff --git a/web/frontend/src/generic/helper/ConcurrentJobs.svelte b/web/frontend/src/generic/helper/ConcurrentJobs.svelte index 85bac83..d42ace1 100644 --- a/web/frontend/src/generic/helper/ConcurrentJobs.svelte +++ b/web/frontend/src/generic/helper/ConcurrentJobs.svelte @@ -17,11 +17,14 @@ Icon } from "@sveltestrap/sveltestrap"; - export let cJobs; - export let showLinks = false; - export let renderCard = false; - export let width = "auto"; - export let height = "400px"; + /* Svelte 5 Props */ + let { + cJobs, + showLinks = false, + renderCard = false, + width = "auto", + height = "400px", + } = $props(); {#if renderCard} diff --git a/web/frontend/src/generic/helper/JobFootprint.svelte b/web/frontend/src/generic/helper/JobFootprint.svelte index 80e905b..8d89731 100644 --- a/web/frontend/src/generic/helper/JobFootprint.svelte +++ b/web/frontend/src/generic/helper/JobFootprint.svelte @@ -23,79 +23,90 @@ } from "@sveltestrap/sveltestrap"; import { findJobFootprintThresholds } from "../utils.js"; - export let job; - export let displayTitle = true; - export let width = "auto"; - export let height = "310px"; + /* Svelte 5 Props */ + let { + job, + displayTitle = true, + width = "auto", + height = "310px", + } = $props(); - const footprintData = job?.footprint?.map((jf) => { - const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name); - if (fmc) { - // Unit - const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "") + /* Derived */ + const footprintData = $derived(buildFootprint(job?.footprint)); + + /* Functions */ + function buildFootprint(input) { + let result = input?.map((jf) => { + const fmc = getContext("getMetricConfig")(job.cluster, job.subCluster, jf.name); + if (fmc) { + // Unit + const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "") - // Threshold / -Differences - const fmt = findJobFootprintThresholds(job, jf.stat, fmc); + // Threshold / -Differences + const fmt = findJobFootprintThresholds(job, jf.stat, fmc); - // Define basic data -> Value: Use as Provided - const fmBase = { - name: jf.name + ' (' + jf.stat + ')', - avg: jf.value, - unit: unit, - max: fmt.peak, - dir: fmc.lowerIsBetter - }; - - if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "alert")) { - return { - ...fmBase, - color: "danger", - message: `Footprint value way ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`, - impact: 3 + // Define basic data -> Value: Use as Provided + const fmBase = { + name: jf.name + ' (' + jf.stat + ')', + avg: jf.value, + unit: unit, + max: fmt.peak, + dir: fmc.lowerIsBetter }; - } else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "caution")) { + + if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "alert")) { + return { + ...fmBase, + color: "danger", + message: `Footprint value way ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`, + impact: 3 + }; + } else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "caution")) { + return { + ...fmBase, + color: "warning", + message: `Footprint value ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`, + impact: 2, + }; + } else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "normal")) { + return { + ...fmBase, + color: "success", + message: "Footprint value within expected thresholds.", + impact: 1, + }; + } else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) { + return { + ...fmBase, + color: "info", + message: + "Footprint value above expected normal threshold: Check for artifacts recommended.", + impact: 0, + }; + } else { + return { + ...fmBase, + color: "secondary", + message: + "Footprint value above expected peak threshold: Check for artifacts!", + impact: -1, + }; + } + } else { // No matching metric config: display as single value return { - ...fmBase, - color: "warning", - message: `Footprint value ${fmc.lowerIsBetter ? "above" : "below"} expected normal threshold.`, - impact: 2, - }; - } else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "normal")) { - return { - ...fmBase, - color: "success", - message: "Footprint value within expected thresholds.", - impact: 1, - }; - } else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) { - return { - ...fmBase, - color: "info", + name: jf.name + ' (' + jf.stat + ')', + avg: jf.value, message: - "Footprint value above expected normal threshold: Check for artifacts recommended.", - impact: 0, - }; - } else { - return { - ...fmBase, - color: "secondary", - message: - "Footprint value above expected peak threshold: Check for artifacts!", - impact: -1, + `No config for metric ${jf.name} found.`, + impact: 4, }; } - } else { // No matching metric config: display as single value - return { - name: jf.name + ' (' + jf.stat + ')', - avg: jf.value, - message: - `No config for metric ${jf.name} found.`, - impact: 4, - }; - } - }).sort(function (a, b) { // Sort by impact value primarily, within impact sort name alphabetically - return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); - });; + }).sort(function (a, b) { // Sort by impact value primarily, within impact sort name alphabetically + return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); + });; + + return result; + }; function evalFootprint(value, thresholds, lowerIsBetter, level) { // Handle Metrics in which less value is better @@ -176,7 +187,7 @@ >{fpd.message}
- + {#if fpd.dir}