Manage server tags in Settings: pre-add tags, recolour, rename, delete

New Settings > Tags tab (admin) listing every tag -- ones servers use and
ones added ahead of time -- with how many servers carry each:
- Add a tag before anything uses it, optionally with a colour. Such tags
  are offered as one-click "Add:" chips (and datalist suggestions) when
  tagging a server, so the same word gets spelled the same way everywhere.
- Give any tag a colour of your choosing, in use or not, or reset it to the
  automatic one. Changes are drafted with Save/Cancel rather than saved as
  the picker drags. Colours show on the Servers page, its tag filter bar,
  the detail page and the editor, and update everywhere without a reload
  through one shared cached colour map.
- Rename a tag; every server that has it is rewritten. Renaming to a name
  that already exists merges the two after a confirmation naming what will
  happen; the target keeps its own colour unless it had none, and a server
  carrying both ends up with one.
- Delete a tag, which removes it from every server that has it, with a
  confirmation stating how many.

Tags still live on the servers (servers.tags); a new tag_definitions table
holds only what a server can't: existence before use, and a colour. A
defined tag stays listed until an admin deletes it, even with no servers.
Rename and delete change the servers and the catalogue in one transaction
so they can't disagree. Only servers that actually carry the tag are
rewritten and counted -- an earlier draft also counted servers whose tags
merely weren't in sorted order, which the tests caught.

Reading the list and colours is open to everyone signed in (needed to draw
tags anywhere); changing the catalogue is admin-only, while tagging a
server stays an operator action. Names go through the same normalisation
as before, colours must be #rrggbb, and every change is audit-logged.

New table tag_definitions (migration 0013).

Verified with 44 backend checks (list/counts, roles, create/adopt/
duplicate/rejects, colour set/reset, rename incl. defined, undefined and
unused tags, merge colour rules and both-sides servers, delete, audit, and
that normal tagging still works afterwards) and in a browser against the
real routers: add with colour, set/reset a colour and see it change on the
Servers page live, merge with confirmation, delete, and the error path.
Not clicked through: the quick-add chips inside the tag editor on a
server's detail page (typechecked; same colour code as the rest).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
bobbanandClaude Sonnet 5 committed 2026-09-26 21:17:09 +02:00
1 parent 72f8c85406
commit ae64cb345c
15 files changed
+2136 -25

No files matched your search

+19
View File
@@ -570,6 +570,14 @@ export interface PrivacyOverview {
};
}
export interface TagEntry {
name: string;
/** "#rrggbb", or null for the automatic colour. */
color: string | null;
/** Servers currently carrying it. */
count: number;
}
export interface DockhandContainer {
id: string;
name: string;
@@ -934,6 +942,17 @@ export const api = {
remove: (id: number, portId: number) => request<void>(`/api/servers/${id}/ports/${portId}`, { method: "DELETE" }),
},
},
tags: {
list: () => request<{ tags: TagEntry[] }>("/api/tags"),
create: (name: string, color?: string) =>
request<{ tags: TagEntry[] }>("/api/tags", { method: "POST", body: JSON.stringify({ name, color }) }),
setColor: (name: string, color: string | null) =>
request<{ tags: TagEntry[] }>("/api/tags/color", { method: "PUT", body: JSON.stringify({ name, color }) }),
rename: (from: string, to: string) =>
request<{ tags: TagEntry[]; updatedServers: number; merged: boolean }>("/api/tags/rename", { method: "POST", body: JSON.stringify({ from, to }) }),
remove: (name: string) =>
request<{ tags: TagEntry[]; updatedServers: number }>("/api/tags/delete", { method: "POST", body: JSON.stringify({ name }) }),
},
privacy: {
overview: () => request<PrivacyOverview>("/api/privacy"),
exportOwnData: async (): Promise<Blob> => {