全量 JSX children + toast/confirm + sr-only 描述硬编码中文清零: - Kubernetes模块: UpdateStrategyCard maxSurge/maxUnavailable说明 + IngressCreateForm host域名/Service端口 + ServiceCreateForm 端口配置/selector + DeploymentEditService 无selector/个关联/暂无Service/Ingress + ClusterOverviewPage 核 + KubernetesClusterDialog 已配置 + KubectlTerminal/ResourceVisualEditor/CreateHPADialog/DeploymentEditContainers/Storage + PodInstancesTable/UpdateStrategySelectorCards - AI模块: PredictiveCapacityTab AI深度解读/紧急/警告/7天预测/预计N天 + PredictiveCostTab AI深度解读 + event-handler.ts 高危操作确认toast - Deploy模块: DeployCredentials 启用/禁用 + DeployRBAC 删除角色confirm + WorkspaceTermDialog + CredFormDialog Windows用户 - Monitor模块: AlertRulesTab 删除confirm/分钟 + PrometheusDatasourceManager/LogDatasourcePage 删除confirm + QueryHistoryPanel 条 - System模块: ServerFileBrowser/TerminalDialog/RdpAuditViewer sr-only + SystemManager/CredentialManager/TerminalSessionsTab 删除confirm + SecuritySettingsTab 30分钟/2小时 - Task模块: CronJobViewDialog 次 新增 61 翻译键 (k8s.editAdvanced 6 + k8s.editService 11 + k8s.cluster 2 + k8s.misc 9 + aiops.capacity 9 + aiops.cost 1 + aiops.cli 2 + deploy 4 + monitor 5 + system 10 + task 1 + common 2) tsc -b=0, npm build✓ (17.71s), ESLint=0/43/any=0, go build=0, 17/17 测试通过
310 lines
11 KiB
TypeScript
310 lines
11 KiB
TypeScript
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Switch } from "@/components/ui/switch";
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "@/components/ui/select";
|
||
import { Lock, Key, Loader2 } from "lucide-react";
|
||
import { useTranslation } from "react-i18next";
|
||
import { useUserStore } from "@/store";
|
||
import type { SystemSettingsTabProps } from "../SystemSettingsTab";
|
||
|
||
// 安全设置 Tab:安全策略 + 会话设置(水印/录制/剪贴板/上传/下载/命令策略/审批/超时/SSO) // Logan.Li
|
||
type SecuritySettingsTabProps = Pick<
|
||
SystemSettingsTabProps,
|
||
| "sessionWatermarkEnabled"
|
||
| "setSessionWatermarkEnabled"
|
||
| "sessionRecordEnabled"
|
||
| "setSessionRecordEnabled"
|
||
| "clipboardEnabled"
|
||
| "setClipboardEnabled"
|
||
| "fileUploadEnabled"
|
||
| "setFileUploadEnabled"
|
||
| "fileDownloadEnabled"
|
||
| "setFileDownloadEnabled"
|
||
| "commandPolicyEnabled"
|
||
| "setCommandPolicyEnabled"
|
||
| "approvalRequired"
|
||
| "setApprovalRequired"
|
||
| "auditLoading"
|
||
| "auditApplying"
|
||
| "handleAuditApply"
|
||
>;
|
||
|
||
export function SecuritySettingsTab(props: SecuritySettingsTabProps) {
|
||
const { t } = useTranslation();
|
||
const currentUsername = useUserStore(
|
||
(state) => state.user?.username || "admin",
|
||
);
|
||
const {
|
||
sessionWatermarkEnabled,
|
||
setSessionWatermarkEnabled,
|
||
sessionRecordEnabled,
|
||
setSessionRecordEnabled,
|
||
clipboardEnabled,
|
||
setClipboardEnabled,
|
||
fileUploadEnabled,
|
||
setFileUploadEnabled,
|
||
fileDownloadEnabled,
|
||
setFileDownloadEnabled,
|
||
commandPolicyEnabled,
|
||
setCommandPolicyEnabled,
|
||
approvalRequired,
|
||
setApprovalRequired,
|
||
auditLoading,
|
||
auditApplying,
|
||
handleAuditApply,
|
||
} = props;
|
||
|
||
return (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
|
||
<Card className="dg-card">
|
||
<CardHeader>
|
||
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||
<Lock className="h-5 w-5 dg-text-blue" />
|
||
{t("system.security")}
|
||
</CardTitle>
|
||
<CardDescription className="dg-text-muted">
|
||
{t("system.securityDesc")}
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.forcePasswordComplexity")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.forcePasswordComplexityDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch disabled className="data-[state=checked]:bg-blue-600" />
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.loginLockout")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.loginLockoutDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
disabled
|
||
defaultChecked
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.twoFactor")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.twoFactorDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch disabled className="data-[state=checked]:bg-blue-600" />
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="dg-card">
|
||
<CardHeader>
|
||
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||
<Key className="h-5 w-5 dg-text-yellow" />
|
||
{t("system.sessionSettings")}
|
||
</CardTitle>
|
||
<CardDescription className="dg-text-muted">
|
||
{t("system.sessionSettingsDesc")}
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div className="min-w-0">
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.sessionWatermark")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.sessionWatermarkDesc", {
|
||
user: currentUsername,
|
||
})}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={sessionWatermarkEnabled}
|
||
onCheckedChange={setSessionWatermarkEnabled}
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
<div className="rounded border border-[var(--dg-border)] bg-[#101418] px-3 py-4 overflow-hidden">
|
||
<div className="relative h-24 rounded bg-black/70">
|
||
<div
|
||
className="absolute inset-0 opacity-80"
|
||
style={{
|
||
backgroundImage: `repeating-linear-gradient(-24deg, transparent 0 42px, rgba(255,255,255,0.12) 43px 44px, transparent 45px 120px)`,
|
||
}}
|
||
/>
|
||
<div className="absolute inset-0 grid grid-cols-2 place-items-center -rotate-[18deg] text-lg font-semibold tracking-normal text-white/30">
|
||
<span>{currentUsername}</span>
|
||
<span>{currentUsername}</span>
|
||
<span>{currentUsername}</span>
|
||
<span>{currentUsername}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-1 gap-3">
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.sessionRecordEnabled")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.sessionRecordEnabledDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={sessionRecordEnabled}
|
||
onCheckedChange={setSessionRecordEnabled}
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.clipboardEnabled")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.clipboardEnabledDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={clipboardEnabled}
|
||
onCheckedChange={setClipboardEnabled}
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.fileUploadEnabled")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.fileUploadEnabledDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={fileUploadEnabled}
|
||
onCheckedChange={setFileUploadEnabled}
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.fileDownloadEnabled")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.fileDownloadEnabledDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={fileDownloadEnabled}
|
||
onCheckedChange={setFileDownloadEnabled}
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.commandPolicyEnabled")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.commandPolicyEnabledDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={commandPolicyEnabled}
|
||
onCheckedChange={setCommandPolicyEnabled}
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.approvalRequired")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.approvalRequiredDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={approvalRequired}
|
||
onCheckedChange={setApprovalRequired}
|
||
className="data-[state=checked]:bg-blue-600"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.sessionTimeoutTime")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.sessionTimeoutDesc")}
|
||
</p>
|
||
</div>
|
||
<Select defaultValue="30" disabled>
|
||
<SelectTrigger className="w-32 h-8 dg-input">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent className="dg-input">
|
||
<SelectItem value="15">
|
||
{t("monitor.traceMgr.min15")}
|
||
</SelectItem>
|
||
<SelectItem value="30">{t("common.minutes30")}</SelectItem>
|
||
<SelectItem value="60">
|
||
{t("monitor.traceMgr.hour1")}
|
||
</SelectItem>
|
||
<SelectItem value="120">{t("common.hours2")}</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<p className="text-sm font-medium text-[var(--dg-text-primary)]">
|
||
{t("system.sso")}
|
||
</p>
|
||
<p className="text-xs dg-text-muted">
|
||
{t("system.singleSignOnDesc")}
|
||
</p>
|
||
</div>
|
||
<Switch disabled className="data-[state=checked]:bg-blue-600" />
|
||
</div>
|
||
<div className="flex justify-end pt-1">
|
||
<Button
|
||
size="sm"
|
||
className="dg-btn-primary"
|
||
onClick={handleAuditApply}
|
||
disabled={auditLoading || auditApplying}
|
||
>
|
||
{auditApplying ? (
|
||
<>
|
||
<Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />
|
||
{t("system.applyingDot")}
|
||
</>
|
||
) : (
|
||
t("common.apply")
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|