Complete admin backend with login, where all integrations (weather, news, Home Assistant, Vikunja, Unraid, MQTT) can be configured via web UI instead of ENV variables. Two-layer config: ENV seeds DB on first start, then DB is source of truth. Auto-migration system on startup. Backend: db.py shared pool, auth.py JWT, settings_service CRUD, seed_service, admin router (protected), test_connections per integration, config.py rewrite. Frontend: react-router v6, login page, admin layout with sidebar, 8 settings pages (General, Weather, News, HA, Vikunja, Unraid, MQTT, ChangePassword), shared IntegrationForm + TestButton components. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Cloud, Loader2 } from "lucide-react";
|
|
import PageHeader from "../components/PageHeader";
|
|
import FormField, { TextInput } from "../components/FormField";
|
|
import IntegrationForm from "../components/IntegrationForm";
|
|
import { getIntegration, type Integration } from "../api";
|
|
|
|
export default function WeatherSettings() {
|
|
const [integration, setIntegration] = useState<Integration | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState("");
|
|
|
|
useEffect(() => {
|
|
loadIntegration();
|
|
}, []);
|
|
|
|
const loadIntegration = async () => {
|
|
try {
|
|
const data = await getIntegration("weather");
|
|
setIntegration(data);
|
|
} catch (err: any) {
|
|
setError(err.message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center py-20">
|
|
<Loader2 className="w-5 h-5 text-slate-400 animate-spin" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error || !integration) {
|
|
return (
|
|
<div>
|
|
<PageHeader icon={Cloud} title="Wetter" description="Wetteranbieter konfigurieren" />
|
|
<div className="glass-card p-6">
|
|
<p className="text-sm text-red-400">{error || "Integration nicht gefunden"}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<PageHeader
|
|
icon={Cloud}
|
|
title="Wetter"
|
|
description="Standorte für die Wettervorhersage konfigurieren (wttr.in)"
|
|
/>
|
|
|
|
<div className="glass-card p-6">
|
|
<IntegrationForm integration={integration} onSaved={setIntegration}>
|
|
{(config, setConfig) => (
|
|
<>
|
|
<FormField label="Primärer Standort" description="Stadt oder Koordinaten für die Hauptanzeige">
|
|
<TextInput
|
|
value={(config.primary_location as string) || ""}
|
|
onChange={(v) => setConfig("primary_location", v)}
|
|
placeholder="z.B. Berlin oder 52.52,13.405"
|
|
/>
|
|
</FormField>
|
|
|
|
<FormField label="Sekundärer Standort" description="Zweiter Standort für den Vergleich">
|
|
<TextInput
|
|
value={(config.secondary_location as string) || ""}
|
|
onChange={(v) => setConfig("secondary_location", v)}
|
|
placeholder="z.B. München oder 48.137,11.576"
|
|
/>
|
|
</FormField>
|
|
</>
|
|
)}
|
|
</IntegrationForm>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|