Add Configuration Actions tab for shell and script action definitions.

Admins can manage built-in and custom actions with placeholders, ENV vars, and actions.create/update/delete permissions.
This commit is contained in:
2026-07-18 19:18:13 +02:00
parent 77be394407
commit c90a47c3ea
17 changed files with 1770 additions and 9 deletions
+94
View File
@@ -1,8 +1,11 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
createAction,
deleteAction,
deleteGroup,
deleteNode,
deleteUser,
fetchActions,
fetchGroups,
fetchHealth,
fetchNetwork,
@@ -11,6 +14,7 @@ import {
fetchStatus,
fetchUsers,
getApiBaseUrl,
patchAction,
saveNetwork,
saveSecurity,
upsertGroup,
@@ -622,3 +626,93 @@ describe('nodes API', () => {
)
})
})
describe('actions API', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it('fetches actions', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
actions: [
{
id: 'a1',
name: 'Uptime',
description: 'uptime',
kind: 'shell',
body: 'uptime',
env: [],
builtin: true,
created_at: '2026-01-01T00:00:00.000Z',
updated_at: '2026-01-01T00:00:00.000Z',
},
],
}),
})
const payload = await fetchActions(fetchMock)
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:8080/api/v1/actions',
expect.objectContaining({ credentials: 'include' }),
)
expect(payload.actions).toHaveLength(1)
expect(payload.actions[0]?.name).toBe('Uptime')
})
it('creates an action', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ actions: [] }),
})
await createAction(
{
name: 'Echo',
description: 'echo ip',
kind: 'shell',
body: 'echo {{node.ip}}',
env: [{ name: 'APP_HOME', value: '/opt/app' }],
},
fetchMock,
)
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:8080/api/v1/actions',
expect.objectContaining({
method: 'POST',
credentials: 'include',
body: JSON.stringify({
name: 'Echo',
description: 'echo ip',
kind: 'shell',
body: 'echo {{node.ip}}',
env: [{ name: 'APP_HOME', value: '/opt/app' }],
}),
}),
)
})
it('patches and deletes an action', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ actions: [] }),
})
await patchAction('custom-1', { name: 'Renamed', body: 'echo hi' }, fetchMock)
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:8080/api/v1/actions?id=custom-1',
expect.objectContaining({
method: 'PATCH',
body: JSON.stringify({ name: 'Renamed', body: 'echo hi' }),
}),
)
await deleteAction('custom-1', fetchMock)
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:8080/api/v1/actions?id=custom-1',
expect.objectContaining({ method: 'DELETE' }),
)
})
})