feat: Setup Wizard for first-run configuration
Container starts with only DB credentials. On first visit, a step-by-step wizard guides through admin password, weather, HA, Vikunja, Unraid, MQTT, n8n and news configuration. Backward-compat: ADMIN_PASSWORD env skips wizard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e25d055ba2
commit
6651bfaf60
9 changed files with 1042 additions and 34 deletions
100
web/src/setup/api.ts
Normal file
100
web/src/setup/api.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
const API_BASE = "/api";
|
||||
|
||||
let setupToken: string | null = null;
|
||||
|
||||
export function setSetupToken(token: string): void {
|
||||
setupToken = token;
|
||||
}
|
||||
|
||||
export function getSetupToken(): string | null {
|
||||
return setupToken;
|
||||
}
|
||||
|
||||
async function setupFetch(
|
||||
path: string,
|
||||
options: RequestInit = {},
|
||||
): Promise<Response> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
...((options.headers as Record<string, string>) || {}),
|
||||
};
|
||||
if (setupToken) {
|
||||
headers["Authorization"] = `Bearer ${setupToken}`;
|
||||
}
|
||||
return fetch(`${API_BASE}${path}`, { ...options, headers });
|
||||
}
|
||||
|
||||
// --- Status ---
|
||||
|
||||
export interface SetupStatus {
|
||||
setup_complete: boolean;
|
||||
integrations: Array<{ type: string; name: string; enabled: boolean }>;
|
||||
}
|
||||
|
||||
export async function getSetupStatus(): Promise<SetupStatus> {
|
||||
const res = await fetch(`${API_BASE}/setup/status`);
|
||||
if (!res.ok) throw new Error("Failed to check setup status");
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// --- Admin Creation ---
|
||||
|
||||
export async function createAdmin(
|
||||
password: string,
|
||||
): Promise<{ token: string; username: string }> {
|
||||
const res = await fetch(`${API_BASE}/setup/admin`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.detail || "Admin-Erstellung fehlgeschlagen");
|
||||
}
|
||||
const data = await res.json();
|
||||
setSetupToken(data.token);
|
||||
return data;
|
||||
}
|
||||
|
||||
// --- Integration Config ---
|
||||
|
||||
export async function saveIntegration(
|
||||
type: string,
|
||||
config: Record<string, unknown>,
|
||||
enabled: boolean,
|
||||
): Promise<Record<string, unknown>> {
|
||||
const res = await setupFetch(`/setup/integration/${type}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({ config, enabled }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.detail || "Speichern fehlgeschlagen");
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function testIntegrationConfig(
|
||||
type: string,
|
||||
config: Record<string, unknown>,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const res = await setupFetch(`/setup/integration/${type}/test`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ config }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.detail || "Test fehlgeschlagen");
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// --- Complete ---
|
||||
|
||||
export async function completeSetup(): Promise<void> {
|
||||
const res = await setupFetch("/setup/complete", { method: "POST" });
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
throw new Error(body.detail || "Setup-Abschluss fehlgeschlagen");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue