AI-admin: «Generer + restart gateway»-knapp

Utvider generate-config endepunktet med ?restart=true som finner
og restarter ai-gateway docker-containeren etter config-generering.
Ny grønn knapp i UI gjør begge operasjoner i ett klikk.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-16 05:30:28 +01:00
parent b1a7e55fff
commit 177e4b6b66
2 changed files with 66 additions and 8 deletions

View file

@ -3,11 +3,13 @@ import type { RequestHandler } from './$types';
import { sql } from '$lib/server/db'; import { sql } from '$lib/server/db';
import { writeFileSync } from 'node:fs'; import { writeFileSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { execSync } from 'node:child_process';
/** /**
* POST /api/admin/ai/generate-config Generer LiteLLM config.yaml fra PG. * POST /api/admin/ai/generate-config Generer LiteLLM config.yaml fra PG.
* Query: ?restart=true restart ai-gateway etter generering.
*/ */
export const POST: RequestHandler = async ({ locals }) => { export const POST: RequestHandler = async ({ locals, url }) => {
if (!locals.workspace || !locals.user) error(401); if (!locals.workspace || !locals.user) error(401);
// Hent aktive aliaser med aktive providers // Hent aktive aliaser med aktive providers
@ -46,9 +48,38 @@ export const POST: RequestHandler = async ({ locals }) => {
const configPath = join(process.cwd(), 'config', 'litellm', 'config.yaml'); const configPath = join(process.cwd(), 'config', 'litellm', 'config.yaml');
writeFileSync(configPath, yaml, 'utf-8'); writeFileSync(configPath, yaml, 'utf-8');
// Restart ai-gateway container hvis forespurt
const shouldRestart = url.searchParams.get('restart') === 'true';
let restarted = false;
let restartError = '';
if (shouldRestart) {
try {
// Finn container med "ai-gateway" i navnet
const name = execSync('docker ps --format "{{.Names}}" | grep ai-gateway', {
encoding: 'utf-8',
timeout: 5000
}).trim();
if (name) {
execSync(`docker restart ${name}`, { timeout: 30000 });
restarted = true;
} else {
restartError = 'ai-gateway container ikke funnet';
}
} catch (e: any) {
restartError = e.message || 'Kunne ikke restarte ai-gateway';
}
}
return json({ return json({
ok: true, ok: true,
message: 'Config generert. Restart ai-gateway for å aktivere.', message: restarted
model_count: rows.length ? 'Config generert og ai-gateway restartet.'
: restartError
? `Config generert, men restart feilet: ${restartError}`
: 'Config generert. Restart ai-gateway for å aktivere.',
model_count: rows.length,
restarted
}); });
}; };

View file

@ -395,16 +395,24 @@
} }
} }
async function generateConfig() { let configLoading = $state(false);
async function generateConfig(restart = false) {
configMsg = ''; configMsg = '';
errorMsg = ''; errorMsg = '';
configLoading = true;
try { try {
const res = await fetch('/api/admin/ai/generate-config', { method: 'POST' }); const url = restart
? '/api/admin/ai/generate-config?restart=true'
: '/api/admin/ai/generate-config';
const res = await fetch(url, { method: 'POST' });
const data = await res.json(); const data = await res.json();
if (!res.ok) throw new Error(data.message ?? 'Feil'); if (!res.ok) throw new Error(data.message ?? 'Feil');
configMsg = `${data.message} (${data.model_count} modeller)`; configMsg = `${data.message} (${data.model_count} modeller)`;
} catch (e: any) { } catch (e: any) {
errorMsg = e.message || 'Feil ved config-generering'; errorMsg = e.message || 'Feil ved config-generering';
} finally {
configLoading = false;
} }
} }
@ -761,11 +769,16 @@
<section> <section>
<h3>Konfigurasjon</h3> <h3>Konfigurasjon</h3>
<div class="config-box"> <div class="config-box">
<button class="generate-btn" onclick={generateConfig}>Generer config.yaml</button> <button class="generate-btn" onclick={() => generateConfig(false)} disabled={configLoading}>
Generer config.yaml
</button>
<button class="generate-btn generate-btn--restart" onclick={() => generateConfig(true)} disabled={configLoading}>
{configLoading ? 'Jobber...' : 'Generer + restart gateway'}
</button>
{#if configMsg} {#if configMsg}
<span class="config-msg">{configMsg}</span> <span class="config-msg">{configMsg}</span>
{/if} {/if}
<p class="hint">Genererer LiteLLM config.yaml fra databasen. AI Gateway (LiteLLM) må restartes for å lese ny config.</p> <p class="hint">Genererer LiteLLM config.yaml fra databasen. «Generer + restart» aktiverer endringene umiddelbart.</p>
</div> </div>
</section> </section>
</div> </div>
@ -1216,10 +1229,24 @@
cursor: pointer; cursor: pointer;
} }
.generate-btn:hover { .generate-btn:hover:not(:disabled) {
background: #264b7a; background: #264b7a;
} }
.generate-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.generate-btn--restart {
background: #1e4f3a;
border-color: #22c55e;
}
.generate-btn--restart:hover:not(:disabled) {
background: #276b4f;
}
/* Forms */ /* Forms */
.add-form { .add-form {
display: flex; display: flex;