feat(frontend): update config UI for DB-first config architecture

- ConfigSource type: 'default' | 'db' (removed 'env')
- Badge: 'db' shows '已配置', 'default' shows '默认值'
- Removed readonly field lock icon and env-var-only warning message
- Updated 'override' → 'db' references in ConfigGroupCard and ConfigManager
- Removed readonly/readonlyWarning from ConfigFieldDto interface
This commit is contained in:
jeffusion
2026-03-05 11:35:27 +08:00
committed by 路遥知码力
parent 4c32a460d3
commit 9c9ef05d13
4 changed files with 11 additions and 37 deletions

View File

@@ -6,7 +6,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Textarea } from '@/components/ui/textarea';
import { Badge } from '@/components/ui/badge';
import { Label } from '@/components/ui/label';
import { Lock } from 'lucide-react';
interface ConfigFieldInputProps {
field: ConfigFieldDto;
@@ -15,18 +14,15 @@ interface ConfigFieldInputProps {
}
export function ConfigFieldInput({ field, value, onChange }: ConfigFieldInputProps) {
const isReadonly = !!field.readonly;
const renderInput = () => {
const baseInputClasses = "bg-zinc-900/50 border-white/10 focus-visible:ring-primary focus-visible:border-primary transition-all duration-200" + (isReadonly ? " opacity-50 cursor-not-allowed" : "");
const baseInputClasses = "bg-zinc-900/50 border-white/10 focus-visible:ring-primary focus-visible:border-primary transition-all duration-200";
switch (field.type) {
case 'boolean':
return (
<Switch
checked={Boolean(value)}
onCheckedChange={onChange}
disabled={isReadonly}
className={`data-[state=checked]:bg-primary ${isReadonly ? 'opacity-50 cursor-not-allowed' : ''}`}
className="data-[state=checked]:bg-primary"
/>
);
case 'enum':
@@ -34,7 +30,6 @@ export function ConfigFieldInput({ field, value, onChange }: ConfigFieldInputPro
<Select
value={value !== undefined && value !== null ? String(value) : ''}
onValueChange={onChange}
disabled={isReadonly}
>
<SelectTrigger className={`w-full ${baseInputClasses}`}>
<SelectValue placeholder="请选择..." />
@@ -55,7 +50,6 @@ export function ConfigFieldInput({ field, value, onChange }: ConfigFieldInputPro
onChange={(e) => onChange(e.target.value)}
placeholder={field.sensitive && field.hasValue ? '••••••••' : ''}
className={`min-h-[100px] ${baseInputClasses}`}
disabled={isReadonly}
/>
);
case 'number':
@@ -67,7 +61,6 @@ export function ConfigFieldInput({ field, value, onChange }: ConfigFieldInputPro
placeholder={field.sensitive && field.hasValue ? '••••••••' : ''}
min={field.min}
max={field.max}
disabled={isReadonly}
className={baseInputClasses}
/>
);
@@ -80,7 +73,6 @@ export function ConfigFieldInput({ field, value, onChange }: ConfigFieldInputPro
value={value !== undefined && value !== null ? String(value) : ''}
onChange={(e) => onChange(e.target.value)}
placeholder={field.sensitive && field.hasValue ? '••••••••' : ''}
disabled={isReadonly}
className={baseInputClasses}
/>
);
@@ -89,10 +81,8 @@ export function ConfigFieldInput({ field, value, onChange }: ConfigFieldInputPro
const getSourceBadge = () => {
switch (field.source) {
case 'override':
return <Badge className="ml-2 bg-primary/20 text-primary border-primary/30 tech-glow hover:bg-primary/30 transition-colors"></Badge>;
case 'env':
return <Badge variant="secondary" className="ml-2 bg-amber-500/20 text-amber-500 border-amber-500/30 hover:bg-amber-500/30"></Badge>;
case 'db':
return <Badge className="ml-2 bg-primary/20 text-primary border-primary/30 tech-glow hover:bg-primary/30 transition-colors"></Badge>;
case 'default':
default:
return <Badge variant="outline" className="ml-2 border-zinc-600 text-zinc-400"></Badge>;
@@ -112,27 +102,13 @@ export function ConfigFieldInput({ field, value, onChange }: ConfigFieldInputPro
</div>
<div className="pt-1">
<span className="font-mono text-xs px-2 py-0.5 rounded-md bg-primary/10 text-primary border border-primary/20 inline-flex items-center">
$ {field.envKey}
{field.envKey}
</span>
</div>
</div>
<div className="flex-1 w-full max-w-xl flex flex-col gap-2">
{renderInput()}
{isReadonly && (
<div className="text-xs text-zinc-500 flex items-center gap-1.5 pt-1">
<Lock className="w-3.5 h-3.5" />
</div>
)}
{!isReadonly && field.readonlyWarning && (
<div className="text-xs text-amber-500 flex items-center gap-1.5 pt-1">
<span className="w-1.5 h-1.5 rounded-full bg-amber-500 animate-pulse" />
{field.readonlyWarning}
</div>
)}
</div>
</div>
</div>

View File

@@ -33,12 +33,12 @@ export function ConfigGroupCard({
onReset,
isResetting,
}: ConfigGroupCardProps) {
const hasOverride = group.fields.some((f) => f.source === 'override');
const hasOverride = group.fields.some((f) => f.source === 'db');
const handleReset = () => {
// Only reset fields that actually have overrides
// Only reset fields that have been stored in DB
const keysToReset = group.fields
.filter((f) => f.source === 'override')
.filter((f) => f.source === 'db')
.map((f) => f.envKey);
if (keysToReset.length > 0) {

View File

@@ -97,7 +97,7 @@ export function ConfigManager() {
if (!data) return;
const allOverrideKeys = data.groups
.flatMap((g) => g.fields)
.filter((f) => f.source === 'override')
.filter((f) => f.source === 'db')
.map((f) => f.envKey);
if (allOverrideKeys.length === 0) return;
if (confirm('确定要重置所有配置到默认值吗?这将立即生效。')) {
@@ -106,7 +106,7 @@ export function ConfigManager() {
};
const hasOverrides = data?.groups.some((g) =>
g.fields.some((f) => f.source === 'override')
g.fields.some((f) => f.source === 'db')
) ?? false;
if (isLoading) {

View File

@@ -1,6 +1,6 @@
import api from '@/lib/api';
export type ConfigSource = 'default' | 'env' | 'override';
export type ConfigSource = 'default' | 'db';
export type ConfigFieldType = 'string' | 'number' | 'boolean' | 'url' | 'text' | 'enum';
export interface ConfigFieldDto {
@@ -9,8 +9,6 @@ export interface ConfigFieldDto {
description: string;
type: ConfigFieldType;
sensitive: boolean;
readonly?: boolean;
readonlyWarning?: string;
enumValues?: string[];
min?: number;
max?: number;