Add per-node action groups with schedules, runs, and logs.

Let operators manage ordered action groups on each host, run them manually or on a schedule over SSH, and inspect disk-backed JSON run logs from a node detail UI.
This commit is contained in:
2026-07-19 20:01:11 +02:00
parent 50568127c5
commit 32af91fd30
22 changed files with 4337 additions and 32 deletions
+34 -8
View File
@@ -20,6 +20,18 @@ describe('pathFor', () => {
expect(pathFor('configuration', 'actions')).toBe('/configuration/actions')
expect(pathFor('configuration', 'roles')).toBe('/configuration/roles')
})
it('maps node detail paths', () => {
expect(pathFor('containers', 'overview', 'overview', 'node-1')).toBe(
'/containers/node-1',
)
expect(
pathFor('containers', 'overview', 'overview', 'node-1', 'actions'),
).toBe('/containers/node-1/actions')
expect(pathFor('vms', 'overview', 'overview', 'abc', 'logs')).toBe(
'/vms/abc/logs',
)
})
})
describe('parseLocation', () => {
@@ -45,10 +57,19 @@ describe('parseLocation', () => {
'/configuration/integrations',
'/configuration/network',
'/configuration/advanced',
'/containers/node-1',
'/containers/node-1/actions',
'/vms/node-2/logs',
]) {
const location = parseLocation(path)
expect(
pathFor(location.section, location.configTab, location.resourceTab),
pathFor(
location.section,
location.configTab,
location.resourceTab,
location.nodeId,
location.nodeDetailTab,
),
).toBe(path === '/docker/overview' ? '/docker' : path)
}
})
@@ -58,21 +79,29 @@ describe('parseLocation', () => {
section: 'overview',
configTab: 'overview',
resourceTab: 'overview',
nodeId: null,
nodeDetailTab: 'overview',
})
expect(parseLocation('/configuration/unknown')).toEqual({
section: 'overview',
configTab: 'overview',
resourceTab: 'overview',
nodeId: null,
nodeDetailTab: 'overview',
})
expect(parseLocation('/vms/extra')).toEqual({
section: 'overview',
section: 'vms',
configTab: 'overview',
resourceTab: 'overview',
nodeId: 'extra',
nodeDetailTab: 'overview',
})
expect(parseLocation('/docker/extra')).toEqual({
expect(parseLocation('/docker/extra/nope')).toEqual({
section: 'overview',
configTab: 'overview',
resourceTab: 'overview',
nodeId: null,
nodeDetailTab: 'overview',
})
})
@@ -81,11 +110,8 @@ describe('parseLocation', () => {
section: 'configuration',
configTab: 'groups',
resourceTab: 'overview',
})
expect(parseLocation('/containers/add/')).toEqual({
section: 'containers',
configTab: 'overview',
resourceTab: 'add',
nodeId: null,
nodeDetailTab: 'overview',
})
})
})