feat(secrets): scoped shared secrets rule-management UI (Phase 2)
Completes scoped shared secrets end-to-end: /shared-secrets list/new/edit routes (mirroring metric-alert-rules) with an env-key name, a WRITE-ONLY value (password input; never pre-filled — the API returns only has_value; omitted on PATCH to keep the stored secret, provided to rotate; cleared after save), an encrypted toggle (flipping it requires re-entering the value, matching the server's 400 guard), a global|app scope with an App-grouping picker (listApps), description, and enabled. 409 conflicts surface a friendly message. New "System" nav entry (IconKey) + api.ts client + full sharedsecrets.* i18n (en/ru parity). Reviewed: typescript APPROVE (0 CRITICAL/HIGH).
This commit is contained in:
@@ -1428,3 +1428,54 @@ export function deleteMetricAlertRule(id: number): Promise<void> {
|
||||
return del<void>(`/api/metric-alert-rules/${id}`);
|
||||
}
|
||||
|
||||
// ── Shared secrets ──────────────────────────────────────────────────
|
||||
// A shared secret is a named env KEY whose value the reconciler injects
|
||||
// into matching workloads. Scope model: scope="global" applies to every
|
||||
// app; scope="app" + app_id narrows it to one app grouping.
|
||||
//
|
||||
// The value is WRITE-ONLY: the API never returns it, only a `has_value`
|
||||
// presence flag. On update, omit `value` to keep the stored secret;
|
||||
// provide it to set/rotate. Flipping `encrypted` requires resubmitting
|
||||
// `value` (the server 400s otherwise). IDs are uuid strings.
|
||||
|
||||
export interface SharedSecret {
|
||||
id: string;
|
||||
name: string; // the env KEY
|
||||
has_value: boolean; // true if a value is stored (value itself is write-only, never returned)
|
||||
encrypted: boolean;
|
||||
scope: 'global' | 'app';
|
||||
app_id: string; // set when scope === 'app'
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
export interface SharedSecretInput {
|
||||
name?: string;
|
||||
value?: string; // omit to keep the existing value on update; provide to set/rotate
|
||||
encrypted?: boolean;
|
||||
scope?: 'global' | 'app';
|
||||
app_id?: string;
|
||||
description?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
export function listSharedSecrets(opts?: {
|
||||
appID?: string;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<SharedSecret[]> {
|
||||
const params = opts?.appID ? `?app_id=${encodeURIComponent(opts.appID)}` : '';
|
||||
return get<SharedSecret[]>(`/api/shared-secrets${params}`, opts?.signal);
|
||||
}
|
||||
export function getSharedSecret(id: string, signal?: AbortSignal): Promise<SharedSecret> {
|
||||
return get<SharedSecret>(`/api/shared-secrets/${id}`, signal);
|
||||
}
|
||||
export function createSharedSecret(data: SharedSecretInput): Promise<SharedSecret> {
|
||||
return post<SharedSecret>('/api/shared-secrets', data);
|
||||
}
|
||||
export function updateSharedSecret(id: string, data: SharedSecretInput): Promise<SharedSecret> {
|
||||
return patch<SharedSecret>(`/api/shared-secrets/${id}`, data);
|
||||
}
|
||||
export function deleteSharedSecret(id: string): Promise<void> {
|
||||
return del<void>(`/api/shared-secrets/${id}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user