feat(groups): host-based forge sources with optional display name

- forgeSources entries are now { host, type, name? }: the repository URL's
  hostname is matched case-insensitively against host (github.com for GitHub,
  distinct hosts for multiple Gitea instances); the footer label is the
  optional name, falling back to the host
- GroupEditor renders one row per source: host input + type select + optional
  display name (grid layout); hostname validation mirrors the server
- fix: apiFetch sends Content-Type: application/json — h3's readBody only
  parses JSON bodies with that header, so every PUT/POST from the refactored
  console arrived as a raw string and failed with 'groups must be an array'
- hardening: readJsonBody (admin + actions) JSON-parses string bodies so curl
  and older clients without the content-type header still work
- regression test: groups PUT without content-type + forgeSources round-trip
- docs: groups.md/message-format.md (en/zh), AGENTS.md, config.example.yaml
This commit is contained in:
RhenCloud 2026-08-14 08:49:48 +08:00
parent 17d10db845
commit 3f6f7f17b5
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
19 changed files with 469 additions and 108 deletions

View file

@ -478,6 +478,32 @@
@apply mt-1 w-full justify-center;
}
.forge-source-row {
@apply mb-2 grid items-center gap-2 rounded-sm border border-border bg-surface-2 p-2.5;
grid-template-areas:
"host type del"
"name name name";
grid-template-columns: 1fr auto auto;
}
.forge-source-row .f-host {
grid-area: host;
}
.forge-source-row .select.forge-type {
grid-area: type;
@apply w-[110px];
}
.forge-source-row .f-name {
grid-area: name;
}
.forge-source-row > .icon-btn {
grid-area: del;
justify-self: end;
}
/* ---- Toasts ---- */
.toasts {
@apply pointer-events-none fixed bottom-7 left-1/2 z-[60] flex -translate-x-1/2 flex-col items-center gap-2;