Skip to content

[ Feature - Dashboard ] Topology Grouping schema + assignment endpoints #127

Description

@mitulgarg

Phase 2 — Grouping schema + assignment endpoints

 Backend

 src/env_doctor/server/models.py — add to Machine:

 group_name = Column(String(64), nullable=True, index=True)

 Base.metadata.create_all() auto-adds the column on next startup for new DBs. For existing DBs SQLite will not
 retroactively add columns — add a one-shot migration in database.py:init_db() that runs ALTER TABLE machines ADD
 COLUMN group_name VARCHAR(64) inside a try/except OperationalError (idempotent: the OperationalError on second run
 means the column already exists).

 src/env_doctor/server/routes.py — add two endpoints:

 - PATCH /api/machines/{id} — body MachineUpdate(group_name: str | None). Pydantic model with group_name:
 Optional[str] = Field(None, max_length=64). Empty string → None (ungroup). Sanitise: strip whitespace, reject names
 containing / or control chars.
 - GET /api/groups — returns [{name, machine_count, status_breakdown: {pass, warning, fail}}]. Single SQL: SELECT
 group_name, COUNT(*), SUM(CASE WHEN latest_status='pass'...) GROUP BY group_name. Treat NULL as the synthetic group
 "ungrouped" in the response.

 POST /report ingest (routes.py ~line 100): if the inbound payload includes machine.group_name, persist it. Optional —
  keeps CLI ↔ dashboard symmetric so a machine can self-tag at registration time. Don't require it.

 Frontend types — web/src/types.ts

 - Add group_name: string | null to both MachineListItem and MachineDetail.
 - Add interface MachineGroup { name: string; machine_count: number; status_breakdown: { pass: number; warning:
 number; fail: number } }.

 Frontend API — web/src/api.ts

 - updateMachineGroup(id: string, group_name: string | null): Promise<MachineDetail> → PATCH wrapper.
 - getGroups(): Promise<MachineGroup[]>.

 MachineDetail inline-edit (web/src/pages/MachineDetail.tsx)

 Add a "Group" row in the summary cards block (~line 114-127). Display: current group name or "—". Click to edit →
 small inline <GroupPicker> (see component below). On save → updateMachineGroup() → optimistic update + refetch. This
 is the first inline-edit pattern in the app — keep it minimal: no modal, just an in-place swap between label and
 picker.

 New shared component — web/src/components/GroupPicker.tsx

 Compact combobox (no library — <input list> won't style well in dark theme):
 - Text input with a dropdown showing existing groups (from getGroups()).
 - Free typing creates a new group on submit (no separate "create" step — the group exists implicitly once a machine
 is assigned to it).
 - Empty submit → ungroup.
 - Props: { value: string | null, onChange: (next: string | null) => void, groups: MachineGroup[] }.
 - Reused by MachineDetail (inline) and by Topology's floating action bar (Phase 3) and Fleet's filter chip (Phase 3).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions