gateway: add local backend daemon controls to dashboard

This commit is contained in:
William Valentin
2026-02-22 16:36:31 -08:00
parent 44c7409a20
commit 6f2925ed10
6 changed files with 671 additions and 1 deletions
+102
View File
@@ -10,6 +10,7 @@ import { createHistoryHandlers } from './history.js';
import { createCanvasHandlers } from './canvas.js';
import { createConfigHandlers, redactConfig } from './config.js';
import { createPairingHandlers } from './pairing.js';
import type { LocalBackendStatus, LocalBackendControlResult } from './localBackends.js';
import { PairingManager } from '../../channels/pairing.js';
import { LaneQueue } from '../lane-queue.js';
import { CanvasStore } from '../canvas-store.js';
@@ -142,6 +143,107 @@ describe('system handlers', () => {
]);
});
it('system.localBackends returns empty list when callback is not provided', async () => {
const req: GatewayRequest = { id: 35, method: 'system.localBackends' };
const result = await handlers['system.localBackends'](req) as GatewayResponse;
expect(getPath(result.result, 'backends')).toEqual([]);
});
it('system.localBackends returns backend statuses from callback', async () => {
const localBackends: LocalBackendStatus[] = [
{
id: 'ollama',
provider: 'ollama',
name: 'Ollama',
unit: 'ollama.service',
configured: true,
loadState: 'loaded',
activeState: 'active',
subState: 'running',
unitFileState: 'enabled',
description: 'Ollama Service',
pid: 1234,
result: 'success',
statusText: 'active (running)',
availableActions: ['restart', 'stop'],
},
];
const getLocalBackends = vi.fn(async (): Promise<LocalBackendStatus[]> => localBackends);
const handlers = createSystemHandlers({
...deps,
getLocalBackends,
});
const req: GatewayRequest = { id: 36, method: 'system.localBackends' };
const result = await handlers['system.localBackends'](req) as GatewayResponse;
expect(getLocalBackends).toHaveBeenCalledTimes(1);
expect(getPath(result.result, 'backends')).toHaveLength(1);
expect(getPath(result.result, 'backends', '0', 'id')).toBe('ollama');
});
it('system.localBackendControl validates required params', async () => {
const handlers = createSystemHandlers({
...deps,
controlLocalBackend: vi.fn(),
});
const missingBackend = await handlers['system.localBackendControl']({
id: 37,
method: 'system.localBackendControl',
params: { action: 'restart' },
}) as GatewayError;
expect(missingBackend.error.code).toBe(ErrorCode.InvalidRequest);
const missingAction = await handlers['system.localBackendControl']({
id: 38,
method: 'system.localBackendControl',
params: { backend: 'ollama' },
}) as GatewayError;
expect(missingAction.error.code).toBe(ErrorCode.InvalidRequest);
const badAction = await handlers['system.localBackendControl']({
id: 39,
method: 'system.localBackendControl',
params: { backend: 'ollama', action: 'reload' },
}) as GatewayError;
expect(badAction.error.code).toBe(ErrorCode.InvalidRequest);
});
it('system.localBackendControl forwards action to callback', async () => {
const controlResult: LocalBackendControlResult = {
backend: 'ollama',
action: 'restart',
status: {
id: 'ollama',
provider: 'ollama',
name: 'Ollama',
unit: 'ollama.service',
configured: true,
loadState: 'loaded',
activeState: 'active',
subState: 'running',
unitFileState: 'enabled',
description: 'Ollama Service',
pid: 321,
result: 'success',
statusText: 'active (running)',
availableActions: ['restart', 'stop'],
},
};
const controlLocalBackend = vi.fn(async (): Promise<LocalBackendControlResult> => controlResult);
const handlers = createSystemHandlers({
...deps,
controlLocalBackend,
});
const req: GatewayRequest = {
id: 40,
method: 'system.localBackendControl',
params: { backend: 'ollama', action: 'restart' },
};
const result = await handlers['system.localBackendControl'](req) as GatewayResponse;
expect(controlLocalBackend).toHaveBeenCalledWith('ollama', 'restart');
expect(getPath(result.result, 'status', 'activeState')).toBe('active');
});
it('system.presence returns empty result when getPresence is not provided', async () => {
const req: GatewayRequest = { id: 4, method: 'system.presence' };
const result = await handlers['system.presence'](req) as GatewayResponse;