mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-10-31 07:55:06 +01:00 
			
		
		
		
	Continue fixing errors
This commit is contained in:
		| @@ -6,7 +6,7 @@ import terser from '@rollup/plugin-terser'; | |||||||
| import css from 'rollup-plugin-css-only'; | import css from 'rollup-plugin-css-only'; | ||||||
|  |  | ||||||
| // const production = !process.env.ROLLUP_WATCH; | // const production = !process.env.ROLLUP_WATCH; | ||||||
| const production = true | const production = false | ||||||
|  |  | ||||||
| const plugins = [ | const plugins = [ | ||||||
|     svelte({ |     svelte({ | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
|     import Filters from "./filters/Filters.svelte"; |     import Filters from "./filters/Filters.svelte"; | ||||||
|     import { queryStore, gql, getContextClient } from "@urql/svelte"; |     import { queryStore, gql, getContextClient } from "@urql/svelte"; | ||||||
|     import { scramble, scrambleNames } from "./joblist/JobInfo.svelte"; |     import { scramble, scrambleNames } from "./joblist/JobInfo.svelte"; | ||||||
|  |   import { UniqueInputFieldNamesRule } from "graphql"; | ||||||
|  |  | ||||||
|     const {} = init(); |     const {} = init(); | ||||||
|  |  | ||||||
| @@ -20,7 +21,9 @@ | |||||||
|         "Invalid list type provided!" |         "Invalid list type provided!" | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     const stats = queryStore({ |     let filters; | ||||||
|  |  | ||||||
|  |     $: stats = queryStore({ | ||||||
|         client: getContextClient(), |         client: getContextClient(), | ||||||
|         query: gql` |         query: gql` | ||||||
|         query($filter: [JobFilter!]!) { |         query($filter: [JobFilter!]!) { | ||||||
| @@ -32,10 +35,10 @@ | |||||||
|             totalCoreHours |             totalCoreHours | ||||||
|         } |         } | ||||||
|     }`, |     }`, | ||||||
|         variables: { filter, type }, |         variables: { filters }, | ||||||
|  |         pause: true | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     let filters; |  | ||||||
|     let nameFilter = ""; |     let nameFilter = ""; | ||||||
|     let sorting = { field: "totalJobs", direction: "down" }; |     let sorting = { field: "totalJobs", direction: "down" }; | ||||||
|  |  | ||||||
| @@ -88,9 +91,8 @@ | |||||||
|             startTimeQuickSelect={true} |             startTimeQuickSelect={true} | ||||||
|             menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up" |             menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up" | ||||||
|             on:update={({ detail }) => { |             on:update={({ detail }) => { | ||||||
|                 $stats.variables = { filter: detail.filters }; |                 filters = detail.filters; | ||||||
|                 $stats.context.pause = false; |                 stats.resume(); | ||||||
|                 $stats.reexecute(); |  | ||||||
|             }} |             }} | ||||||
|         /> |         /> | ||||||
|     </Col> |     </Col> | ||||||
|   | |||||||
| @@ -81,8 +81,6 @@ | |||||||
|         $jobs.reexecute({ requestPolicy: 'network-only' }) |         $jobs.reexecute({ requestPolicy: 'network-only' }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     query(jobs) |  | ||||||
|  |  | ||||||
|     let tableWidth = null |     let tableWidth = null | ||||||
|     let jobInfoColumnWidth = 250 |     let jobInfoColumnWidth = 250 | ||||||
|     $: plotWidth = Math.floor((tableWidth - jobInfoColumnWidth) / metrics.length - 10) |     $: plotWidth = Math.floor((tableWidth - jobInfoColumnWidth) / metrics.length - 10) | ||||||
|   | |||||||
| @@ -1,7 +1,11 @@ | |||||||
| import { expiringCacheExchange } from './cache-exchange.js' | import { expiringCacheExchange } from "./cache-exchange.js"; | ||||||
| import { Client, setContextClient, fetchExchange } from '@urql/svelte'; | import { | ||||||
| import { setContext, getContext, hasContext, onDestroy, tick } from 'svelte' |   Client, | ||||||
| import { readable } from 'svelte/store' |   setContextClient, | ||||||
|  |   fetchExchange, | ||||||
|  | } from "@urql/svelte"; | ||||||
|  | import { setContext, getContext, hasContext, onDestroy, tick } from "svelte"; | ||||||
|  | import { readable } from "svelte/store"; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Call this function only at component initialization time! |  * Call this function only at component initialization time! | ||||||
| @@ -13,28 +17,29 @@ import { readable } from 'svelte/store' | |||||||
|  * - Adds 'clusters' to the context (object with cluster names as keys) |  * - Adds 'clusters' to the context (object with cluster names as keys) | ||||||
|  * - Adds 'metrics' to the context, a function that takes a cluster and metric name and returns the MetricConfig (or undefined) |  * - Adds 'metrics' to the context, a function that takes a cluster and metric name and returns the MetricConfig (or undefined) | ||||||
|  */ |  */ | ||||||
| export function init(extraInitQuery = '') { | export function init(extraInitQuery = "") { | ||||||
|     const jwt = hasContext('jwt') |   const jwt = hasContext("jwt") | ||||||
|         ? getContext('jwt') |     ? getContext("jwt") | ||||||
|         : getContext('cc-config')['jwt'] |     : getContext("cc-config")["jwt"]; | ||||||
|  |  | ||||||
|   const client = new Client({ |   const client = new Client({ | ||||||
|     url: `${window.location.origin}/query`, |     url: `${window.location.origin}/query`, | ||||||
|         fetchOptions: jwt != null |     fetchOptions: | ||||||
|             ? { headers: { 'Authorization': `Bearer ${jwt}` } } : {}, |       jwt != null ? { headers: { Authorization: `Bearer ${jwt}` } } : {}, | ||||||
|     exchanges: [ |     exchanges: [ | ||||||
|             dedupExchange, |  | ||||||
|       expiringCacheExchange({ |       expiringCacheExchange({ | ||||||
|         ttl: 5 * 60 * 1000, |         ttl: 5 * 60 * 1000, | ||||||
|         maxSize: 150, |         maxSize: 150, | ||||||
|       }), |       }), | ||||||
|             fetchExchange |       fetchExchange, | ||||||
|         ] |     ], | ||||||
|     }) |   }); | ||||||
|  |  | ||||||
|     setContextClient(client) |   setContextClient(client); | ||||||
|  |  | ||||||
|     const query = client.query(`query { |   const query = client | ||||||
|  |     .query( | ||||||
|  |       `query { | ||||||
|         clusters { |         clusters { | ||||||
|             name, |             name, | ||||||
|             metricConfig { |             metricConfig { | ||||||
| @@ -62,227 +67,247 @@ export function init(extraInitQuery = '') { | |||||||
|         } |         } | ||||||
|         tags { id, name, type } |         tags { id, name, type } | ||||||
|         ${extraInitQuery} |         ${extraInitQuery} | ||||||
|     }`).toPromise() |     }` | ||||||
|  |     ) | ||||||
|  |     .toPromise(); | ||||||
|  |  | ||||||
|     let state = { fetching: true, error: null, data: null } |   let state = { fetching: true, error: null, data: null }; | ||||||
|     let subscribers = [] |   let subscribers = []; | ||||||
|   const subscribe = (callback) => { |   const subscribe = (callback) => { | ||||||
|         callback(state) |     callback(state); | ||||||
|         subscribers.push(callback) |     subscribers.push(callback); | ||||||
|     return () => { |     return () => { | ||||||
|             subscribers = subscribers.filter(cb => cb != callback) |       subscribers = subscribers.filter((cb) => cb != callback); | ||||||
|         } |     }; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|     const tags = [], clusters = [] |   const tags = [], | ||||||
|     setContext('tags', tags) |     clusters = []; | ||||||
|     setContext('clusters', clusters) |   setContext("tags", tags); | ||||||
|     setContext('metrics', (cluster, metric) => { |   setContext("clusters", clusters); | ||||||
|         if (typeof cluster !== 'object') |   setContext("metrics", (cluster, metric) => { | ||||||
|             cluster = clusters.find(c => c.name == cluster) |     if (typeof cluster !== "object") | ||||||
|  |       cluster = clusters.find((c) => c.name == cluster); | ||||||
|  |  | ||||||
|         return cluster.metricConfig.find(m => m.name == metric) |     return cluster.metricConfig.find((m) => m.name == metric); | ||||||
|     }) |   }); | ||||||
|     setContext('on-init', callback => state.fetching |   setContext("on-init", (callback) => | ||||||
|         ? subscribers.push(callback) |     state.fetching ? subscribers.push(callback) : callback(state) | ||||||
|         : callback(state)) |   ); | ||||||
|     setContext('initialized', readable(false, (set) => |   setContext( | ||||||
|         subscribers.push(() => set(true)))) |     "initialized", | ||||||
|  |     readable(false, (set) => subscribers.push(() => set(true))) | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   query.then(({ error, data }) => { |   query.then(({ error, data }) => { | ||||||
|         state.fetching = false |     state.fetching = false; | ||||||
|     if (error != null) { |     if (error != null) { | ||||||
|             console.error(error) |       console.error(error); | ||||||
|             state.error = error |       state.error = error; | ||||||
|             tick().then(() => subscribers.forEach(cb => cb(state))) |       tick().then(() => subscribers.forEach((cb) => cb(state))); | ||||||
|             return |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         for (let tag of data.tags) |     for (let tag of data.tags) tags.push(tag); | ||||||
|             tags.push(tag) |  | ||||||
|  |  | ||||||
|         for (let cluster of data.clusters) |     for (let cluster of data.clusters) clusters.push(cluster); | ||||||
|             clusters.push(cluster) |  | ||||||
|  |  | ||||||
|         state.data = data |     state.data = data; | ||||||
|         tick().then(() => subscribers.forEach(cb => cb(state))) |     tick().then(() => subscribers.forEach((cb) => cb(state))); | ||||||
|     }) |   }); | ||||||
|  |  | ||||||
|   return { |   return { | ||||||
|     query: { subscribe }, |     query: { subscribe }, | ||||||
|     tags, |     tags, | ||||||
|     clusters, |     clusters, | ||||||
|     } |   }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function formatNumber(x) { | export function formatNumber(x) { | ||||||
|     let suffix = '' |   let suffix = ""; | ||||||
|   if (x >= 1000000000) { |   if (x >= 1000000000) { | ||||||
|         x /= 1000000 |     x /= 1000000; | ||||||
|         suffix = 'G' |     suffix = "G"; | ||||||
|   } else if (x >= 1000000) { |   } else if (x >= 1000000) { | ||||||
|         x /= 1000000 |     x /= 1000000; | ||||||
|         suffix = 'M' |     suffix = "M"; | ||||||
|   } else if (x >= 1000) { |   } else if (x >= 1000) { | ||||||
|         x /= 1000 |     x /= 1000; | ||||||
|         suffix = 'k' |     suffix = "k"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|     return `${(Math.round(x * 100) / 100)} ${suffix}` |   return `${Math.round(x * 100) / 100} ${suffix}`; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Use https://developer.mozilla.org/en-US/docs/Web/API/structuredClone instead? | // Use https://developer.mozilla.org/en-US/docs/Web/API/structuredClone instead? | ||||||
| export function deepCopy(x) { | export function deepCopy(x) { | ||||||
|     return JSON.parse(JSON.stringify(x)) |   return JSON.parse(JSON.stringify(x)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function fuzzyMatch(term, string) { | function fuzzyMatch(term, string) { | ||||||
|     return string.toLowerCase().includes(term) |   return string.toLowerCase().includes(term); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function fuzzySearchTags(term, tags) { | export function fuzzySearchTags(term, tags) { | ||||||
|     if (!tags) |   if (!tags) return []; | ||||||
|         return [] |  | ||||||
|  |  | ||||||
|     let results = [] |   let results = []; | ||||||
|     let termparts = term.split(':').map(s => s.trim()).filter(s => s.length > 0) |   let termparts = term | ||||||
|  |     .split(":") | ||||||
|  |     .map((s) => s.trim()) | ||||||
|  |     .filter((s) => s.length > 0); | ||||||
|  |  | ||||||
|   if (termparts.length == 0) { |   if (termparts.length == 0) { | ||||||
|         results = tags.slice() |     results = tags.slice(); | ||||||
|   } else if (termparts.length == 1) { |   } else if (termparts.length == 1) { | ||||||
|     for (let tag of tags) |     for (let tag of tags) | ||||||
|             if (fuzzyMatch(termparts[0], tag.type) |       if ( | ||||||
|                 || fuzzyMatch(termparts[0], tag.name)) |         fuzzyMatch(termparts[0], tag.type) || | ||||||
|                 results.push(tag) |         fuzzyMatch(termparts[0], tag.name) | ||||||
|  |       ) | ||||||
|  |         results.push(tag); | ||||||
|   } else if (termparts.length == 2) { |   } else if (termparts.length == 2) { | ||||||
|     for (let tag of tags) |     for (let tag of tags) | ||||||
|             if (fuzzyMatch(termparts[0], tag.type) |       if ( | ||||||
|                 && fuzzyMatch(termparts[1], tag.name)) |         fuzzyMatch(termparts[0], tag.type) && | ||||||
|                 results.push(tag) |         fuzzyMatch(termparts[1], tag.name) | ||||||
|  |       ) | ||||||
|  |         results.push(tag); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return results.sort((a, b) => { |   return results.sort((a, b) => { | ||||||
|         if (a.type < b.type) return -1 |     if (a.type < b.type) return -1; | ||||||
|         if (a.type > b.type) return 1 |     if (a.type > b.type) return 1; | ||||||
|         if (a.name < b.name) return -1 |     if (a.name < b.name) return -1; | ||||||
|         if (a.name > b.name) return 1 |     if (a.name > b.name) return 1; | ||||||
|         return 0 |     return 0; | ||||||
|     }) |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function groupByScope(jobMetrics) { | export function groupByScope(jobMetrics) { | ||||||
|     let metrics = new Map() |   let metrics = new Map(); | ||||||
|   for (let metric of jobMetrics) { |   for (let metric of jobMetrics) { | ||||||
|         if (metrics.has(metric.name)) |     if (metrics.has(metric.name)) metrics.get(metric.name).push(metric); | ||||||
|             metrics.get(metric.name).push(metric) |     else metrics.set(metric.name, [metric]); | ||||||
|         else |  | ||||||
|             metrics.set(metric.name, [metric]) |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|     return [...metrics.values()].sort((a, b) => a[0].name.localeCompare(b[0].name)) |   return [...metrics.values()].sort((a, b) => | ||||||
|  |     a[0].name.localeCompare(b[0].name) | ||||||
|  |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
| const scopeGranularity = { | const scopeGranularity = { | ||||||
|     "node": 10, |   node: 10, | ||||||
|     "socket": 5, |   socket: 5, | ||||||
|     "accelerator": 5, |   accelerator: 5, | ||||||
|     "core": 2, |   core: 2, | ||||||
|     "hwthread": 1 |   hwthread: 1, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export function maxScope(scopes) { | export function maxScope(scopes) { | ||||||
|     console.assert(scopes.length > 0 && scopes.every(x => scopeGranularity[x] != null)) |   console.assert( | ||||||
|     let sm = scopes[0], gran = scopeGranularity[scopes[0]] |     scopes.length > 0 && scopes.every((x) => scopeGranularity[x] != null) | ||||||
|  |   ); | ||||||
|  |   let sm = scopes[0], | ||||||
|  |     gran = scopeGranularity[scopes[0]]; | ||||||
|   for (let scope of scopes) { |   for (let scope of scopes) { | ||||||
|         let otherGran = scopeGranularity[scope] |     let otherGran = scopeGranularity[scope]; | ||||||
|     if (otherGran > gran) { |     if (otherGran > gran) { | ||||||
|             sm = scope |       sm = scope; | ||||||
|             gran = otherGran |       gran = otherGran; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|     return sm |   return sm; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function minScope(scopes) { | export function minScope(scopes) { | ||||||
|     console.assert(scopes.length > 0 && scopes.every(x => scopeGranularity[x] != null)) |   console.assert( | ||||||
|     let sm = scopes[0], gran = scopeGranularity[scopes[0]] |     scopes.length > 0 && scopes.every((x) => scopeGranularity[x] != null) | ||||||
|  |   ); | ||||||
|  |   let sm = scopes[0], | ||||||
|  |     gran = scopeGranularity[scopes[0]]; | ||||||
|   for (let scope of scopes) { |   for (let scope of scopes) { | ||||||
|         let otherGran = scopeGranularity[scope] |     let otherGran = scopeGranularity[scope]; | ||||||
|     if (otherGran < gran) { |     if (otherGran < gran) { | ||||||
|             sm = scope |       sm = scope; | ||||||
|             gran = otherGran |       gran = otherGran; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|     return sm |   return sm; | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function fetchMetrics(job, metrics, scopes) { | export async function fetchMetrics(job, metrics, scopes) { | ||||||
|     if (job.monitoringStatus == 0) |   if (job.monitoringStatus == 0) return null; | ||||||
|         return null |  | ||||||
|  |  | ||||||
|     let query = [] |   let query = []; | ||||||
|   if (metrics != null) { |   if (metrics != null) { | ||||||
|     for (let metric of metrics) { |     for (let metric of metrics) { | ||||||
|             query.push(`metric=${metric}`) |       query.push(`metric=${metric}`); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   if (scopes != null) { |   if (scopes != null) { | ||||||
|     for (let scope of scopes) { |     for (let scope of scopes) { | ||||||
|             query.push(`scope=${scope}`) |       query.push(`scope=${scope}`); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   try { |   try { | ||||||
|         let res = await fetch(`/api/jobs/metrics/${job.id}${(query.length > 0) ? '?' : ''}${query.join('&')}`) |     let res = await fetch( | ||||||
|  |       `/api/jobs/metrics/${job.id}${query.length > 0 ? "?" : ""}${query.join( | ||||||
|  |         "&" | ||||||
|  |       )}` | ||||||
|  |     ); | ||||||
|     if (res.status != 200) { |     if (res.status != 200) { | ||||||
|             return { error: { status: res.status, message: await res.text() } } |       return { error: { status: res.status, message: await res.text() } }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         return await res.json() |     return await res.json(); | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|         return { error: e } |     return { error: e }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export function fetchMetricsStore() { | export function fetchMetricsStore() { | ||||||
|     let set = null |   let set = null; | ||||||
|     let prev = { fetching: true, error: null, data: null } |   let prev = { fetching: true, error: null, data: null }; | ||||||
|   return [ |   return [ | ||||||
|         readable(prev, (_set) => { set = _set }), |     readable(prev, (_set) => { | ||||||
|         (job, metrics, scopes) => fetchMetrics(job, metrics, scopes).then(res => { |       set = _set; | ||||||
|             let next = { fetching: false, error: res.error, data: res.data } |     }), | ||||||
|  |     (job, metrics, scopes) => | ||||||
|  |       fetchMetrics(job, metrics, scopes).then((res) => { | ||||||
|  |         let next = { fetching: false, error: res.error, data: res.data }; | ||||||
|         if (prev.data && next.data) |         if (prev.data && next.data) | ||||||
|                 next.data.jobMetrics.push(...prev.data.jobMetrics) |           next.data.jobMetrics.push(...prev.data.jobMetrics); | ||||||
|  |  | ||||||
|             prev = next |         prev = next; | ||||||
|             set(next) |         set(next); | ||||||
|         }) |       }), | ||||||
|     ] |   ]; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function stickyHeader(datatableHeaderSelector, updatePading) { | export function stickyHeader(datatableHeaderSelector, updatePading) { | ||||||
|     const header = document.querySelector('header > nav.navbar') |   const header = document.querySelector("header > nav.navbar"); | ||||||
|     if (!header) |   if (!header) return; | ||||||
|         return |  | ||||||
|  |  | ||||||
|     let ticking = false, datatableHeader = null |   let ticking = false, | ||||||
|     const onscroll = event => { |     datatableHeader = null; | ||||||
|         if (ticking) |   const onscroll = (event) => { | ||||||
|             return |     if (ticking) return; | ||||||
|  |  | ||||||
|         ticking = true |     ticking = true; | ||||||
|     window.requestAnimationFrame(() => { |     window.requestAnimationFrame(() => { | ||||||
|             ticking = false |       ticking = false; | ||||||
|       if (!datatableHeader) |       if (!datatableHeader) | ||||||
|                 datatableHeader = document.querySelector(datatableHeaderSelector) |         datatableHeader = document.querySelector(datatableHeaderSelector); | ||||||
|  |  | ||||||
|             const top = datatableHeader.getBoundingClientRect().top |       const top = datatableHeader.getBoundingClientRect().top; | ||||||
|             updatePading(top < header.clientHeight |       updatePading( | ||||||
|                 ? (header.clientHeight - top) + 10 |         top < header.clientHeight ? header.clientHeight - top + 10 : 10 | ||||||
|                 : 10) |       ); | ||||||
|         }) |     }); | ||||||
|     } |   }; | ||||||
|  |  | ||||||
|     document.addEventListener('scroll', onscroll) |   document.addEventListener("scroll", onscroll); | ||||||
|     onDestroy(() => document.removeEventListener('scroll', onscroll)) |   onDestroy(() => document.removeEventListener("scroll", onscroll)); | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user