feat(web): settings with 9 sidebar sections and Agent upstream management

- /console/settings#<section>[/<anchor>]; legacy ?tab= redirected in clientLoader
- SETTINGS_SECTIONS in contracts with field-ownership test
- Agent section: upstream list, create wizard (provider -> credentials/model -> login/test/default),
  edit drawer with OAuth browser/device login, test, set default, delete
- sites section with site registration, snapshot/rate-limit drawer and account credentials
- path mappings moved into downloader / media-server drawers; metadata and account sections

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 03:05:51 +08:00
1 parent 459991421e
commit c67f6a9682
25 files changed
+3842 -942

No files matched your search

+49
View File
@@ -16,8 +16,11 @@ import type {
PathMappingUpdate,
Site,
SiteAccount,
SiteAccountUpdate,
SiteCreate,
SiteCredentialsPut,
SiteCredentialsResult,
SiteUpdate,
} from "@umt/contracts";
import { apiData } from "~/lib/api.ts";
import { type QueryArea, qk } from "~/lib/query-keys.ts";
@@ -115,6 +118,52 @@ export function usePutSiteCredentials() {
apiData<SiteCredentialsResult>("/sites/{id}/credentials", { method: "PUT", pathParams: { id: siteId }, body }),
onSuccess: () => {
qc.invalidateQueries({ queryKey: qk.all("site-accounts") });
qc.invalidateQueries({ queryKey: qk.all("sites") });
},
});
}
// ─── 站点登记与参数(设置"站点与账号";浏览在站点工作台) ───
function useInvalidateSites() {
const qc = useQueryClient();
return () => {
// sites.ts 用 ["sites", …],这里的列表用 qk.list("sites"),两者同前缀
qc.invalidateQueries({ queryKey: qk.all("sites") });
qc.invalidateQueries({ queryKey: qk.all("site-accounts") });
};
}
export function useCreateSite() {
const inv = useInvalidateSites();
return useMutation({
mutationFn: (body: SiteCreate) => apiData<Site>("/sites", { method: "POST", body }),
onSuccess: inv,
});
}
export function useUpdateSite() {
const inv = useInvalidateSites();
return useMutation({
mutationFn: ({ id, body }: { id: string; body: SiteUpdate }) =>
apiData<Site>("/sites/{id}", { method: "PATCH", pathParams: { id }, body }),
onSuccess: inv,
});
}
export function useUpdateSiteAccount() {
const inv = useInvalidateSites();
return useMutation({
mutationFn: ({ id, body }: { id: string; body: SiteAccountUpdate }) =>
apiData<SiteAccount>("/site-accounts/{id}", { method: "PATCH", pathParams: { id }, body }),
onSuccess: inv,
});
}
export function useRemoveSiteAccount() {
const inv = useInvalidateSites();
return useMutation({
mutationFn: (id: string) => apiData<unknown>("/site-accounts/{id}", { method: "DELETE", pathParams: { id } }),
onSuccess: inv,
});
}