diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 67396d77408..c3ac2683cf0 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -18,6 +18,7 @@ - [Dashboard] Display backup issues data (PR [#7974](https://github.com/vatesfr/xen-orchestra/pull/7974)) - [REST API] Add S3 backup repository and VMs protection information in the `/rest/v0/dashboard` endpoint (PRs [#7978](https://github.com/vatesfr/xen-orchestra/pull/7978), [#7964](https://github.com/vatesfr/xen-orchestra/pull/7964)) - [Backups] Display more informations in the _Notes_ column of the backup page (PR [#7977](https://github.com/vatesfr/xen-orchestra/pull/7977)) +- [REST API] Add `/alarms` endpoint and remove alarms from the `/dashboard` and `/messages` endpoints (PR [#7959](https://github.com/vatesfr/xen-orchestra/pull/7959)) ### Bug fixes diff --git a/packages/xo-server/src/utils.mjs b/packages/xo-server/src/utils.mjs index 8f803ae44bb..b0610bef98a 100644 --- a/packages/xo-server/src/utils.mjs +++ b/packages/xo-server/src/utils.mjs @@ -347,3 +347,7 @@ export const isReplicaVm = vm => 'start' in vm.blockedOperations && vm.other['xo // ------------------------------------------------------------------- export const vmContainsNoBakTag = vm => vm.tags.some(t => t.split('=', 1)[0] === 'xo:no-bak') + +// ------------------------------------------------------------------- + +export const isAlarm = alarm => alarm.type === 'message' && alarm.name === 'ALARM' diff --git a/packages/xo-server/src/xo-mixins/rest-api.mjs b/packages/xo-server/src/xo-mixins/rest-api.mjs index 983b82684f3..78996abaf19 100644 --- a/packages/xo-server/src/xo-mixins/rest-api.mjs +++ b/packages/xo-server/src/xo-mixins/rest-api.mjs @@ -20,12 +20,12 @@ import * as CM from 'complex-matcher' import { VDI_FORMAT_RAW, VDI_FORMAT_VHD } from '@xen-orchestra/xapi' import { parse } from 'xo-remote-parser' -import { getUserPublicProperties, isReplicaVm, isSrWritable, vmContainsNoBakTag } from '../utils.mjs' +import { getUserPublicProperties, isAlarm, isReplicaVm, isSrWritable, vmContainsNoBakTag } from '../utils.mjs' import { compileXoJsonSchema } from './_xoJsonSchema.mjs' import { createPredicate } from 'value-matcher' // E.g: 'value: 0.6\nconfig:\n\n\n\n\n'; -const ALARM_BODY_REGEX = /^value:\s*(\d+(?:\.\d+)?)\s*config:\s*\s*\s* { - try { - const [, value, name] = body.match(ALARM_BODY_REGEX) - - let object - try { - object = app.getObject($object) - } catch (error) { - console.error(error) - object = { - type: 'unknown', - uuid: $object, - } - } - - acc.push({ - name, - object: { - type: object.type, - uuid: object.uuid, - }, - timestamp: time, - value: +value, - }) - } catch (error) { - console.error(error) - } - - return acc - }, []) return dashboard } const getDashboardStats = throttle(_getDashboardStats, 6e4, { trailing: false, leading: true }) +const keepNonAlarmMessages = message => message.type === 'message' && !isAlarm(message) export default class RestApi { #api @@ -548,7 +514,7 @@ export default class RestApi { await sendObjects( Object.values( app.getObjects({ - filter: every(_ => _.type === 'message' && _.$object === id, handleOptionalUserFilter(query.filter)), + filter: every(_ => _.$object === id, keepNonAlarmMessages, handleOptionalUserFilter(query.filter)), limit: ifDef(query.limit, Number), }) ), @@ -557,10 +523,35 @@ export default class RestApi { '/messages' ) } + + async function alarms(req, res) { + const { + object: { id }, + query, + } = req + await sendObjects( + Object.values( + app.getObjects({ + filter: every(_ => _.$object === id, isAlarm, handleOptionalUserFilter(query.filter)), + limit: ifDef(query.limit, Number), + }) + ), + req, + res, + '/alarms' + ) + } + for (const type of types) { const id = type.toLocaleLowerCase() + 's' - collections[id] = { getObject, getObjects, routes: { messages }, isCorrectType: _ => _.type === type, type } + collections[id] = { + getObject, + getObjects, + routes: { messages, alarms }, + isCorrectType: _ => _.type === type, + type, + } } collections.hosts.routes = { @@ -808,6 +799,73 @@ export default class RestApi { }, } collections.dashboard = {} + collections.messages = { + getObject(id) { + const message = app.getObject(id, 'message') + if (isAlarm(message)) { + throw noSuchObject(id, 'message') + } + + return message + }, + getObjects(filter, limit) { + return handleArray( + Object.values( + app.getObjects({ + filter: every(keepNonAlarmMessages, filter), + limit, + }) + ) + ) + }, + } + collections.alarms = { + getObject(id, req) { + const alarm = app.getObject(id, 'message') + if (!isAlarm(alarm)) { + throw noSuchObject(id, 'alarm') + } + + const { $object, body } = alarm + let object = {} + try { + object = app.getObject($object) + } catch (error) { + object = { + type: 'unknown', + uuid: $object, + } + } + + const { baseUrl } = req + const objType = object.type.toLowerCase() + 's' + const href = collections[objType] === undefined ? undefined : `${baseUrl}/${objType}/${object.uuid}` + const [, value, name] = body.match(ALARM_BODY_REGEX) + + return { + ...alarm, + body: { + value, // Keep the value as a string because NaN, Infinity, -Infinity is not valid JSON + name, + }, + object: { + type: object.type, + uuid: object.uuid, + href, + }, + } + }, + getObjects(filter, limit) { + return handleArray( + Object.values( + app.getObjects({ + filter: every(isAlarm, filter), + limit, + }) + ) + ) + }, + } // normalize collections for (const id of Object.keys(collections)) {