From d5b6c55383310696bed01841e9b339c6b786a3f5 Mon Sep 17 00:00:00 2001 From: Bobban Rydh Date: Fri, 18 Sep 2026 19:57:03 +0200 Subject: [PATCH] Simplify Integrations to a plain list + Add button Flagged back when Proxmox/Synology got their own pages: once all six integration types had moved to dedicated pages, the "browsing" dropdown here only ever showed one of six near-identical "go manage this on its own page" redirects, and its final fallback branch was dead code (every IntegrationType was already covered). Now that all the actual data views have moved out, drop that mode entirely. The page is now just what "Manage integrations" already was: a sortable, CSV-exportable table of configured integrations (name/type/ status), visible to every role since none of that is sensitive, with an admin-only "Add integration" button and per-row edit/enable-disable/ delete actions. No more mode toggle, no provider-picker dropdown, no per-type redirect cards -- the six dedicated pages (and the sidebar nav that already points at them) are how you actually use each integration now. Co-Authored-By: Claude Sonnet 5 --- README.md | 7 +- web/src/pages/Integrations.tsx | 280 +++++++++++++-------------------- 2 files changed, 115 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 7d7ac65..4fe5452 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,11 @@ All modules from the original plan are built: start/stop/restart actions, a host filter, and a live running/total widget. - Add/edit/enable/disable/remove each integration's credential itself - under Integrations → Manage integrations. + The Integrations page itself is now just a list of configured + integrations (name/type/status, visible to every role) with an + admin-only "Add integration" button and edit/enable/disable/delete + actions per row — the six dedicated pages above are where you + actually use each one. - Every table in the app is click-to-sort on any column (numbers, booleans, and dates/text sort correctly regardless of how the column formats them) and has an "Export CSV" button next to it that exports whatever's diff --git a/web/src/pages/Integrations.tsx b/web/src/pages/Integrations.tsx index 301730e..ef268c1 100644 --- a/web/src/pages/Integrations.tsx +++ b/web/src/pages/Integrations.tsx @@ -1,5 +1,4 @@ import { useEffect, useState, type CSSProperties } from "react"; -import { Link } from "react-router-dom"; import { api, type CurrentUser, type IntegrationSummary, type IntegrationType } from "../api/client"; import IntegrationForm from "../components/IntegrationForm"; import IntegrationEditForm from "../components/IntegrationEditForm"; @@ -24,12 +23,10 @@ function typeBadgeStyle(colors: Record, type: IntegrationType): export default function Integrations({ user }: { user: CurrentUser }) { const isAdmin = user.role === "admin"; - const canEdit = user.role === "admin" || user.role === "operator"; const [integrations, setIntegrations] = useState(null); - const [managing, setManaging] = useState(false); + const [adding, setAdding] = useState(false); const [editingIntegration, setEditingIntegration] = useState(null); - const [selectedId, setSelectedId] = useState(null); const [error, setError] = useState(null); const [integrationColors, setIntegrationColors] = useState>({}); @@ -40,17 +37,12 @@ export default function Integrations({ user }: { user: CurrentUser }) { function loadIntegrations() { api.integrations .list() - .then((res) => { - setIntegrations(res.integrations); - if (!selectedId && res.integrations.length > 0) setSelectedId(res.integrations[0].id); - }) + .then((res) => setIntegrations(res.integrations)) .catch((err) => setError(String(err))); } useEffect(loadIntegrations, []); - const selected = integrations?.find((i) => i.id === selectedId) ?? null; - async function toggleEnabled(i: IntegrationSummary) { try { await api.integrations.update(i.id, { enabled: !i.enabled }); @@ -64,7 +56,6 @@ export default function Integrations({ user }: { user: CurrentUser }) { if (!confirm(`Delete integration "${i.name}"?`)) return; try { await api.integrations.remove(i.id); - if (selectedId === i.id) setSelectedId(null); loadIntegrations(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); @@ -85,172 +76,121 @@ export default function Integrations({ user }: { user: CurrentUser }) { return ( <>
-

Integrations

- {isAdmin && ( - )}
{error &&
{error}
} - {managing ? ( - <> - {editingIntegration ? ( - { - setEditingIntegration(null); - loadIntegrations(); - }} - onCancel={() => setEditingIntegration(null)} - /> - ) : ( - setManaging(false)} /> - )} -
-
- -
-
- - - - label="Name" sortKeyName="name" activeKey={sortKey} direction={sortDir} onSort={requestSort} /> - label="Type" sortKeyName="type" activeKey={sortKey} direction={sortDir} onSort={requestSort} /> - - label="Status" - sortKeyName="enabled" - activeKey={sortKey} - direction={sortDir} - onSort={requestSort} - /> - - - - - {sortedIntegrations?.map((i) => ( - - - - - - - ))} - {sortedIntegrations?.length === 0 && ( - - - - )} - -
Actions
{i.name} - - {TYPE_LABELS[i.type]} - - - - -
- - -
-
- No integrations configured yet. -
-
-
- + {editingIntegration ? ( + { + setEditingIntegration(null); + loadIntegrations(); + }} + onCancel={() => setEditingIntegration(null)} + /> ) : ( - <> - {integrations?.length === 0 ? ( -
-
- No integrations configured yet. - {isAdmin && ( - <> - {" "} - - . - - )} -
-
- ) : ( - <> -
- -
- - {selected?.type === "tailscale" ? ( -
-
- Manage this integration's devices on the Tailscale page. -
-
- ) : selected?.type === "gitea" ? ( -
-
- Manage this integration's repositories on the Gitea page. -
-
- ) : selected?.type === "semaphore" ? ( -
-
- Manage this integration's templates on the Semaphore page. -
-
- ) : selected?.type === "proxmox" ? ( -
-
- Manage this integration's VMs & containers on the Proxmox page. -
-
- ) : selected?.type === "synology" ? ( -
-
- View this integration's storage on the Synology page. -
-
- ) : selected?.type === "dockhand" ? ( -
-
- Manage this integration's containers on the Docker page. -
-
- ) : ( -
-
- {selected ? `${TYPE_LABELS[selected.type]} isn't wired up in the UI yet.` : "Select an integration."} -
-
- )} - - )} - + adding && ( + { + setAdding(false); + loadIntegrations(); + }} + onCancel={() => setAdding(false)} + /> + ) )} + +
+
+ +
+
+ + + + label="Name" sortKeyName="name" activeKey={sortKey} direction={sortDir} onSort={requestSort} /> + label="Type" sortKeyName="type" activeKey={sortKey} direction={sortDir} onSort={requestSort} /> + + label="Status" + sortKeyName="enabled" + activeKey={sortKey} + direction={sortDir} + onSort={requestSort} + /> + {isAdmin && } + + + + {sortedIntegrations?.map((i) => ( + + + + + {isAdmin && ( + + )} + + ))} + {sortedIntegrations?.length === 0 && ( + + + + )} + +
Actions
{i.name} + + {TYPE_LABELS[i.type]} + + + {isAdmin ? ( + + ) : ( + + {i.enabled ? "Enabled" : "Disabled"} + + )} + +
+ + +
+
+ No integrations configured yet. + {isAdmin && ( + <> + {" "} + + . + + )} +
+
+
); }