resource-visual-editors/ 5 文件 (ConfigMap/Secret/Service/Ingress/PvcEditor): - 保存按钮文案 (保存配置/密钥/Service/Ingress/PVC) - IngressEditor: (可选)/手工输入/帮助文本/常见示例/手动输入/继续手动输入 - PvcEditor: 访问模式描述 (单节点读写/多节点只读/多节点读写) pod-logs-viewer/ 3 文件: - ControlBar: 导出/刷新/实时/停止按钮 - LogContentArea: 加载中… 占位 - LogsHeader: 日志标题模板 PodLogsViewer: 6 处错误消息 + DialogTitle/Description PodMetricsPage: subtitle 模板字面量 + 所有命名空间 PodExecTerminal: 连接错误消息 17 处 toast description 模板字面量中文→i18n.t() 插值: - DeploymentEditAdvanced/Containers/Storage: 更新已提交 - DeploymentView: 回滚/伸缩 - DeploymentDetail: 伸缩/镜像更新 - DeploymentEditService: 资源删除 - ResourceVisualEditor/NamespaceResourcePage: 资源更新/YAML下载 - ClusterOverviewPage: 集群未找到 - DeploymentEditContainers: 容器名称/镜像为空 5 处 confirm description 中文→i18n.t() 插值: - DeploymentView/EditService/Detail/WorkloadViewPage/MiddlewareMonitor/SiteMonitor/HealthProbeSection 5 处 subtitle 模板字面量中文→i18n.t(): - PodMetricsPage/CapacityPage/EventsPage/KubernetesNodes/ClusterOverviewPage 新增 k8s.rve 14键 + k8s.toast 18键 + k8s.podMetrics 4键 + k8s.podLogs 13键 = 49 翻译键 zh/en 7481→7524 完全对齐 tsc -b=0, npm build✓ (17.76s), ESLint errors=0/warnings=43/any=0, go build=0, 17/17 测试通过
115 lines
5.4 KiB
TypeScript
115 lines
5.4 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
// SecretEditor — Secret 键值对表格可视化编辑器 (Base64 编码/解码, 从 ResourceVisualEditor.tsx 拆分)
|
|
import { useState } from 'react';
|
|
import { Plus, Trash2, Loader2, Save } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import i18n from '@/i18n';
|
|
|
|
export interface SecretEditorProps {
|
|
manifest: any;
|
|
onSave: (m: any) => void;
|
|
saving: boolean;
|
|
isCreate?: boolean;
|
|
}
|
|
|
|
export function SecretEditor({ manifest, onSave, saving, isCreate }: SecretEditorProps) {
|
|
const rawData = manifest?.data || {};
|
|
const [resName, setResName] = useState(manifest?.metadata?.name || '');
|
|
const [expandAll, setExpandAll] = useState(false);
|
|
// Decode base64 values for editing
|
|
const [entries, setEntries] = useState<{ key: string; value: string; encoded: boolean }[]>(
|
|
Object.entries(rawData).map(([key, value]) => {
|
|
const v = String(value);
|
|
try {
|
|
// Try to decode — if it's valid base64
|
|
const decoded = atob(v);
|
|
return { key, value: decoded, encoded: true };
|
|
} catch {
|
|
return { key, value: v, encoded: false };
|
|
}
|
|
})
|
|
);
|
|
|
|
const update = (i: number, field: 'key' | 'value', val: string) => {
|
|
const next = [...entries];
|
|
next[i] = { ...next[i], [field]: val };
|
|
setEntries(next);
|
|
};
|
|
|
|
const addRow = () => setEntries([...entries, { key: '', value: '', encoded: true }]);
|
|
const removeRow = (i: number) => setEntries(entries.filter((_, idx) => idx !== i));
|
|
|
|
const handleSave = () => {
|
|
const newData: Record<string, string> = {};
|
|
entries.forEach(e => {
|
|
if (e.key.trim()) {
|
|
// Re-encode if originally base64
|
|
newData[e.key.trim()] = e.encoded ? btoa(e.value) : e.value;
|
|
}
|
|
});
|
|
onSave({ ...manifest, metadata: { ...manifest.metadata, name: resName.trim() }, data: newData });
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full">
|
|
<div className="shrink-0 grid grid-cols-2 gap-4">
|
|
<div>
|
|
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('kubernetes.secret.secretName')} </Label>
|
|
<Input value={resName} onChange={(e) => setResName(e.target.value)} readOnly={!isCreate} className="mt-1 font-mono text-xs bg-[var(--dg-bg-tertiary)]" />
|
|
</div>
|
|
<div>
|
|
<Label className="text-[var(--dg-text-secondary)] text-xs"> {i18n.t('system.credentialType')} </Label>
|
|
<Input value={manifest?.type || 'Opaque'} readOnly className="mt-1 font-mono text-xs bg-[var(--dg-bg-tertiary)]" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 min-h-0 flex flex-col mt-3">
|
|
<div className="flex items-center justify-between mb-2 shrink-0">
|
|
<Label className="text-[var(--dg-text-secondary)] text-sm"> {i18n.t('common.loading')} </Label>
|
|
<div className="flex gap-1">
|
|
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={() => setExpandAll(!expandAll)}>
|
|
{expandAll ? i18n.t('k8s.configMap.collapseAll') : i18n.t('system.expandAll')}
|
|
</Button>
|
|
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={addRow}><Plus className="h-3 w-3 mr-1" /> {i18n.t('monitor.dashboard.custom.addBtn')} </Button>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 min-h-0 overflow-y-auto pr-1 space-y-2 border border-[var(--dg-border)] rounded-lg bg-[var(--dg-bg-secondary)] p-2">
|
|
{entries.map((entry, i) => (
|
|
<div key={i} className="flex items-start gap-2 p-2 rounded-lg border border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]">
|
|
<Input
|
|
value={entry.key}
|
|
onChange={e => update(i, 'key', e.target.value)}
|
|
placeholder={i18n.t('k8s.rve.keyPlaceholder')}
|
|
className="flex-1 font-mono text-xs h-8 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)]"
|
|
/>
|
|
<Textarea
|
|
value={entry.value}
|
|
onChange={e => update(i, 'value', e.target.value)}
|
|
placeholder={i18n.t('k8s.rve.keyPlaceholder')}
|
|
rows={expandAll || entry.value.length > 300 ? 10 : (entry.value.length > 100 ? 6 : 3)}
|
|
className="flex-[2] font-mono text-xs min-h-[70px] max-h-[420px] resize-y bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] leading-relaxed"
|
|
/>
|
|
<Button variant="ghost" size="icon" className="h-8 w-8 dg-text-red hover:dg-text-red shrink-0" onClick={() => removeRow(i)}>
|
|
<Trash2 className="h-3.5 w-3.5" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
{entries.length === 0 && (
|
|
<div className="text-center py-8 text-[var(--dg-text-muted)] text-xs"> {i18n.t('common.name')} </div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="shrink-0 flex justify-end gap-2 pt-3 mt-2 border-t border-[var(--dg-border)]">
|
|
<Button size="sm" className="bg-blue-600 hover:bg-blue-700" onClick={handleSave} disabled={saving}>
|
|
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin mr-1" /> : <Save className="h-3.5 w-3.5 mr-1" />}
|
|
{i18n.t('k8s.rve.saveSecret')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|