Merge branch 'hotfix' into add_detailed_nodelist

This commit is contained in:
Christoph Kluge
2025-01-07 14:07:41 +01:00
93 changed files with 3383 additions and 3922 deletions

View File

@@ -1,18 +1,18 @@
{
"name": "cc-frontend",
"version": "1.0.1",
"version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cc-frontend",
"version": "1.0.1",
"version": "1.0.2",
"license": "MIT",
"dependencies": {
"@rollup/plugin-replace": "^5.0.7",
"@sveltestrap/sveltestrap": "^6.2.7",
"@urql/svelte": "^4.2.1",
"chart.js": "^4.4.5",
"@urql/svelte": "^4.2.2",
"chart.js": "^4.4.6",
"date-fns": "^2.30.0",
"graphql": "^16.9.0",
"mathjs": "^12.4.3",
@@ -25,16 +25,16 @@
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-terser": "^0.4.4",
"@timohausmann/quadtree-js": "^1.2.6",
"rollup": "^4.24.0",
"rollup": "^4.27.4",
"rollup-plugin-css-only": "^4.5.2",
"rollup-plugin-svelte": "^7.2.2",
"svelte": "^4.2.19"
}
},
"node_modules/@0no-co/graphql.web": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@0no-co/graphql.web/-/graphql.web-1.0.8.tgz",
"integrity": "sha512-8BG6woLtDMvXB9Ajb/uE+Zr/U7y4qJ3upXi0JQHZmsKUJa7HjF/gFvmL2f3/mSmfZoQGRr9VoY97LCX2uaFMzA==",
"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==",
"license": "MIT",
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
@@ -59,9 +59,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz",
"integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==",
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@@ -130,9 +130,9 @@
}
},
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@popperjs/core": {
@@ -241,14 +241,14 @@
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz",
"integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==",
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz",
"integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
@@ -263,9 +263,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
"integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
"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==",
"cpu": [
"arm"
],
@@ -277,9 +277,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
"integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
"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==",
"cpu": [
"arm64"
],
@@ -291,9 +291,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
"integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
"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==",
"cpu": [
"arm64"
],
@@ -305,9 +305,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
"integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
"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==",
"cpu": [
"x64"
],
@@ -318,10 +318,38 @@
"darwin"
]
},
"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==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"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==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
"integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
"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==",
"cpu": [
"arm"
],
@@ -333,9 +361,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
"integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
"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==",
"cpu": [
"arm"
],
@@ -347,9 +375,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
"integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
"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==",
"cpu": [
"arm64"
],
@@ -361,9 +389,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
"integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
"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==",
"cpu": [
"arm64"
],
@@ -375,9 +403,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
"integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
"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==",
"cpu": [
"ppc64"
],
@@ -389,9 +417,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
"integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
"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==",
"cpu": [
"riscv64"
],
@@ -403,9 +431,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
"integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
"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==",
"cpu": [
"s390x"
],
@@ -417,9 +445,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
"integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
"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==",
"cpu": [
"x64"
],
@@ -431,9 +459,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
"integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
"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==",
"cpu": [
"x64"
],
@@ -445,9 +473,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
"integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
"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==",
"cpu": [
"arm64"
],
@@ -459,9 +487,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
"integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
"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==",
"cpu": [
"ia32"
],
@@ -473,9 +501,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
"integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
"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==",
"cpu": [
"x64"
],
@@ -519,9 +547,9 @@
"license": "MIT"
},
"node_modules/@urql/core": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-5.0.6.tgz",
"integrity": "sha512-38rgSDqVNihFDauw1Pm9V7XLWIKuK8V9CKgrUF7/xEKinze8ENKP1ZeBhkG+dxWzJan7CHK+SLl46kAdvZwIlA==",
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-5.0.8.tgz",
"integrity": "sha512-1GOnUw7/a9bzkcM0+U8U5MmxW2A7FE5YquuEmcJzTtW5tIs2EoS4F2ITpuKBjRBbyRjZgO860nWFPo1m4JImGA==",
"license": "MIT",
"dependencies": {
"@0no-co/graphql.web": "^1.0.5",
@@ -529,9 +557,9 @@
}
},
"node_modules/@urql/svelte": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-4.2.1.tgz",
"integrity": "sha512-tzjt5qElu6EF4ns+AWLUFvvGFH+bDGEgLStHQTBu76puQcMCW374MrjxWM9lKA6lfA7iUyu1KXkIRhxNy09l4Q==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-4.2.2.tgz",
"integrity": "sha512-6ntLGsWcnNtaMZVmFpePfFTSpYxYpznCAqnuvLDjt7Oa7YqHcFiyPnz7IIsiPD9VE6hZSi0+RwmRk5BMba/teQ==",
"license": "MIT",
"dependencies": {
"@urql/core": "^5.0.0",
@@ -543,9 +571,9 @@
}
},
"node_modules/acorn": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz",
"integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -597,9 +625,9 @@
"license": "MIT"
},
"node_modules/chart.js": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.5.tgz",
"integrity": "sha512-CVVjg1RYTJV9OCC8WeJPMx8gsV8K6WIyIEQUE3ui4AR9Hfgls9URri6Ja3hyMVBbTF8Q2KFa19PE815gWcWhng==",
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
"integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
@@ -645,9 +673,9 @@
"license": "MIT"
},
"node_modules/complex.js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.3.0.tgz",
"integrity": "sha512-wWHzifVdUPbPBhh+ObvpVGIzrAQjTvmnnEJKBfLW5YbyAB6OXQ0r+Q92fByMIrSSlxUuCujqxriJSR6R/kVxPA==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz",
"integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==",
"license": "MIT",
"engines": {
"node": "*"
@@ -867,9 +895,9 @@
"license": "MIT"
},
"node_modules/magic-string": {
"version": "0.30.12",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
"integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
"version": "0.30.14",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz",
"integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
@@ -955,21 +983,21 @@
}
},
"node_modules/periscopic/node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"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": "*"
"@types/estree": "^1.0.6"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"engines": {
"node": ">=8.6"
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
@@ -1020,9 +1048,9 @@
}
},
"node_modules/rollup": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
"integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
"version": "4.27.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz",
"integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
@@ -1036,22 +1064,24 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.24.0",
"@rollup/rollup-android-arm64": "4.24.0",
"@rollup/rollup-darwin-arm64": "4.24.0",
"@rollup/rollup-darwin-x64": "4.24.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
"@rollup/rollup-linux-arm-musleabihf": "4.24.0",
"@rollup/rollup-linux-arm64-gnu": "4.24.0",
"@rollup/rollup-linux-arm64-musl": "4.24.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
"@rollup/rollup-linux-riscv64-gnu": "4.24.0",
"@rollup/rollup-linux-s390x-gnu": "4.24.0",
"@rollup/rollup-linux-x64-gnu": "4.24.0",
"@rollup/rollup-linux-x64-musl": "4.24.0",
"@rollup/rollup-win32-arm64-msvc": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.24.0",
"@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",
"fsevents": "~2.3.2"
}
},
@@ -1103,6 +1133,19 @@
"node": ">= 8.0.0"
}
},
"node_modules/rollup-plugin-svelte/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -1235,12 +1278,12 @@
}
},
"node_modules/svelte/node_modules/is-reference": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"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": "*"
"@types/estree": "^1.0.6"
}
},
"node_modules/terser": {

View File

@@ -11,7 +11,7 @@
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-terser": "^0.4.4",
"@timohausmann/quadtree-js": "^1.2.6",
"rollup": "^4.24.0",
"rollup": "^4.27.4",
"rollup-plugin-css-only": "^4.5.2",
"rollup-plugin-svelte": "^7.2.2",
"svelte": "^4.2.19"
@@ -19,8 +19,8 @@
"dependencies": {
"@rollup/plugin-replace": "^5.0.7",
"@sveltestrap/sveltestrap": "^6.2.7",
"@urql/svelte": "^4.2.1",
"chart.js": "^4.4.5",
"@urql/svelte": "^4.2.2",
"chart.js": "^4.4.6",
"date-fns": "^2.30.0",
"graphql": "^16.9.0",
"mathjs": "^12.4.3",

View File

@@ -422,14 +422,14 @@
<td><Icon name="circle-fill" style="color: {colors[i]};" /></td>
{#if groupSelection.key == "user"}
<th scope="col"
><a href="/monitoring/user/{te.id}?cluster={cluster.name}"
><a href="/monitoring/user/{te.id}?cluster={cluster}"
>{te.id}</a
></th
>
{:else}
<th scope="col"
><a
href="/monitoring/jobs/?cluster={cluster.name}&project={te.id}&projectMatch=eq"
href="/monitoring/jobs/?cluster={cluster}&project={te.id}&projectMatch=eq"
>{te.id}</a
></th
>

View File

@@ -15,6 +15,7 @@
export let isAdmin;
export let isApi;
export let username;
export let ncontent;
</script>
{#if isAdmin == true}
@@ -22,7 +23,7 @@
<CardHeader>
<CardTitle class="mb-1">Admin Options</CardTitle>
</CardHeader>
<AdminSettings />
<AdminSettings {ncontent}/>
</Card>
{/if}

View File

@@ -139,9 +139,6 @@
return names;
}, [])
),
...(ccconfig[`job_view_polarPlotMetrics:${job.cluster}`] ||
ccconfig[`job_view_polarPlotMetrics`]
),
...(ccconfig[`job_view_nodestats_selectedMetrics:${job.cluster}`] ||
ccconfig[`job_view_nodestats_selectedMetrics`]
),

View File

@@ -6,7 +6,8 @@ new Config({
props: {
isAdmin: isAdmin,
isApi: isApi,
username: username
username: username,
ncontent: ncontent,
},
context: new Map([
['cc-config', clusterCockpitConfig],

View File

@@ -10,6 +10,9 @@
import AddUser from "./admin/AddUser.svelte";
import ShowUsers from "./admin/ShowUsers.svelte";
import Options from "./admin/Options.svelte";
import NoticeEdit from "./admin/NoticeEdit.svelte";
export let ncontent;
let users = [];
let roles = [];
@@ -52,4 +55,5 @@
<EditProject on:reload={getUserList} />
</Col>
<Options />
<NoticeEdit {ncontent}/>
</Row>

View File

@@ -0,0 +1,78 @@
<!--
@component Admin edit notice.txt content card
-->
<script>
import { Col, Card, CardTitle, CardBody } from "@sveltestrap/sveltestrap";
import { fade } from "svelte/transition";
export let ncontent;
let message = { msg: "", color: "#d63384" };
let displayMessage = false;
async function handleEditNotice() {
const content = document.querySelector("#notice-content").value;
let formData = new FormData();
formData.append("new-content", content);
try {
const res = await fetch(`/config/notice/`, {
method: "POST",
body: formData,
});
if (res.ok) {
let text = await res.text();
popMessage(text, "#048109");
} else {
let text = await res.text();
throw new Error("Response Code " + res.status + "-> " + text);
}
} catch (err) {
popMessage(err, "#d63384");
}
}
function popMessage(response, rescolor) {
message = { msg: response, color: rescolor };
displayMessage = true;
setTimeout(function () {
displayMessage = false;
}, 3500);
}
</script>
<Col>
<Card class="h-100">
<CardBody>
<CardTitle class="mb-3">Edit Notice Shown On Homepage</CardTitle>
<p>Empty content ("No Content.") hides notice card on homepage.</p>
<div class="input-group mb-3">
<input
type="text"
class="form-control"
placeholder="No Content."
value={ncontent}
id="notice-content"
/>
<!-- PreventDefault on Sveltestrap-Button more complex to achieve than just use good ol' html button -->
<!-- see: https://stackoverflow.com/questions/69630422/svelte-how-to-use-event-modifiers-in-my-own-components -->
<button
class="btn btn-primary"
type="button"
id="edit-notice-button"
on:click|preventDefault={handleEditNotice}>Edit Notice</button
>
</div>
<p>
{#if displayMessage}<b
><code style="color: {message.color};" out:fade
>Update: {message.msg}</code
></b
>{/if}
</p>
</CardBody>
</Card>
</Col>

View File

@@ -76,7 +76,7 @@
numHWThreads: filterPresets.numHWThreads || { from: null, to: null },
numAccelerators: filterPresets.numAccelerators || { from: null, to: null },
stats: [],
stats: filterPresets.stats || [],
};
let isClusterOpen = false,
@@ -127,27 +127,30 @@
items.push({ jobId: { [filters.jobIdMatch]: filters.jobId } });
if (filters.arrayJobId != null)
items.push({ arrayJobId: filters.arrayJobId });
if (filters.numNodes.from != null || filters.numNodes.to != null)
if (filters.numNodes.from != null || filters.numNodes.to != null) {
items.push({
numNodes: { from: filters.numNodes.from, to: filters.numNodes.to },
});
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null)
isNodesModified = true;
}
if (filters.numHWThreads.from != null || filters.numHWThreads.to != null) {
items.push({
numHWThreads: {
from: filters.numHWThreads.from,
to: filters.numHWThreads.to,
},
});
if (
filters.numAccelerators.from != null ||
filters.numAccelerators.to != null
)
isHwthreadsModified = true;
}
if (filters.numAccelerators.from != null || filters.numAccelerators.to != null) {
items.push({
numAccelerators: {
from: filters.numAccelerators.from,
to: filters.numAccelerators.to,
},
});
isAccsModified = true;
}
if (filters.user)
items.push({ user: { [filters.userMatch]: filters.user } });
if (filters.project)
@@ -197,10 +200,10 @@
opts.push(`energy=${filters.energy.from}-${filters.energy.to}`);
if (filters.numNodes.from && filters.numNodes.to)
opts.push(`numNodes=${filters.numNodes.from}-${filters.numNodes.to}`);
if (filters.numHWThreads.from && filters.numHWThreads.to)
opts.push(`numHWThreads=${filters.numHWThreads.from}-${filters.numHWThreads.to}`);
if (filters.numAccelerators.from && filters.numAccelerators.to)
opts.push(
`numAccelerators=${filters.numAccelerators.from}-${filters.numAccelerators.to}`,
);
opts.push(`numAccelerators=${filters.numAccelerators.from}-${filters.numAccelerators.to}`);
if (filters.user.length != 0)
if (filters.userMatch != "in") {
opts.push(`user=${filters.user}`);
@@ -214,7 +217,10 @@
if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`);
if (filters.project && filters.projectMatch != "contains")
opts.push(`projectMatch=${filters.projectMatch}`);
if (filters.stats.length != 0)
for (let stat of filters.stats) {
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
}
if (opts.length == 0 && window.location.search.length <= 1) return;
let newurl = `${window.location.pathname}?${opts.join("&")}`;
@@ -364,8 +370,7 @@
{#if (isNodesModified || isHwthreadsModified) && isAccsModified},
{/if}
{#if isAccsModified}
Accelerators: {filters.numAccelerators.from} - {filters
.numAccelerators.to}
Accelerators: {filters.numAccelerators.from} - {filters.numAccelerators.to}
{/if}
</Info>
{/if}
@@ -385,7 +390,7 @@
{#if filters.stats.length > 0}
<Info icon="bar-chart" on:click={() => (isStatsOpen = true)}>
{filters.stats
.map((stat) => `${stat.text}: ${stat.from} - ${stat.to}`)
.map((stat) => `${stat.field}: ${stat.from} - ${stat.to}`)
.join(", ")}
</Info>
{/if}

View File

@@ -30,6 +30,10 @@
initialized = getContext("initialized"),
globalMetrics = getContext("globalMetrics");
const equalsCheck = (a, b) => {
return JSON.stringify(a) === JSON.stringify(b);
}
export let sorting = { field: "startTime", type: "col", order: "DESC" };
export let matchedJobs = 0;
export let metrics = ccconfig.plot_list_selectedMetrics;
@@ -40,6 +44,8 @@
let page = 1;
let paging = { itemsPerPage, page };
let filter = [];
let lastFilter = [];
let lastSorting = null;
let triggerMetricRefresh = false;
function getUnit(m) {
@@ -105,9 +111,34 @@
variables: { paging, sorting, filter },
});
let jobs = []
$: if (!usePaging && sorting) {
// console.log('Reset Paging ...')
paging = { itemsPerPage: 10, page: 1 }
};
let jobs = [];
$: if ($initialized && $jobsStore.data) {
jobs = [...$jobsStore.data.jobs.items]
if (usePaging) {
jobs = [...$jobsStore.data.jobs.items]
} else { // Prevents jump to table head in continiuous mode, only if no change in sort or filter
if (equalsCheck(filter, lastFilter) && equalsCheck(sorting, lastSorting)) {
// console.log('Both Equal: Continuous Addition ... Set None')
jobs = jobs.concat([...$jobsStore.data.jobs.items])
} else if (equalsCheck(filter, lastFilter)) {
// console.log('Filter Equal: Continuous Reset ... Set lastSorting')
lastSorting = { ...sorting }
jobs = [...$jobsStore.data.jobs.items]
} else if (equalsCheck(sorting, lastSorting)) {
// console.log('Sorting Equal: Continuous Reset ... Set lastFilter')
lastFilter = [ ...filter ]
jobs = [...$jobsStore.data.jobs.items]
} else {
// console.log('None Equal: Continuous Reset ... Set lastBoth')
lastSorting = { ...sorting }
lastFilter = [ ...filter ]
jobs = [...$jobsStore.data.jobs.items]
}
}
}
$: matchedJobs = $jobsStore.data != null ? $jobsStore.data.jobs.count : -1;
@@ -170,7 +201,6 @@
}
if (!usePaging) {
let scrollMultiplier = 1
window.addEventListener('scroll', () => {
let {
scrollTop,
@@ -181,8 +211,7 @@
// Add 100 px offset to trigger load earlier
if (scrollTop + clientHeight >= scrollHeight - 100 && $jobsStore.data != null && $jobsStore.data.jobs.hasNextPage) {
let pendingPaging = { ...paging }
scrollMultiplier += 1
pendingPaging.itemsPerPage = itemsPerPage * scrollMultiplier
pendingPaging.page += 1
paging = pendingPaging
};
});

View File

@@ -77,6 +77,13 @@
dispatch("set-filter", { states });
}}>Close & Apply</Button
>
<Button
color="warning"
on:click={() => {
states = [...allJobStates];
pendingStates = [];
}}>Deselect All</Button
>
<Button
color="danger"
on:click={() => {

View File

@@ -5,10 +5,10 @@
- `cluster Object?`: The currently selected cluster config [Default: null]
- `isOpen Bool?`: Is this filter component opened [Default: false]
- `numNodes Object?`: The currently selected numNodes filter [Default: {from:null, to:null}]
- `numHWThreads Object?`: The currently selected numHWTreads 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]
- `isHwtreadsModified Bool?`: Is the Hwthreads 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]
@@ -60,7 +60,7 @@
);
// Limited to Single-Node Thread Count
const findMaxNumHWTreadsPerNode = (clusters) =>
const findMaxNumHWThreadsPerNode = (clusters) =>
clusters.reduce(
(max, cluster) =>
Math.max(
@@ -91,13 +91,13 @@
minNumNodes = filterRanges.numNodes.from;
maxNumNodes = filterRanges.numNodes.to;
maxNumAccelerators = findMaxNumAccels([{ subClusters }]);
maxNumHWThreads = findMaxNumHWTreadsPerNode([{ subClusters }]);
maxNumHWThreads = findMaxNumHWThreadsPerNode([{ subClusters }]);
} else if (clusters.length > 0) {
const { filterRanges } = header.clusters[0];
minNumNodes = filterRanges.numNodes.from;
maxNumNodes = filterRanges.numNodes.to;
maxNumAccelerators = findMaxNumAccels(clusters);
maxNumHWThreads = findMaxNumHWTreadsPerNode(clusters);
maxNumHWThreads = findMaxNumHWThreadsPerNode(clusters);
for (let cluster of header.clusters) {
const { filterRanges } = cluster;
minNumNodes = Math.min(minNumNodes, filterRanges.numNodes.from);

View File

@@ -29,10 +29,11 @@
export let isOpen = false;
export let stats = [];
let statistics = []
let statistics = [];
function loadRanges(isInitialized) {
if (!isInitialized) return;
statistics = getStatsItems();
statistics = getStatsItems(stats);
}
function resetRanges() {

View File

@@ -8,44 +8,6 @@
- `height String?`: Height of the card [Default: '310px']
-->
<script context="module">
function findJobThresholds(job, metricConfig) {
if (!job || !metricConfig) {
console.warn("Argument missing for findJobThresholds!");
return null;
}
// metricConfig is on subCluster-Level
const defaultThresholds = {
peak: metricConfig.peak,
normal: metricConfig.normal,
caution: metricConfig.caution,
alert: metricConfig.alert
};
// Job_Exclusivity does not matter, only aggregation
if (metricConfig.aggregation === "avg") {
return defaultThresholds;
} else if (metricConfig.aggregation === "sum") {
const topol = getContext("getHardwareTopology")(job.cluster, job.subCluster)
const jobFraction = job.numHWThreads / topol.node.length;
return {
peak: round(defaultThresholds.peak * jobFraction, 0),
normal: round(defaultThresholds.normal * jobFraction, 0),
caution: round(defaultThresholds.caution * jobFraction, 0),
alert: round(defaultThresholds.alert * jobFraction, 0),
};
} else {
console.warn(
"Missing or unkown aggregation mode (sum/avg) for metric:",
metricConfig,
);
return defaultThresholds;
}
}
</script>
<script>
import { getContext } from "svelte";
import {
@@ -59,7 +21,7 @@
Row,
Col
} from "@sveltestrap/sveltestrap";
import { round } from "mathjs";
import { findJobFootprintThresholds } from "../utils.js";
export let job;
export let displayTitle = true;
@@ -73,8 +35,7 @@
const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "")
// Threshold / -Differences
const fmt = findJobThresholds(job, fmc);
if (jf.name === "flops_any") fmt.peak = round(fmt.peak * 0.85, 0);
const fmt = findJobFootprintThresholds(job, jf.stat, fmc);
// Define basic data -> Value: Use as Provided
const fmBase = {
@@ -89,21 +50,21 @@
return {
...fmBase,
color: "danger",
message: `Metric average way ${fmc.lowerIsBetter ? "above" : "below"} expected normal thresholds.`,
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: `Metric average ${fmc.lowerIsBetter ? "above" : "below"} expected normal thresholds.`,
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: "Metric average within expected thresholds.",
message: "Footprint value within expected thresholds.",
impact: 1,
};
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) {
@@ -111,7 +72,7 @@
...fmBase,
color: "info",
message:
"Metric average above expected normal thresholds: Check for artifacts recommended.",
"Footprint value above expected normal threshold: Check for artifacts recommended.",
impact: 0,
};
} else {
@@ -119,7 +80,7 @@
...fmBase,
color: "secondary",
message:
"Metric average above expected peak threshold: Check for artifacts!",
"Footprint value above expected peak threshold: Check for artifacts!",
impact: -1,
};
}
@@ -136,25 +97,25 @@
return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
});;
function evalFootprint(mean, thresholds, lowerIsBetter, level) {
function evalFootprint(value, thresholds, lowerIsBetter, level) {
// Handle Metrics in which less value is better
switch (level) {
case "peak":
if (lowerIsBetter)
return false; // metric over peak -> return false to trigger impact -1
else return mean <= thresholds.peak && mean > thresholds.normal;
else return value <= thresholds.peak && value > thresholds.normal;
case "alert":
if (lowerIsBetter)
return mean <= thresholds.peak && mean >= thresholds.alert;
else return mean <= thresholds.alert && mean >= 0;
return value <= thresholds.peak && value >= thresholds.alert;
else return value <= thresholds.alert && value >= 0;
case "caution":
if (lowerIsBetter)
return mean < thresholds.alert && mean >= thresholds.caution;
else return mean <= thresholds.caution && mean > thresholds.alert;
return value < thresholds.alert && value >= thresholds.caution;
else return value <= thresholds.caution && value > thresholds.alert;
case "normal":
if (lowerIsBetter)
return mean < thresholds.caution && mean >= 0;
else return mean <= thresholds.normal && mean > thresholds.caution;
return value < thresholds.caution && value >= 0;
else return value <= thresholds.normal && value > thresholds.caution;
default:
return false;
}
@@ -181,10 +142,14 @@
>
<div class="mx-1">
<!-- Alerts Only -->
{#if fpd.impact === 3 || fpd.impact === -1}
<Icon name="exclamation-triangle-fill" class="text-danger" />
{#if fpd.impact === 3}
<Icon name="exclamation-triangle-fill" class="text-danger" />
{:else if fpd.impact === 2}
<Icon name="exclamation-triangle" class="text-warning" />
{:else if fpd.impact === 0}
<Icon name="info-circle" class="text-info" />
{:else if fpd.impact === -1}
<Icon name="info-circle-fill" class="text-danger" />
{/if}
<!-- Emoji for all states-->
{#if fpd.impact === 3}
@@ -194,7 +159,7 @@
{:else if fpd.impact === 1}
<Icon name="emoji-smile" class="text-success" />
{:else if fpd.impact === 0}
<Icon name="emoji-laughing" class="text-info" />
<Icon name="emoji-smile" class="text-info" />
{:else if fpd.impact === -1}
<Icon name="emoji-dizzy" class="text-danger" />
{/if}

View File

@@ -120,10 +120,13 @@
function matchJobTags(tags, availableTags, type, isAdmin, isSupport) {
const jobTagIds = tags.map((t) => t.id)
if (isAdmin || type == 'used') { // Always show used tags, admin also show all unused
if (type == 'used') { // Always show used tags
return availableTags.filter((at) => jobTagIds.includes(at.id))
} else { // ... for unused
if (isSupport) { // ... show global tags for support
if (isAdmin) { // ... show all tags for admin
return availableTags.filter((at) => !jobTagIds.includes(at.id))
} else if (isSupport) { // ... show global tags for support
return availableTags.filter((at) => !jobTagIds.includes(at.id) && at.scope !== "admin")
} else { // ... show only private tags for user, manager
return availableTags.filter((at) => !jobTagIds.includes(at.id) && at.scope !== "admin" && at.scope !== "global")

View File

@@ -7,7 +7,7 @@
-->
<script>
import { Badge, Button, Icon } from "@sveltestrap/sveltestrap";
import { Badge, Button, Icon, Tooltip } from "@sveltestrap/sveltestrap";
import { scrambleNames, scramble } from "../utils.js";
import Tag from "../helper/Tag.svelte";
import TagManagement from "../helper/TagManagement.svelte";
@@ -42,12 +42,30 @@
let displayCheck = false;
function clipJobId(jid) {
displayCheck = true;
navigator.clipboard
.writeText(jid)
.catch((reason) => console.error(reason));
setTimeout(function () {
displayCheck = false;
}, 1500);
// Navigator clipboard api needs a secure context (https)
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard
.writeText(jid)
.catch((reason) => console.error(reason));
} else {
// Workaround: Create, Fill, And Copy Content of Textarea
const textArea = document.createElement("textarea");
textArea.value = jid;
textArea.style.position = "absolute";
textArea.style.left = "-999999px";
document.body.prepend(textArea);
textArea.select();
try {
document.execCommand('copy');
} catch (error) {
console.error(error);
} finally {
textArea.remove();
}
}
setTimeout(function () {
displayCheck = false;
}, 1000);
}
</script>
@@ -58,13 +76,18 @@
<a href="/monitoring/job/{job.id}" target="_blank">{job.jobId}</a>
({job.cluster})
</span>
<Button outline color="secondary" size="sm" title="Copy JobID to Clipboard" on:click={clipJobId(job.jobId)} >
<Button id={`${job.cluster}-${job.jobId}-clipboard`} outline color="secondary" size="sm" on:click={clipJobId(job.jobId)} >
{#if displayCheck}
<Icon name="clipboard2-check-fill"/> Copied
<Icon name="clipboard2-check-fill"/>
{:else}
<Icon name="clipboard2"/> Job ID
<Icon name="clipboard2"/>
{/if}
</Button>
<Tooltip
target={`${job.cluster}-${job.jobId}-clipboard`}
placement="right">
{ displayCheck ? 'Copied!' : 'Copy Job ID to Clipboard' }
</Tooltip>
</span>
{#if job.metaData?.jobName}
{#if job.metaData?.jobName.length <= 25}

View File

@@ -37,6 +37,7 @@
: ["node"];
let selectedResolution = resampleDefault;
let zoomStates = {};
let thresholdStates = {};
const cluster = getContext("clusters").find((c) => c.name == job.cluster);
const client = getContextClient();
@@ -80,6 +81,13 @@
zoomStates[metric] = {...detail.lastZoomState}
}
if ( // States have to differ, causes deathloop if just set
detail?.lastThreshold &&
thresholdStates[metric] !== detail.lastThreshold
) { // Handle to correctly reset on summed metric scope change
thresholdStates[metric] = detail.lastThreshold;
}
if (detail?.newRes) { // Triggers GQL
selectedResolution = detail.newRes
}
@@ -191,6 +199,7 @@
numhwthreads={job.numHWThreads}
numaccs={job.numAcc}
zoomState={zoomStates[metric.data.name] || null}
thresholdState={thresholdStates[metric.data.name] || null}
/>
{:else if metric.disabled == true && metric.data}
<Card body color="info"

View File

@@ -50,7 +50,7 @@
}
// removed arg "subcluster": input metricconfig and topology now directly derived from subcluster
function findThresholds(
function findJobAggregationThresholds(
subClusterTopology,
metricConfig,
scope,
@@ -60,10 +60,16 @@
) {
if (!subClusterTopology || !metricConfig || !scope) {
console.warn("Argument missing for findThresholds!");
console.warn("Argument missing for findJobAggregationThresholds!");
return null;
}
// handle special *-stat scopes
if (scope.match(/(.*)-stat$/)) {
const statParts = scope.split('-');
scope = statParts[0]
}
if (
(scope == "node" && isShared == false) ||
metricConfig?.aggregation == "avg"
@@ -78,19 +84,20 @@
if (metricConfig?.aggregation == "sum") {
let divisor = 1
let divisor;
if (isShared == true) { // Shared
if (numaccs > 0) divisor = subClusterTopology.accelerators.length / numaccs;
else if (numhwthreads > 0) divisor = subClusterTopology.node.length / numhwthreads;
else if (numhwthreads > 0) divisor = subClusterTopology.core.length / numhwthreads;
}
else if (scope == 'socket') divisor = subClusterTopology.socket.length;
else if (scope == "core") divisor = subClusterTopology.core.length;
else if (scope == "accelerator")
divisor = subClusterTopology.accelerators.length;
else if (scope == "hwthread") divisor = subClusterTopology.node.length;
else if (scope == 'node') divisor = 1; // Use as configured for nodes
else if (scope == 'socket') divisor = subClusterTopology.socket.length;
else if (scope == "memoryDomain") divisor = subClusterTopology.memoryDomain.length;
else if (scope == "core") divisor = subClusterTopology.core.length;
else if (scope == "hwthread") divisor = subClusterTopology.core.length; // alt. name for core
else if (scope == "accelerator") divisor = subClusterTopology.accelerators.length;
else {
// console.log('TODO: how to calc thresholds for ', scope)
return null;
console.log('Unknown scope, return default aggregation thresholds ', scope)
divisor = 1;
}
return {
@@ -130,6 +137,7 @@
export let numhwthreads = 0;
export let numaccs = 0;
export let zoomState = null;
export let thresholdState = null;
if (useStatsSeries == null) useStatsSeries = statisticsSeries != null;
if (useStatsSeries == false && series == null) useStatsSeries = true;
@@ -149,7 +157,7 @@
caution: "rgba(255, 128, 0, 0.3)",
alert: "rgba(255, 0, 0, 0.3)",
};
const thresholds = findThresholds(
const thresholds = findJobAggregationThresholds(
subClusterTopology,
metricConfig,
scope,
@@ -468,12 +476,14 @@
// console.log('Dispatch Zoom with Res from / to', timestep, closest)
dispatch('zoom', {
newRes: closest,
lastZoomState: u?.scales
lastZoomState: u?.scales,
lastThreshold: thresholds?.normal
});
}
} else {
dispatch('zoom', {
lastZoomState: u?.scales
lastZoomState: u?.scales,
lastThreshold: thresholds?.normal
});
};
};
@@ -498,16 +508,19 @@
let timeoutId = null;
function render(ren_width, ren_height) {
if (!uplot) { // Init uPlot
if (!uplot) {
opts.width = ren_width;
opts.height = ren_height;
if (zoomState) {
if (zoomState && metricConfig?.aggregation == "avg") {
opts.scales = {...zoomState}
} else if (zoomState && metricConfig?.aggregation == "sum") {
// Allow Zoom In === Ymin changed
if (zoomState.y.min !== 0) { // scope change?: only use zoomState if thresholds match
if ((thresholdState === thresholds?.normal)) { opts.scales = {...zoomState} };
} // else: reset scaling to default
}
// console.log('Init Sizes ...', { width: opts.width, height: opts.height })
uplot = new uPlot(opts, plotData, plotWrapper);
} else { // Update size
// console.log('Update uPlot ...', { width: ren_width, height: ren_height })
} else {
uplot.setSize({ width: ren_width, height: ren_height });
}
}

View File

@@ -45,7 +45,7 @@
if (footprintData) {
return footprintData.filter(fpd => {
if (!jobMetrics.find(m => m.name == fpd.name && m.scope == "node" || fpd.impact == 4)) {
console.warn(`PolarPlot: No metric data (or config) for '${fpd.name}'`)
console.warn(`PolarPlot: No metric data for '${fpd.name}'`)
return false
}
return true
@@ -72,6 +72,7 @@
const getMetricConfig = getContext("getMetricConfig");
const getValuesForStatGeneric = (getStat) => labels.map(name => {
// TODO: Requires Scaling if Shared Job
const peak = getMetricConfig(cluster, subCluster, name).peak
const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
const value = getStat(metric.metric) / peak
@@ -79,6 +80,7 @@
})
const getValuesForStatFootprint = (getStat) => labels.map(name => {
// FootprintData 'Peak' is pre-scaled for Shared Jobs in JobSummary Component
const peak = footprintData.find(fpd => fpd.name === name).peak
const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
const value = getStat(metric.metric) / peak
@@ -86,14 +88,21 @@
})
function getMax(metric) {
let max = 0
let max = metric.series[0].statistics.max;
for (let series of metric.series)
max = Math.max(max, series.statistics.max)
return max
}
function getMin(metric) {
let min = metric.series[0].statistics.min;
for (let series of metric.series)
min = Math.min(min, series.statistics.min)
return min
}
function getAvg(metric) {
let avg = 0
let avg = 0;
for (let series of metric.series)
avg += series.statistics.avg
return avg / metric.series.length
@@ -104,6 +113,8 @@
return getValuesForStatGeneric(getAvg)
} else if (type === 'max') {
return getValuesForStatGeneric(getMax)
} else if (type === 'min') {
return getValuesForStatGeneric(getMin)
}
console.log('Unknown Type For Polar Data')
return []
@@ -114,6 +125,8 @@
return getValuesForStatFootprint(getAvg)
} else if (type === 'max') {
return getValuesForStatFootprint(getMax)
} else if (type === 'min') {
return getValuesForStatFootprint(getMin)
}
console.log('Unknown Type For Polar Data')
return []
@@ -124,25 +137,36 @@
datasets: [
{
label: 'Max',
data: footprintData ? loadDataForFootprint('max') : loadDataGeneric('max'), //
data: footprintData ? loadDataForFootprint('max') : loadDataGeneric('max'), // Node Scope Only
fill: 1,
backgroundColor: 'rgba(0, 102, 255, 0.25)',
borderColor: 'rgb(0, 102, 255)',
pointBackgroundColor: 'rgb(0, 102, 255)',
backgroundColor: 'rgba(0, 0, 255, 0.25)',
borderColor: 'rgb(0, 0, 255)',
pointBackgroundColor: 'rgb(0, 0, 255)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(0, 102, 255)'
pointHoverBorderColor: 'rgb(0, 0, 255)'
},
{
label: 'Avg',
data: footprintData ? loadDataForFootprint('avg') : loadDataGeneric('avg'), // getValuesForStat(getAvg)
fill: true,
backgroundColor: 'rgba(255, 153, 0, 0.25)',
borderColor: 'rgb(255, 153, 0)',
pointBackgroundColor: 'rgb(255, 153, 0)',
data: footprintData ? loadDataForFootprint('avg') : loadDataGeneric('avg'), // Node Scope Only
fill: 2,
backgroundColor: 'rgba(255, 210, 0, 0.25)',
borderColor: 'rgb(255, 210, 0)',
pointBackgroundColor: 'rgb(255, 210, 0)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(255, 153, 0)'
pointHoverBorderColor: 'rgb(255, 210, 0)'
},
{
label: 'Min',
data: footprintData ? loadDataForFootprint('min') : loadDataGeneric('min'), // Node Scope Only
fill: true,
backgroundColor: 'rgba(255, 0, 0, 0.25)',
borderColor: 'rgb(255, 0, 0)',
pointBackgroundColor: 'rgb(255, 0, 0)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgb(255, 0, 0)'
}
]
}

View File

@@ -6,6 +6,7 @@ import {
} from "@urql/svelte";
import { setContext, getContext, hasContext, onDestroy, tick } from "svelte";
import { readable } from "svelte/store";
import { round } from "mathjs";
/*
* Call this function only at component initialization time!
@@ -318,23 +319,34 @@ export function checkMetricsDisabled(ma, c, s) { // [m]etric[a]rray, [c]luster,
return result
}
export function getStatsItems() {
export function getStatsItems(presetStats = []) {
// console.time('stats')
const globalMetrics = getContext("globalMetrics")
const result = globalMetrics.map((gm) => {
if (gm?.footprint) {
// console.time('deep')
const mc = getMetricConfigDeep(gm.name, null, null)
// console.timeEnd('deep')
if (mc) {
return {
field: gm.name + '_' + gm.footprint,
text: gm.name + ' (' + gm.footprint + ')',
metric: gm.name,
from: 0,
to: mc.peak,
peak: mc.peak,
enabled: false
const presetEntry = presetStats.find((s) => s?.field === (gm.name + '_' + gm.footprint))
if (presetEntry) {
return {
field: gm.name + '_' + gm.footprint,
text: gm.name + ' (' + gm.footprint + ')',
metric: gm.name,
from: presetEntry.from,
to: presetEntry.to,
peak: mc.peak,
enabled: true
}
} else {
return {
field: gm.name + '_' + gm.footprint,
text: gm.name + ' (' + gm.footprint + ')',
metric: gm.name,
from: 0,
to: mc.peak,
peak: mc.peak,
enabled: false
}
}
}
}
@@ -344,6 +356,38 @@ export function getStatsItems() {
return [...result];
};
export function findJobFootprintThresholds(job, stat, metricConfig) {
if (!job || !metricConfig || !stat) {
console.warn("Argument missing for findJobThresholds!");
return null;
}
// metricConfig is on subCluster-Level
const defaultThresholds = {
peak: metricConfig.peak,
normal: metricConfig.normal,
caution: metricConfig.caution,
alert: metricConfig.alert
};
/*
Footprints should be comparable:
Always use unchanged single node thresholds for exclusive jobs and "avg" Footprints.
For shared jobs, scale thresholds by the fraction of the job's HWThreads to the node's HWThreads.
'stat' is one of: avg, min, max
*/
if (job.exclusive === 1 || stat === "avg") {
return defaultThresholds
} else {
const topol = getContext("getHardwareTopology")(job.cluster, job.subCluster)
const jobFraction = job.numHWThreads / topol.node.length;
return {
peak: round(defaultThresholds.peak * jobFraction, 0),
normal: round(defaultThresholds.normal * jobFraction, 0),
caution: round(defaultThresholds.caution * jobFraction, 0),
alert: round(defaultThresholds.alert * jobFraction, 0),
};
}
}
export function getSortItems() {
//console.time('sort')
const globalMetrics = getContext("globalMetrics")

View File

@@ -8,44 +8,6 @@
- `height String?`: Height of the card [Default: '310px']
-->
<script context="module">
function findJobThresholds(job, metricConfig) {
if (!job || !metricConfig) {
console.warn("Argument missing for findJobThresholds!");
return null;
}
// metricConfig is on subCluster-Level
const defaultThresholds = {
peak: metricConfig.peak,
normal: metricConfig.normal,
caution: metricConfig.caution,
alert: metricConfig.alert
};
// Job_Exclusivity does not matter, only aggregation
if (metricConfig.aggregation === "avg") {
return defaultThresholds;
} else if (metricConfig.aggregation === "sum") {
const topol = getContext("getHardwareTopology")(job.cluster, job.subCluster)
const jobFraction = job.numHWThreads / topol.node.length;
return {
peak: round(defaultThresholds.peak * jobFraction, 0),
normal: round(defaultThresholds.normal * jobFraction, 0),
caution: round(defaultThresholds.caution * jobFraction, 0),
alert: round(defaultThresholds.alert * jobFraction, 0),
};
} else {
console.warn(
"Missing or unkown aggregation mode (sum/avg) for metric:",
metricConfig,
);
return defaultThresholds;
}
}
</script>
<script>
import { getContext } from "svelte";
import {
@@ -60,7 +22,7 @@
TabPane
} from "@sveltestrap/sveltestrap";
import Polar from "../generic/plots/Polar.svelte";
import { round } from "mathjs";
import { findJobFootprintThresholds } from "../generic/utils.js";
export let job;
export let jobMetrics;
@@ -77,8 +39,7 @@
const unit = (fmc?.unit?.prefix ? fmc.unit.prefix : "") + (fmc?.unit?.base ? fmc.unit.base : "")
// Threshold / -Differences
const fmt = findJobThresholds(job, fmc);
if (jf.name === "flops_any") fmt.peak = round(fmt.peak * 0.85, 0);
const fmt = findJobFootprintThresholds(job, jf.stat, fmc);
// Define basic data -> Value: Use as Provided
const fmBase = {
@@ -94,21 +55,21 @@
return {
...fmBase,
color: "danger",
message: `Metric average way ${fmc.lowerIsBetter ? "above" : "below"} expected normal thresholds.`,
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: `Metric average ${fmc.lowerIsBetter ? "above" : "below"} expected normal thresholds.`,
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: "Metric average within expected thresholds.",
message: "Footprint value within expected thresholds.",
impact: 1,
};
} else if (evalFootprint(jf.value, fmt, fmc.lowerIsBetter, "peak")) {
@@ -116,7 +77,7 @@
...fmBase,
color: "info",
message:
"Metric average above expected normal thresholds: Check for artifacts recommended.",
"Footprint value above expected normal threshold: Check for artifacts recommended.",
impact: 0,
};
} else {
@@ -124,7 +85,7 @@
...fmBase,
color: "secondary",
message:
"Metric average above expected peak threshold: Check for artifacts!",
"Footprint value above expected peak threshold: Check for artifacts!",
impact: -1,
};
}
@@ -142,25 +103,25 @@
return a.impact - b.impact || ((a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
});;
function evalFootprint(mean, thresholds, lowerIsBetter, level) {
function evalFootprint(value, thresholds, lowerIsBetter, level) {
// Handle Metrics in which less value is better
switch (level) {
case "peak":
if (lowerIsBetter)
return false; // metric over peak -> return false to trigger impact -1
else return mean <= thresholds.peak && mean > thresholds.normal;
else return value <= thresholds.peak && value > thresholds.normal;
case "alert":
if (lowerIsBetter)
return mean <= thresholds.peak && mean >= thresholds.alert;
else return mean <= thresholds.alert && mean >= 0;
return value <= thresholds.peak && value >= thresholds.alert;
else return value <= thresholds.alert && value >= 0;
case "caution":
if (lowerIsBetter)
return mean < thresholds.alert && mean >= thresholds.caution;
else return mean <= thresholds.caution && mean > thresholds.alert;
return value < thresholds.alert && value >= thresholds.caution;
else return value <= thresholds.caution && value > thresholds.alert;
case "normal":
if (lowerIsBetter)
return mean < thresholds.caution && mean >= 0;
else return mean <= thresholds.normal && mean > thresholds.caution;
return value < thresholds.caution && value >= 0;
else return value <= thresholds.normal && value > thresholds.caution;
default:
return false;
}
@@ -244,10 +205,14 @@
id={`footprint-${job.jobId}-${index}`}
>
<div class="mx-1">
{#if fpd.impact === 3 || fpd.impact === -1}
{#if fpd.impact === 3}
<Icon name="exclamation-triangle-fill" class="text-danger" />
{:else if fpd.impact === 2}
<Icon name="exclamation-triangle" class="text-warning" />
{:else if fpd.impact === 0}
<Icon name="info-circle" class="text-info" />
{:else if fpd.impact === -1}
<Icon name="info-circle-fill" class="text-danger" />
{/if}
{#if fpd.impact === 3}
<Icon name="emoji-frown" class="text-danger" />
@@ -256,7 +221,7 @@
{:else if fpd.impact === 1}
<Icon name="emoji-smile" class="text-success" />
{:else if fpd.impact === 0}
<Icon name="emoji-laughing" class="text-info" />
<Icon name="emoji-smile" class="text-info" />
{:else if fpd.impact === -1}
<Icon name="emoji-dizzy" class="text-danger" />
{/if}

View File

@@ -54,6 +54,7 @@
let statsSeries = rawData.map((data) => data?.statisticsSeries ? data.statisticsSeries : null);
let zoomState = null;
let pendingZoomState = null;
let thresholdState = null;
const dispatch = createEventDispatcher();
const statsPattern = /(.*)-stat$/;
@@ -96,18 +97,24 @@
(pendingZoomState?.x?.min !== detail?.lastZoomState?.x?.min) &&
(pendingZoomState?.y?.max !== detail?.lastZoomState?.y?.max)
) {
pendingZoomState = {...detail.lastZoomState}
pendingZoomState = {...detail.lastZoomState};
}
if (detail?.lastThreshold) { // Handle to correctly reset on summed metric scope change
thresholdState = detail.lastThreshold;
} else {
thresholdState = null;
}
if (detail?.newRes) { // Triggers GQL
pendingResolution = detail.newRes
pendingResolution = detail.newRes;
}
}
};
let metricData;
let selectedScopes = [...scopes]
let selectedScopes = [...scopes];
const dbid = job.id;
const selectedMetrics = [metricName]
const selectedMetrics = [metricName];
$: if (selectedScope || pendingResolution) {
@@ -206,9 +213,12 @@
timestep={data.timestep}
scope={selectedScope}
metric={metricName}
numaccs={job.numAcc}
numhwthreads={job.numHWThreads}
{series}
{isShared}
{zoomState}
{thresholdState}
/>
{:else if statsSeries[selectedScopeIndex] != null && patternMatches}
<Timeseries
@@ -218,9 +228,12 @@
timestep={data.timestep}
scope={selectedScope}
metric={metricName}
numaccs={job.numAcc}
numhwthreads={job.numHWThreads}
{series}
{isShared}
{zoomState}
{thresholdState}
statisticsSeries={statsSeries[selectedScopeIndex]}
useStatsSeries={!!statsSeries[selectedScopeIndex]}
/>

View File

@@ -13,6 +13,7 @@
const filterPresets = {{ .FilterPresets }};
const clusterCockpitConfig = {{ .Config }};
const resampleConfig = {{ .Resampling }};
const ncontent = {{ .Infos.ncontent }};
</script>
<script src='/build/config.js'></script>
{{end}}

View File

@@ -41,6 +41,7 @@
{{- if .Infos.hasOpenIDConnect}}
<a class="btn btn-primary" href="/oidc-login">OpenID Connect Login</a>
{{end}}
<input type="hidden" id="redirect" name="redirect" value="{{ .Redirect }}" />
</form>
</div>
</div>

View File

@@ -99,6 +99,7 @@ type Page struct {
Infos map[string]interface{} // For generic use (e.g. username for /monitoring/user/<id>, job id for /monitoring/job/<id>)
Config map[string]interface{} // UI settings for the currently logged in user (e.g. line width, ...)
Resampling *schema.ResampleConfig // If not nil, defines resampling trigger and resolutions
Redirect string // The originally requested URL, for intermediate login handling
}
func RenderTemplate(rw http.ResponseWriter, file string, page *Page) {