243 lines
6.0 KiB
JavaScript
243 lines
6.0 KiB
JavaScript
/**
|
|
* Metrics wall taxonomy — maps chart catalog entries into sections/groups.
|
|
* Versioned independently so new collectors can land without UI rewrites.
|
|
*/
|
|
|
|
export const TAXONOMY_VERSION = 1
|
|
|
|
/** @typedef {{ id: string, title: string, order: number, match: (id: string, meta: object) => boolean }} SectionDef */
|
|
|
|
/** @type {SectionDef[]} */
|
|
export const SECTIONS = [
|
|
{
|
|
id: 'system',
|
|
title: 'System',
|
|
order: 10,
|
|
match: (id, meta) =>
|
|
starts(id, [
|
|
'system.',
|
|
'cpu.',
|
|
'mem.',
|
|
'disk_',
|
|
'disk.',
|
|
'net.',
|
|
'ip.',
|
|
'ipv4.',
|
|
'ipv6.',
|
|
'processes.',
|
|
]) || ctxStarts(meta, ['system.', 'cpu.', 'mem.', 'disk.', 'net.', 'ip.']),
|
|
},
|
|
{
|
|
id: 'containers',
|
|
title: 'Containers',
|
|
order: 20,
|
|
match: (id, meta) =>
|
|
starts(id, ['docker.', 'cgroup.', 'peardock.']) ||
|
|
ctxStarts(meta, ['docker.', 'cgroup.', 'peardock.']),
|
|
},
|
|
{
|
|
id: 'storage',
|
|
title: 'Storage',
|
|
order: 30,
|
|
match: (id, meta) =>
|
|
starts(id, ['zfs.', 'bcache.', 'dmcache.', 'md.', 'mdstat.', 'smart.', 'fs.', 'ioping.']) ||
|
|
ctxStarts(meta, ['zfs.', 'bcache.', 'dmcache.', 'md.', 'smart.', 'fs.']),
|
|
},
|
|
{
|
|
id: 'hardware',
|
|
title: 'Hardware',
|
|
order: 40,
|
|
match: (id, meta) =>
|
|
starts(id, ['sensors.', 'gpu.', 'nvidia.', 'ipmi.', 'system.power', 'power.']) ||
|
|
ctxStarts(meta, ['sensors.', 'gpu.', 'ipmi.', 'power.']),
|
|
},
|
|
{
|
|
id: 'applications',
|
|
title: 'Applications',
|
|
order: 50,
|
|
match: (id, meta) =>
|
|
starts(id, [
|
|
'nginx.',
|
|
'apache.',
|
|
'redis.',
|
|
'mysql.',
|
|
'postgres.',
|
|
'memcached.',
|
|
'mongodb.',
|
|
'rabbitmq.',
|
|
'kafka.',
|
|
'nats.',
|
|
'unbound.',
|
|
]) ||
|
|
ctxStarts(meta, [
|
|
'nginx.',
|
|
'apache.',
|
|
'redis.',
|
|
'mysql.',
|
|
'postgres.',
|
|
'memcached.',
|
|
'mongodb.',
|
|
'rabbitmq.',
|
|
'kafka.',
|
|
'nats.',
|
|
'unbound.',
|
|
]),
|
|
},
|
|
{
|
|
id: 'network-svc',
|
|
title: 'Network services',
|
|
order: 60,
|
|
match: (id, meta) =>
|
|
starts(id, ['sockets.', 'net.conntrack', 'system.softnet', 'ip.tcp', 'ip.udp']) ||
|
|
ctxStarts(meta, ['sockets.', 'net.conntrack']),
|
|
},
|
|
{
|
|
id: 'observability',
|
|
title: 'Observability',
|
|
order: 70,
|
|
match: (id, meta) =>
|
|
starts(id, ['ebpf.', 'statsd.', 'prometheus.', 'peardata.', 'self.']) ||
|
|
ctxStarts(meta, ['ebpf.', 'statsd.', 'prometheus.', 'peardata.']),
|
|
},
|
|
{
|
|
id: 'fleet',
|
|
title: 'Fleet',
|
|
order: 80,
|
|
match: (id, meta) => starts(id, ['fleet.']) || ctxStarts(meta, ['fleet.']),
|
|
},
|
|
{
|
|
id: 'other',
|
|
title: 'Other',
|
|
order: 900,
|
|
match: () => true,
|
|
},
|
|
]
|
|
|
|
/**
|
|
* @param {string} id
|
|
* @param {string[]} prefixes
|
|
*/
|
|
function starts(id, prefixes) {
|
|
const s = String(id || '')
|
|
return prefixes.some((p) => s === p || s.startsWith(p))
|
|
}
|
|
|
|
/**
|
|
* @param {object} meta
|
|
* @param {string[]} prefixes
|
|
*/
|
|
function ctxStarts(meta, prefixes) {
|
|
const c = String(meta?.context || meta?.name || '')
|
|
return prefixes.some((p) => c === p || c.startsWith(p))
|
|
}
|
|
|
|
/**
|
|
* @param {string} id
|
|
* @param {object} meta
|
|
*/
|
|
export function sectionForChart(id, meta = {}) {
|
|
for (const sec of SECTIONS) {
|
|
if (sec.id === 'other') continue
|
|
if (sec.match(id, meta)) return sec
|
|
}
|
|
return SECTIONS[SECTIONS.length - 1]
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, object>} catalog
|
|
* @param {string} [filter]
|
|
* @returns {{
|
|
* sections: Array<{
|
|
* id: string,
|
|
* title: string,
|
|
* order: number,
|
|
* count: number,
|
|
* groups: Array<{ family: string, charts: Array<{ id: string, meta: object }> }>
|
|
* }>
|
|
* }}
|
|
*/
|
|
export function groupCatalog(catalog, filter = '') {
|
|
const q = String(filter || '')
|
|
.trim()
|
|
.toLowerCase()
|
|
/** @type {Map<string, { id: string, title: string, order: number, families: Map<string, Array<{ id: string, meta: object }>> }>} */
|
|
const bySection = new Map()
|
|
for (const sec of SECTIONS) {
|
|
bySection.set(sec.id, {
|
|
id: sec.id,
|
|
title: sec.title,
|
|
order: sec.order,
|
|
families: new Map(),
|
|
})
|
|
}
|
|
|
|
const ids = Object.keys(catalog || {})
|
|
for (const id of ids) {
|
|
const meta = catalog[id] || {}
|
|
if (q && !matchesFilter(id, meta, q)) continue
|
|
const sec = sectionForChart(id, meta)
|
|
const bucket = bySection.get(sec.id)
|
|
if (!bucket) continue
|
|
const family = String(meta.family || meta.context || '_default')
|
|
if (!bucket.families.has(family)) bucket.families.set(family, [])
|
|
bucket.families.get(family).push({ id, meta })
|
|
}
|
|
|
|
const sections = [...bySection.values()]
|
|
.map((sec) => {
|
|
const groups = [...sec.families.entries()]
|
|
.map(([family, charts]) => ({
|
|
family,
|
|
charts: charts.sort(compareCharts),
|
|
}))
|
|
.sort((a, b) => a.family.localeCompare(b.family))
|
|
const count = groups.reduce((n, g) => n + g.charts.length, 0)
|
|
return { id: sec.id, title: sec.title, order: sec.order, count, groups }
|
|
})
|
|
.filter((s) => s.count > 0)
|
|
.sort((a, b) => a.order - b.order)
|
|
|
|
return { sections }
|
|
}
|
|
|
|
/**
|
|
* @param {string} id
|
|
* @param {object} meta
|
|
* @param {string} q lowercase
|
|
*/
|
|
function matchesFilter(id, meta, q) {
|
|
const hay = [
|
|
id,
|
|
meta.title,
|
|
meta.name,
|
|
meta.context,
|
|
meta.family,
|
|
meta.plugin,
|
|
meta.units,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')
|
|
.toLowerCase()
|
|
return hay.includes(q)
|
|
}
|
|
|
|
/**
|
|
* @param {{ id: string, meta: object }} a
|
|
* @param {{ id: string, meta: object }} b
|
|
*/
|
|
function compareCharts(a, b) {
|
|
const pa = Number(a.meta?.priority) || 99999
|
|
const pb = Number(b.meta?.priority) || 99999
|
|
if (pa !== pb) return pa - pb
|
|
return a.id.localeCompare(b.id)
|
|
}
|
|
|
|
/** Time presets for the global metrics bar (seconds of history). */
|
|
export const TIME_PRESETS = [
|
|
{ id: '1m', label: '1m', seconds: 60 },
|
|
{ id: '5m', label: '5m', seconds: 300 },
|
|
{ id: '15m', label: '15m', seconds: 900 },
|
|
{ id: '1h', label: '1h', seconds: 3600 },
|
|
{ id: '6h', label: '6h', seconds: 21600 },
|
|
]
|