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)) {