Extend badge color settings to cover integration types too

Settings → DNS Badges only let you customize DNS provider badge
colors. Expand it into a general Badges page with a second section
for the six integration types (Tailscale, Proxmox, Synology,
Semaphore, Gitea, Dockhand), applied to the type badge in the Manage
integrations table.

- New integrationColors key on AppSettings, stored/merged the same way
  as providerColors via the existing settingsStore.
- New GET /api/settings/integration-colors — non-secret, any signed-in
  user, mirroring /provider-colors — so the badge color can be read
  without needing admin access to the full settings payload.
- Renamed DnsBadgeSettings.tsx -> BadgeSettings.tsx (route
  /settings/dns-badges -> /settings/badges, sub-nav label "DNS
  Badges" -> "Badges") with both color sections saved together.

Verified end-to-end against the real dev server: PUT persists
integration colors independently of provider colors, the public
integration-colors endpoint reflects updates immediately, and the
change is audit-logged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
bobbanandClaude Sonnet 5 committed 2026-09-15 21:28:26 +02:00
1 parent 7fed1dbfa4
commit ad6783b6a1
9 files changed
+175 -100

No files matched your search

+17 -2
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useState, type CSSProperties } from "react";
import {
api,
type CurrentUser,
@@ -130,6 +130,12 @@ const TYPE_LABELS: Record<IntegrationType, string> = {
dockhand: "Dockhand",
};
function typeBadgeStyle(colors: Record<string, string>, type: IntegrationType): CSSProperties {
const color = colors[type];
if (!color) return {};
return { backgroundColor: `${color}22`, color, border: `1px solid ${color}55` };
}
export default function Integrations({ user }: { user: CurrentUser }) {
const isAdmin = user.role === "admin";
const canEdit = user.role === "admin" || user.role === "operator";
@@ -139,6 +145,11 @@ export default function Integrations({ user }: { user: CurrentUser }) {
const [editingIntegration, setEditingIntegration] = useState<IntegrationSummary | null>(null);
const [selectedId, setSelectedId] = useState<number | null>(null);
const [error, setError] = useState<string | null>(null);
const [integrationColors, setIntegrationColors] = useState<Record<string, string>>({});
useEffect(() => {
api.settings.integrationColors().then((res) => setIntegrationColors(res.integrationColors)).catch(() => {});
}, []);
const [tailscaleData, setTailscaleData] = useState<TailscaleDevicesResponse | null>(null);
const [tailscaleError, setTailscaleError] = useState<string | null>(null);
@@ -418,7 +429,11 @@ export default function Integrations({ user }: { user: CurrentUser }) {
{integrations?.map((i) => (
<tr key={i.id}>
<td>{i.name}</td>
<td>{TYPE_LABELS[i.type]}</td>
<td>
<span className="badge" style={typeBadgeStyle(integrationColors, i.type)}>
{TYPE_LABELS[i.type]}
</span>
</td>
<td>
<button
className={`badge ${i.enabled ? "bg-green-lt text-green" : "bg-secondary-lt text-secondary"} border-0`}