feat(projects): group Servers as an organizational lens#31
Merged
Conversation
- Migration: enable citext; create projects (binary_id, unique case- insensitive name, optional description/color); add nullable servers.project_id with on_delete: :nilify_all and index. - Schema Mast.Fleet.Project with color preset whitelist (slate, indigo, emerald, amber, rose, violet), trimmed name, length validation. - Context Mast.Fleet.Projects: list/get/create/update/delete plus assign_server/unassign_server. Every write goes through Multi and Audit.multi_log; rename and recolor each emit their own audit event only when the field actually changes. delete_project cascades into one server.project_unassigned per affected server inside the same transaction. - Server schema gains belongs_to :project and accepts :project_id on the changeset with a foreign-key constraint. Refs #24
…frames - CLAUDE.md UI section: any new helper in lib/mast_web/components/ui/* must be responsive across the full breakpoint scale on day one, and added to /dev/ui for multi-width preview. Patching to responsiveness later is a sweep we have already done twice. - components.pen: pulled the Projects updates from main (new frame 'Fleet Overview - Projects' and new reusable 'ProjectGroup/Header'). Refs #24
- New tab 'Projects' on SettingsLive mirrors the SSH Keys pattern: inline add form, list with color swatch + server count, inline edit (rename + recolor + description), delete with confirm. - ProjectsTab template extracted to MastWeb.SettingsLive.ProjectsTab so SettingsLive stays under the ~500 LOC house limit. - Responsive by default: flex-wrap headers, sm: padding scales, min-w-0 + truncate on names, ≥40px tap targets for icon buttons, color picker wraps on narrow viewports. - color_swatch/1 helper maps preset colors to bg-* classes; reused by Slice 4 (Fleet group header + Server badge). Refs #24
Add Server modal (DashboardLive): - Project select with inline 'Add new project' affordance, mirroring the SSH key flow. Saving a new project selects it on the server form automatically. - Generalized server_form_with/3 helper (was server_form_with_key/2). Server settings tab (ServerLive): - New 'Project' card with a Save-on-submit dropdown. Reassignment routes through Projects.assign_server/2 or unassign_server/1, which fire the server.project_(un)assigned audit events. Blank selection unassigns. Same-value selection is a no-op. Tests: - 4 new DashboardLive tests (Project dropdown, submit with project_id, inline create toggle, inline create selects the new project). - 3 new ServerLive tests (settings dropdown present, assign emits audit event, blank value unassigns). Refs #24
New UI helpers in MastWeb.Components.UI.Domain: - ui_project_group_header: chevron + colored dot + name + 'N servers' count. Pass a phx-click map via :toggle to make it collapsible; static when toggle is empty. Matches ProjectGroup/Header pen comp. - ui_project_badge: small color-tinted chip used near the Server detail header and elsewhere. Title attribute reads 'Project: <name>'. - project_color_bg/1 helper maps preset colors to bg-* classes. Fleet page (DashboardLive): - Servers belonging to a Project render under their group header; ungrouped servers render as plain cards alongside. No 'Unassigned' pseudo-group. Per-group expand/collapse tracked in a MapSet. Server detail (ServerLive.Header): - Renders the Project badge next to the page title when set. - ServerLive loads the project on mount and re-loads it on update-project so the badge reflects assignment changes immediately. DevUi (/dev/ui): - Adds preview cards for the two new components so engineers can eyeball them at every breakpoint via the existing width toggle. Tests: - Fleet grouping: header renders with member count; ungrouped server still shows; no 'Unassigned' string; collapse toggle removes the cards. - Server header: badge renders 'Project: <name>' when assigned; absent otherwise. - DevUi smoke: both new previews appear on the page. Refs #24
…oject card Settings → Projects: - Convert the inline Add Project panel to a ui_modal mirroring the Add Server pattern (title + subtitle, footer with Cancel + Save). Avoids the awkward overlap where the empty state showed under an open create form on the same page. Server detail → Settings → Project card: - Title + description block at the top, full-width select on its own row, Save button anchored bottom-right. The previous flex-end row collided the label, select, and button at row height ~32px. Refs #24
Swap the 4th Fleet Overview stat tile from 'Seen Recently' to 'Releases' (count of all Release rows across the fleet), matching the 'Fleet Overview - Projects' pen frame. Adds Fleet.count_all_releases/0. Rebuild ui_server_card to match Card/Server in the same pen frame: - Server icon tile + name + host stacked top-left, status badge top-right - Meta line with OS + last-seen relative - CPU / MEM / DISK in a 3-col grid with labeled bars instead of stacked DevUi sample server gets host, os_id, and last_seen_at so the preview renders the new chrome. Refs #24
When at least one Project group renders, the ungrouped server cards flowed inline below the last group with no visual break, so a server that belonged to no project looked like a member of the last group. Add a horizontal divider with an 'Ungrouped' label and count above the orphan grid, but only when groups are also present. Pure ungrouped fleets (no projects assigned anywhere) keep the clean unlabeled grid. Refs #24
The border-t divider looked heavy, especially in dark mode. Match the chrome of ui_project_group_header (label + count, no line) so the 'Ungrouped' header reads as the same kind of section header as a project group, just without a chevron or color dot. Refs #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #24.
Summary
Adds Project as a named grouping of Servers. A Server belongs to
at most one Project. Projects are purely an organizational lens on
the Fleet page and a badge on Server detail. No Project page, no
aggregated views, no drag-drop in v1 (per the issue).
What landed
Domain —
Mast.Fleet.Project+Mast.Fleet.Projectscontext withcase-insensitive uniqueness (citext), preset color palette
(
slate, indigo, emerald, amber, rose, violet), and a Multi-basedcascade on delete that nulls
servers.project_idand emits oneserver.project_unassignedper affected server inside the sametransaction.
Audit events (all wired):
project.created,project.renamed,project.recolored,project.deleted,server.project_assigned,server.project_unassigned.UI:
rename + recolor / delete with confirm).
that auto-selects the new project on save (mirrors the SSH key
flow).
ui_project_group_headerrows. Servers without a Project renderunder an "Ungrouped" header when projects exist, or as a plain
grid when none do. No "Unassigned" pseudo-group.
title.
ui_project_group_headerandui_project_badgehelpers, bothpreviewed on
/dev/ui.Drive-by polish (caught during verification):
Recently) to match the
Fleet Overview - Projectspen frame.Adds
Fleet.count_all_releases/0.ui_server_cardrebuilt to match Card/Server in the pen (icontile, name + host, status badge, OS + last-seen meta row, 3-col
labeled CPU/MEM/DISK bars).
Docs / process:
responsive on day one and added to
/dev/ui. (We've done theresponsive sweep twice already.)
[Unreleased]updated.Test coverage
test/mast/fleet/projects_test.exs— 16 unit tests covering CRUD,case-insensitive uniqueness, color whitelist, cascade unassign,
assign/unassign no-ops, audit event shape.
test/mast_web/live/settings_live_test.exs— 5 LiveView tests forthe Projects tab (renders, lists with count, inline create, inline
rename, delete).
test/mast_web/live/dashboard_live_test.exs— 6 tests for the AddServer modal Project picker (dropdown, submit with project_id,
inline create toggle, inline create selects new) and Fleet
grouping (group header with count + ungrouped coexistence,
collapse toggle).
test/mast_web/live/server_live_test.exs— 5 tests for thesettings tab Project field (dropdown present, assign emits audit
event, blank value unassigns) and the header Project badge
(renders when assigned, absent otherwise).
test/mast_web/live/dev_ui_live_test.exs— smoke for the two newcomponent previews.
mix precommitclean: compile-warnings-as-errors, deps.unlock,format, credo --strict, 310 tests passing.
Migration
priv/repo/migrations/20260523192231_create_projects.exs— enablescitext, createsprojects(binary_id, unique citextname,optional
description/color), adds nullableservers.project_idwith
on_delete: :nilify_all+ index. No backfill (per spec).Commits