feat(usage): enrich audit metadata and detail views

This commit is contained in:
elky
2026-07-17 19:20:16 +08:00
parent 0be380243b
commit 664c063a06
79 changed files with 6968 additions and 1294 deletions
@@ -0,0 +1,69 @@
import { describe, expect, it } from 'vitest'
import {
buildApiKeyRedactionFeatureSettingsPatch,
resolveApiKeyRedactionFormState,
} from '../apiKeyFeatureSettings'
const redaction = {
enabled: true,
inject_model_instruction: false,
}
describe('managed API key feature setting inheritance', () => {
it('uses the target user value for an inherited key form', () => {
expect(resolveApiKeyRedactionFormState(null, {
chat_pii_redaction: redaction,
})).toEqual({
mode: 'inherit',
...redaction,
})
})
it('omits feature_settings when a created key keeps inheritance', () => {
expect(buildApiKeyRedactionFeatureSettingsPatch({
isEditing: false,
currentFeatureSettings: undefined,
mode: 'inherit',
value: redaction,
})).toEqual({})
})
it('writes an override only when custom mode is selected', () => {
expect(buildApiKeyRedactionFeatureSettingsPatch({
isEditing: false,
currentFeatureSettings: undefined,
mode: 'custom',
value: redaction,
})).toEqual({
feature_settings: {
chat_pii_redaction: redaction,
},
})
})
it('does not create an override when only another field of an inherited key changes', () => {
expect(buildApiKeyRedactionFeatureSettingsPatch({
isEditing: true,
currentFeatureSettings: null,
mode: 'inherit',
value: redaction,
})).toEqual({})
})
it('removes only the existing redaction override when inheritance is restored', () => {
expect(buildApiKeyRedactionFeatureSettingsPatch({
isEditing: true,
currentFeatureSettings: {
chat_pii_redaction: { enabled: false },
notification_push_service: { enabled: true },
},
mode: 'inherit',
value: redaction,
})).toEqual({
feature_settings: {
notification_push_service: { enabled: true },
},
})
})
})
@@ -0,0 +1,64 @@
import type { FeatureSettings } from '@/api/users'
import {
hasChatPiiRedactionFeatureSettings,
mergeChatPiiRedactionFeatureSettings,
readChatPiiRedactionFeatureSettings,
removeChatPiiRedactionFeatureSettings,
type ChatPiiRedactionFeatureSettings,
} from '@/utils/featureSettings'
export type ApiKeyRedactionMode = 'inherit' | 'custom'
export interface ApiKeyRedactionFormState extends ChatPiiRedactionFeatureSettings {
mode: ApiKeyRedactionMode
}
export function resolveApiKeyRedactionFormState(
apiKeyFeatureSettings: FeatureSettings | null | undefined,
inheritedUserFeatureSettings: FeatureSettings | null | undefined,
): ApiKeyRedactionFormState {
const hasCustomRedaction = hasChatPiiRedactionFeatureSettings(apiKeyFeatureSettings)
const value = readChatPiiRedactionFeatureSettings(
hasCustomRedaction ? apiKeyFeatureSettings : inheritedUserFeatureSettings,
)
return {
mode: hasCustomRedaction ? 'custom' : 'inherit',
...value,
}
}
/**
* Builds only the feature-settings portion of an API-key mutation.
*
* An omitted field preserves inheritance. `null` (or an object with the
* redaction key removed) is emitted only when an existing custom override is
* explicitly switched back to inheritance.
*/
export function buildApiKeyRedactionFeatureSettingsPatch(options: {
isEditing: boolean
currentFeatureSettings: FeatureSettings | null | undefined
mode: ApiKeyRedactionMode
value: ChatPiiRedactionFeatureSettings
}): { feature_settings?: FeatureSettings | null } {
if (options.mode === 'custom') {
return {
feature_settings: mergeChatPiiRedactionFeatureSettings(
options.isEditing ? options.currentFeatureSettings : null,
options.value,
),
}
}
if (
options.isEditing
&& hasChatPiiRedactionFeatureSettings(options.currentFeatureSettings)
) {
return {
feature_settings: removeChatPiiRedactionFeatureSettings(
options.currentFeatureSettings,
),
}
}
return {}
}
@@ -104,15 +104,47 @@
<div class="space-y-3 rounded-lg border border-border bg-muted/30 p-3">
<div class="flex items-center justify-between gap-3">
<div>
<Label class="text-sm font-medium">
{{ legacyT('敏感信息保护') }}
</Label>
<p class="mt-1 text-xs text-muted-foreground">
{{ legacyT(form.chat_pii_redaction_mode === 'inherit' ? '跟随目标用户设置' : '仅覆盖此 API Key') }}
</p>
</div>
<div class="flex items-center gap-2">
<Button
size="sm"
:variant="form.chat_pii_redaction_mode === 'inherit' ? 'default' : 'outline'"
@click="updateField('chat_pii_redaction_mode', 'inherit')"
>
{{ legacyT('跟随用户') }}
</Button>
<Button
size="sm"
:variant="form.chat_pii_redaction_mode === 'custom' ? 'default' : 'outline'"
@click="updateField('chat_pii_redaction_mode', 'custom')"
>
{{ legacyT('单独配置') }}
</Button>
</div>
</div>
<div
v-if="form.chat_pii_redaction_mode === 'custom'"
class="flex items-center justify-between gap-3 border-t border-border/50 pt-3"
>
<Label class="text-sm font-medium">
{{ legacyT('敏感信息保护') }}
{{ legacyT('启用保护') }}
</Label>
<Switch
:model-value="form.chat_pii_redaction_enabled"
@update:model-value="updateField('chat_pii_redaction_enabled', $event)"
/>
</div>
<div class="flex items-center justify-between gap-3">
<div
v-if="form.chat_pii_redaction_mode === 'custom' && form.chat_pii_redaction_enabled"
class="flex items-center justify-between gap-3 border-t border-border/50 pt-3"
>
<Label class="text-sm font-medium">
{{ legacyT('占位符说明') }}
</Label>
@@ -156,6 +188,7 @@ export interface UserApiKeyFormState {
rate_limit?: number
concurrent_limit?: number
ip_rules_text: string
chat_pii_redaction_mode: 'inherit' | 'custom'
chat_pii_redaction_enabled: boolean
chat_pii_redaction_placeholder_notice: boolean
}