Add editable system configuration flow

This commit is contained in:
2026-04-02 00:25:14 +03:00
parent 25f6beedd8
commit 1f73a29137
11 changed files with 756 additions and 152 deletions

View File

@@ -3,6 +3,7 @@ import os from 'node:os';
import path from 'node:path';
import request from 'supertest';
import { afterEach, describe, expect, it } from 'vitest';
import type { UpdateSystemInput } from '../src/shared/contracts';
import { createApp } from './app';
import type { RuntimeSnapshot } from './lib/config';
import type { RuntimeController } from './lib/runtime';
@@ -75,6 +76,56 @@ describe('panel api', () => {
false,
);
});
it('rejects system updates when two services reuse the same port', async () => {
const app = await createTestApp();
const initial = await request(app).get('/api/state');
const system = createSystemPayload(initial.body);
system.services[1].port = system.services[0].port;
const response = await request(app).put('/api/system').send(system);
expect(response.status).toBe(400);
expect(response.body.error).toMatch(/cannot share port/i);
});
it('rejects system updates that strand existing users on a disabled service', async () => {
const app = await createTestApp();
const initial = await request(app).get('/api/state');
const system = createSystemPayload(initial.body);
system.services = system.services.map((service) =>
service.id === 'socks-main' ? { ...service, enabled: false } : service,
);
const response = await request(app).put('/api/system').send(system);
expect(response.status).toBe(400);
expect(response.body.error).toMatch(/enabled assignable service/i);
expect(response.body.error).toMatch(/night-shift/i);
});
it('updates system settings and regenerates the rendered config', async () => {
const app = await createTestApp();
const initial = await request(app).get('/api/state');
const system = createSystemPayload(initial.body);
system.publicHost = 'ops-gateway.example.net';
system.services = system.services.map((service) =>
service.id === 'socks-main' ? { ...service, port: 1180 } : service,
);
const response = await request(app).put('/api/system').send(system);
expect(response.status).toBe(200);
expect(response.body.system.publicHost).toBe('ops-gateway.example.net');
expect(response.body.system.services.find((service: { id: string }) => service.id === 'socks-main').port).toBe(
1180,
);
expect(response.body.system.previewConfig).toContain('socks -p1180 -u2');
expect(response.body.service.lastEvent).toBe('System configuration updated from panel');
});
});
async function createTestApp() {
@@ -90,3 +141,8 @@ async function createTestApp() {
runtimeRootDir: dir,
});
}
function createSystemPayload(body: { system: Record<string, unknown> }): UpdateSystemInput {
const { previewConfig: _previewConfig, ...system } = body.system;
return structuredClone(system) as unknown as UpdateSystemInput;
}