Group all integration node tiles by integration except Proxmox (#39)
Generalizes the Uptime Kuma monitor-grouping pattern to every integration: Node Status now collapses each integration's resources into one tile (e.g. 30 EC2 instances under one "AWS" tile) instead of flooding the grid, with members listed in Node Detail on selection. Proxmox stays ungrouped since its VMs/LXCs are managed individually elsewhere in the app. Adds integrationType to the /api/integrations/resources response so the frontend can group/exclude by adapter type rather than resource kind (kind alone can't distinguish Proxmox VMs from AWS VMs, for example). Documents the grouping rule in HANDOFF.md and adds a paid-tier roadmap entry for per-integration node tabs that will show every individual node. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9a1d2803a2
commit
9f10e8ee6f
5 changed files with 45 additions and 12 deletions
|
|
@ -54,6 +54,7 @@ No new feature is queued. Pick up from here:
|
|||
- `backend/src/routes/` — one file per route group (`auth`, `bookmarks`, `integrations`, `events`, `terminal`, `tunnels`, `files`, `docker`, `dockerSsh`, `agents`, `guacamole`, `metrics`, `transfer`, `data`).
|
||||
- `backend/src/routes/auth.ts` — `/api/setup` (first-run, creates the first admin user), `/api/auth/login`, `/api/auth/me` (GET/PUT), `/api/auth/password`, `/api/auth/sessions`, `/api/auth/logout`, `/api/auth/login-events` (Phase 2), plus user-management endpoints `/api/users` (GET/POST) and `/api/users/:id` (PUT/DELETE) gated by `requireAdmin` (Phase 3).
|
||||
- `backend/src/integrations/` — the 8 integration adapters (Proxmox, Docker, NetBird, Cloudflare, AWS, Uptime Kuma, Weather, SSH).
|
||||
- **Node Status grouping rule**: `GET /api/integrations/resources` tags every resource with `integrationType` (the adapter's `IntegrationType`, e.g. `'aws'`, `'docker'`). `Infrastructure.tsx`'s Node Status tab collapses every integration's resources into **one tile per integration** — except Proxmox (`ungroupedIntegrationTypes` in `Infrastructure.tsx`), which stays ungrouped since its VMs/LXCs are managed individually elsewhere in the app. Clicking a grouped tile lists its members in the Node Detail card. This means e.g. 30 EC2 instances under one AWS integration show as a single "AWS" tile, not 30 separate tiles. See `ROADMAP.md` for the planned paid-tier per-integration tabs that will surface every individual node.
|
||||
- `backend/src/ssh/` — SSH-backed feature engines: terminal sessions, tunnels, file ops, host metrics collectors, host-to-host transfer, and `docker.ts` (**Docker-over-SSH** — runs the `docker` CLI on a remote SSH host; PR #31).
|
||||
- Docker images run on Alpine; **OpenSSL legacy provider is enabled** in `backend/Dockerfile` (`OPENSSL_CONF=/etc/ssl/openssl-legacy.cnf`) so old-format encrypted PEM keys (`BEGIN RSA PRIVATE KEY` + `DEK-Info`) still decrypt under OpenSSL 3 — don't remove this without understanding why it's there.
|
||||
- **Required env vars, no defaults**: `ARCHNEST_SECRET_KEY`, `ARCHNEST_JWT_SECRET`. Server refuses to start without both. Optional: `ARCHNEST_DB_PATH`, `PORT`, `ARCHNEST_GUAC_CRYPT_KEY`/`ARCHNEST_GUACD_HOST`/`ARCHNEST_GUACD_PORT`, `ARCHNEST_CORS_ORIGIN`, **`ARCHNEST_AGENT_TOKEN`** (enables the Docker agent ingest endpoint — when unset, ingest is disabled / returns 503), **`ARCHNEST_AGENT_STALE_MS`** (default 90000; when an agent report is considered stale).
|
||||
|
|
|
|||
21
ROADMAP.md
21
ROADMAP.md
|
|
@ -136,6 +136,27 @@ A complementary **agent** model is planned, split across tiers:
|
|||
|
||||
---
|
||||
|
||||
## Per-integration node tabs — PAID ADD-ON
|
||||
|
||||
**Status:** not built; planned as a paid-tier feature.
|
||||
|
||||
Node Status on the Infrastructure page collapses every integration (except
|
||||
Proxmox) into a **single tile per integration** — e.g. 30 EC2 instances under
|
||||
one "AWS" tile, all of Uptime Kuma's monitors under one "Uptime" tile — with
|
||||
the individual members only visible in the Node Detail card after selecting
|
||||
that tile (`ungroupedIntegrationTypes` in `src/pages/Infrastructure.tsx`).
|
||||
This keeps the grid usable when an integration has dozens/hundreds of
|
||||
resources, but it means there's currently no way to see *all* nodes of a
|
||||
given integration laid out at once.
|
||||
|
||||
Planned scope (paid tier): a dedicated **tab per integration** (alongside
|
||||
today's Overview/Network etc. sub-tabs) that lists every node belonging to
|
||||
that integration — full grid, not just the grouped summary tile — for users
|
||||
who want to browse/filter dozens of EC2 instances, Docker containers, or
|
||||
Uptime Kuma monitors directly rather than drilling through Node Detail.
|
||||
|
||||
---
|
||||
|
||||
## Known non-blocking stubs (cosmetic, not scheduled)
|
||||
|
||||
Not flagged as work to do unless explicitly asked:
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export async function integrationRoutes(app: FastifyInstance) {
|
|||
|
||||
app.get('/api/integrations/resources', async () => {
|
||||
const rows = db.prepare("SELECT * FROM integrations WHERE enabled = 1 AND status = 'connected'").all() as IntegrationRow[]
|
||||
const resources: (Resource & { integration: string })[] = []
|
||||
const resources: (Resource & { integration: string; integrationType: string })[] = []
|
||||
for (const row of rows) {
|
||||
const adapter = adapterRegistry[row.type as IntegrationType]
|
||||
if (!adapter.listResources) continue
|
||||
|
|
@ -151,7 +151,7 @@ export async function integrationRoutes(app: FastifyInstance) {
|
|||
const secrets = loadSecrets(row.id)
|
||||
try {
|
||||
const found = await adapter.listResources(config, secrets)
|
||||
for (const r of found) resources.push({ ...r, integration: row.name })
|
||||
for (const r of found) resources.push({ ...r, integration: row.name, integrationType: row.type })
|
||||
} catch (err) {
|
||||
app.log.warn(`listResources failed for integration "${row.name}" (${row.type}): ${err instanceof Error ? err.message : err}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -443,6 +443,7 @@ export interface Resource {
|
|||
status: 'healthy' | 'warning' | 'critical' | 'unknown'
|
||||
detail?: string
|
||||
integration: string
|
||||
integrationType: string
|
||||
kind?: 'vm' | 'container' | 'app' | 'host' | 'network'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,18 +68,26 @@ const nodeStatusColor: Record<string, string> = {
|
|||
|
||||
const integrationPalette = ['#C8A434', '#E67E22', '#2ECC71', '#7A7D85', '#3B82F6', '#8B5E3C']
|
||||
|
||||
// Kinds that represent a monitoring/aggregator app with many sub-items (e.g. Uptime
|
||||
// Kuma's individual monitors) collapse into one tile per integration on Node Status,
|
||||
// with the underlying items shown in Node Detail once selected.
|
||||
const groupedKinds = new Set(['app'])
|
||||
// Every integration except Proxmox collapses its resources into one tile per
|
||||
// integration on Node Status (e.g. 30 EC2 instances under one "AWS" tile, all of
|
||||
// Uptime Kuma's monitors under one "Uptime" tile) — the underlying items are listed
|
||||
// in Node Detail once selected. Proxmox stays ungrouped since its VMs/LXCs are
|
||||
// managed individually elsewhere in the app. See ROADMAP.md for the planned paid-tier
|
||||
// per-integration tabs that will show every node, not just the grouped tile.
|
||||
const ungroupedIntegrationTypes = new Set(['proxmox'])
|
||||
|
||||
const cdnIconByIntegrationKind: Record<string, string> = {
|
||||
app: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/uptime-kuma.png',
|
||||
const cdnIconByIntegrationType: Record<string, string> = {
|
||||
uptime_kuma: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/uptime-kuma.png',
|
||||
aws: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/aws.png',
|
||||
docker: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/docker.png',
|
||||
netbird: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/netbird.png',
|
||||
cloudflare: 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/cloudflare.png',
|
||||
}
|
||||
|
||||
interface NodeGroup {
|
||||
isGroup: true
|
||||
integration: string
|
||||
integrationType: string
|
||||
kind: string
|
||||
status: Resource['status']
|
||||
members: Resource[]
|
||||
|
|
@ -163,14 +171,15 @@ export default function Infrastructure() {
|
|||
return Array.from(byIntegration.entries()).map(([name, value], i) => ({ name, value, color: integrationPalette[i % integrationPalette.length] }))
|
||||
}, [resources])
|
||||
|
||||
// Kinds like Uptime Kuma's monitors can number in the hundreds — collapse them into
|
||||
// one tile per integration on Node Status, with members listed in Node Detail.
|
||||
// An integration can have dozens of resources (e.g. 30 EC2 instances) — collapse
|
||||
// everything except Proxmox into one tile per integration on Node Status, with
|
||||
// members listed in Node Detail.
|
||||
const nodeTiles = useMemo<NodeTile[]>(() => {
|
||||
if (!resources) return []
|
||||
const groups = new Map<string, Resource[]>()
|
||||
const singles: Resource[] = []
|
||||
for (const r of resources) {
|
||||
if (r.kind && groupedKinds.has(r.kind)) {
|
||||
if (!ungroupedIntegrationTypes.has(r.integrationType)) {
|
||||
const arr = groups.get(r.integration) ?? []
|
||||
arr.push(r)
|
||||
groups.set(r.integration, arr)
|
||||
|
|
@ -181,6 +190,7 @@ export default function Infrastructure() {
|
|||
const groupTiles: NodeGroup[] = Array.from(groups.entries()).map(([integration, members]) => ({
|
||||
isGroup: true,
|
||||
integration,
|
||||
integrationType: members[0]?.integrationType ?? '',
|
||||
kind: members[0]?.kind ?? '',
|
||||
status: members.some((m) => m.status === 'critical')
|
||||
? 'critical'
|
||||
|
|
@ -307,7 +317,7 @@ export default function Infrastructure() {
|
|||
{nodeTiles.length > 0 ? (
|
||||
<div className="scrollbar-ghost grid min-h-0 flex-1 grid-cols-5 content-start gap-2" style={{ overflowY: 'auto' }}>
|
||||
{nodeTiles.map((node, i) => {
|
||||
const cdnIcon = cdnIconByIntegrationKind[node.kind ?? '']
|
||||
const cdnIcon = cdnIconByIntegrationType[node.integrationType]
|
||||
const NodeIcon = kindIcon[node.kind ?? ''] ?? Server
|
||||
const label = 'isGroup' in node ? node.integration : node.name
|
||||
const tooltip = 'isGroup' in node ? `${node.integration}: ${node.members.length} monitored` : `${node.name}: ${node.detail ?? node.status}`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue