gateway: add local backend update action

This commit is contained in:
William Valentin
2026-02-22 16:57:57 -08:00
parent c48f6f5fd3
commit c79e082905
7 changed files with 199 additions and 22 deletions
+4 -2
View File
@@ -37,6 +37,7 @@ const LOCAL_BACKEND_ACTION_LABELS = {
start: 'Start',
restart: 'Restart',
stop: 'Stop',
update: 'Update',
};
const SERVICE_TOGGLE_PATCH_PATHS = {
heartbeat: 'automation.heartbeat.enabled',
@@ -1569,7 +1570,7 @@ function updateLocalBackends(localBackendsData) {
const loadText = backend.loadState || 'unknown';
const resultText = backend.result || 'unknown';
const availableActions = Array.isArray(backend.availableActions)
? backend.availableActions.filter((value) => ['start', 'restart', 'stop'].includes(String(value)))
? backend.availableActions.filter((value) => ['start', 'restart', 'stop', 'update'].includes(String(value)))
: [];
const actionMessage = actionState?.message
? `<div class="text-xs ${actionState.tone === 'error' ? 'text-red-400' : actionState.tone === 'success' ? 'text-green-400' : 'text-zinc-400'}">${escapeHtml(String(actionState.message))}</div>`
@@ -1623,6 +1624,7 @@ async function handleLocalBackendAction(backendId, action) {
action,
});
const status = result?.status;
const resultMessage = typeof result?.message === 'string' ? result.message : null;
if (status && typeof status === 'object') {
_lastLocalBackends = _lastLocalBackends.map((backend) =>
backend.id === backendId ? status : backend);
@@ -1630,7 +1632,7 @@ async function handleLocalBackendAction(backendId, action) {
_localBackendActionState.set(backendId, {
pending: false,
tone: 'success',
message: `${actionLabel} completed`,
message: resultMessage ? `${actionLabel} completed: ${resultMessage}` : `${actionLabel} completed`,
});
updateLocalBackends({ backends: _lastLocalBackends });
+15 -6
View File
@@ -106,7 +106,7 @@ function createMockClient() {
pid: 111,
result: 'success',
statusText: 'active (running)',
availableActions: ['restart', 'stop'],
availableActions: ['restart', 'stop', 'update'],
},
{
id: 'llamacpp',
@@ -122,7 +122,7 @@ function createMockClient() {
pid: null,
result: 'success',
statusText: 'inactive/dead',
availableActions: ['start', 'restart'],
availableActions: ['start', 'restart', 'update'],
},
],
calls: [] as Array<{ method: string; params?: Record<string, unknown> }>,
@@ -227,14 +227,16 @@ function createMockClient() {
backend.subState = 'running';
backend.statusText = 'active (running)';
backend.pid = backend.id === 'ollama' ? 222 : 333;
backend.availableActions = ['restart', 'stop'];
backend.availableActions = ['restart', 'stop', 'update'];
backend.result = 'success';
} else if (action === 'stop') {
backend.activeState = 'inactive';
backend.subState = 'dead';
backend.statusText = 'inactive/dead';
backend.pid = null;
backend.availableActions = ['start', 'restart'];
backend.availableActions = ['start', 'restart', 'update'];
backend.result = 'success';
} else if (action === 'update') {
backend.result = 'success';
}
@@ -242,6 +244,7 @@ function createMockClient() {
backend: backendId,
action,
status: deepClone(backend),
message: action === 'update' ? 'Updated backend assets' : undefined,
};
}
return null;
@@ -445,7 +448,7 @@ describe('DashboardPage assistant controls', () => {
expect(toolCalls.some((entry) => entry.params?.tool === 'cron.trigger')).toBe(true);
});
it('renders local backend controls and triggers system.localBackendControl', async () => {
it('renders local backend controls and triggers system.localBackendControl actions', async () => {
const { state, client } = createMockClient();
await DashboardPage.render(container, client);
@@ -460,10 +463,16 @@ describe('DashboardPage assistant controls', () => {
startBtn.dispatchEvent(new windowObj.Event('click', { bubbles: true }));
await flush();
const updateBtn = container.querySelector('#ops-local-backends .local-backend-action-btn[data-backend-id="ollama"][data-action="update"]');
expect(updateBtn).toBeTruthy();
updateBtn.dispatchEvent(new windowObj.Event('click', { bubbles: true }));
await flush();
const backendCalls = state.calls.filter((entry) => entry.method === 'system.localBackendControl');
expect(backendCalls).toHaveLength(2);
expect(backendCalls).toHaveLength(3);
expect(backendCalls[0].params).toEqual({ backend: 'ollama', action: 'restart' });
expect(backendCalls[1].params).toEqual({ backend: 'llamacpp', action: 'start' });
expect(backendCalls[2].params).toEqual({ backend: 'ollama', action: 'update' });
expect(state.localBackends.find((entry) => entry.id === 'llamacpp')?.activeState).toBe('active');
});
});