feat(260924-i8v): Titel und Serverauswahl der Proxmox-Kachel an der Kachel und in den Einstellungen

- ProxmoxServerPicker gemeinsam fuer Kachel und Einstellungsformular
- Bearbeitungsmodus: entprelltes Titelfeld, Server auswaehlen statt Liste, sofort gespeichert
- ProxmoxWidgetConfigForm unter Einstellungen > Dashboard, Titel-Zusatz in der Kopfzeile

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 13:33:48 +02:00
parent 377b6e37b2
commit 586da44fa1
6 changed files with 298 additions and 33 deletions
@@ -0,0 +1,81 @@
'use client';
import { useTranslations } from 'next-intl';
import { type ReactNode, useEffect, useState } from 'react';
import { resolveProxmoxWidgetConfig } from '@/components/dashboard/widgets/proxmox-widget-model';
import { ProxmoxServerPicker } from '@/components/proxmox/proxmox-server-picker';
// T-I8V-02: nur die Leseabfrage des Zwischenlagers, nie die manuelle Abfrage.
import { listServers, type ProxmoxServer } from '@/lib/proxmox-api';
interface ProxmoxWidgetConfigFormProps {
config: Record<string, unknown>;
onChange: (cfg: Record<string, unknown>) => void;
}
/**
* Einstellungen der Proxmox-Kachel unter Einstellungen > Dashboard
* (quick-260924-i8v): Titel und Serverauswahl. Laedt die Serverliste einmal;
* dieselbe Auswahl gibt es an der Kachel im Bearbeitungsmodus (dort auch
* fuer Kacheln auf weiteren Reitern, die diese Seite nicht zeigt).
*/
export function ProxmoxWidgetConfigForm({ config, onChange }: ProxmoxWidgetConfigFormProps) {
const t = useTranslations('widgets');
const tp = useTranslations('proxmox');
const { title: rawTitle, serverIds } = resolveProxmoxWidgetConfig(config);
// Anzeige getrimmt (Leerraum-Titel sind fachlich „kein Titel“), gesendet
// wird der rohe Tippwert — Muster FavoritesConfig.
const title = rawTitle.trim() === '' ? '' : rawTitle;
const [servers, setServers] = useState<ProxmoxServer[] | null>(null);
const [loadFailed, setLoadFailed] = useState(false);
useEffect(() => {
let cancelled = false;
listServers()
.then((list) => {
if (!cancelled) setServers(list);
})
.catch(() => {
if (!cancelled) setLoadFailed(true);
});
return () => {
cancelled = true;
};
}, []);
let serverPart: ReactNode;
if (loadFailed) {
serverPart = <p className="text-sm text-muted-foreground">{tp('loadError')}</p>;
} else if (servers === null) {
serverPart = <p className="text-sm text-muted-foreground">{tp('loading')}</p>;
} else if (servers.length === 0) {
serverPart = <p className="text-sm text-muted-foreground">{t('proxmox.empty')}</p>;
} else {
serverPart = (
<ProxmoxServerPicker
servers={servers}
selectedIds={serverIds}
onChange={(next) => onChange({ serverIds: next })}
/>
);
}
return (
<div className="space-y-4">
<div>
<label htmlFor="proxmox-widget-title" className="mb-1 block text-sm text-foreground">
{t('proxmox.titleLabel')}
</label>
<input
id="proxmox-widget-title"
type="text"
className="h-9 w-full max-w-xs rounded border border-border bg-background px-3 text-sm text-foreground"
value={title}
placeholder={t('proxmox.titlePlaceholder')}
onChange={(e) => onChange({ title: e.target.value })}
/>
</div>
{serverPart}
</div>
);
}
@@ -5,6 +5,7 @@ import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { WIDGET_REGISTRY, type WidgetType } from '@/components/dashboard/widget-registry';
import { PictureFrameConfigForm } from '@/components/settings/picture-frame-config-form';
import { ProxmoxWidgetConfigForm } from '@/components/settings/proxmox-widget-config-form';
import { SearchProviderForm } from '@/components/settings/search-provider-form';
import { XframeConfigForm } from '@/components/settings/xframe-config-form';
import { updateWidgetConfig } from '@/lib/dashboard-api';
@@ -122,7 +123,8 @@ export function WidgetSettingsPanel({
{t(def.nameKey)} #{idx + 1}
{(widget.widgetType === 'note' ||
widget.widgetType === 'favorites' ||
widget.widgetType === 'xframe') &&
widget.widgetType === 'xframe' ||
widget.widgetType === 'proxmox') &&
typeof widget.config.title === 'string' &&
widget.config.title.trim() !== '' && (
<span className="ml-2 text-muted-foreground">
@@ -232,6 +234,16 @@ export function WidgetSettingsPanel({
}
/>
)}
{/* Proxmox: Titel und Serverauswahl (quick-260924-i8v) */}
{widget.widgetType === 'proxmox' && (
<ProxmoxWidgetConfigForm
config={widget.config}
onChange={(cfg) =>
handleConfigChange(widget.id, cfg)
}
/>
)}
</div>
)}
</div>