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 && ( + <> + {" "} + + . + + )} +
+
+
); }