refactor(quality): Q8 前端巨型组件拆分 — SystemSettingsTab 754L→173L (-77%), 提取通用/安全/LDAP 3 个独立子 Tab 组件
- GeneralSettingsTab.tsx (154L): 通用设置 Tab(审计策略 + 系统信息) - SecuritySettingsTab.tsx (309L): 安全设置 Tab(安全策略 + 会话设置 9 项开关) - LdapSettingsTab.tsx (243L): LDAP 设置 Tab(配置表单 + 管理区转发) - 主文件仅保留 Props 接口 + Tab 路由 + 分发逻辑(173L) - 每个子 Tab 用 Pick<SystemSettingsTabProps,...> 精确声明 props 子集 - tsc -b=0, npm build✓(17.71s), ESLint errors=0/warnings=59(无新增) - 17/17 集成回归测试通过, Docker 部署 healthy
This commit is contained in:
1 parent
2cc5ad719b
commit
8a4dfb0db6
7 files changed
+797
-589
No files matched your search
@@ -129,7 +129,7 @@
|
|||||||
| ~~`api.ts` 1324 行/352 方法~~ → 已拆分为 12 个域文件 (api/kubernetes.ts, deploy.ts, monitor.ts 等) | ✅ 已解决 | 前端 | ~~P0~~ done |
|
| ~~`api.ts` 1324 行/352 方法~~ → 已拆分为 12 个域文件 (api/kubernetes.ts, deploy.ts, monitor.ts 等) | ✅ 已解决 | 前端 | ~~P0~~ done |
|
||||||
| 前端 **1026 处** `any` 类型 | 类型安全缺失,重构风险高 | 前端全局 | ~~P0~~ **done** (any 1076→40, unused 25→6, console 93→1, tsc --noEmit=0) |
|
| 前端 **1026 处** `any` 类型 | 类型安全缺失,重构风险高 | 前端全局 | ~~P0~~ **done** (any 1076→40, unused 25→6, console 93→1, tsc --noEmit=0) |
|
||||||
| ~~K8s 双路由体系(集群域+Kuboard 域)~~ → code 路由统一为主,ID 路由标记 deprecated 向后兼容 | ✅ 已解决 | 后端 | ~~P1~~ done |
|
| ~~K8s 双路由体系(集群域+Kuboard 域)~~ → code 路由统一为主,ID 路由标记 deprecated 向后兼容 | ✅ 已解决 | 后端 | ~~P1~~ done |
|
||||||
| 25 个 ≥500 行巨型组件 | 拆分困难 | 前端 | P1 → **Q6+Q7 done: 最大组件 ResourceVisualEditor 882L→282L (-68%) + AgentCLI 814L→521L (-36%),累计提取 11 个独立组件文件** |
|
| 25 个 ≥500 行巨型组件 | 拆分困难 | 前端 | P1 → **Q6+Q7+Q8 done: 最大组件 ResourceVisualEditor 882L→282L (-68%) + AgentCLI 814L→521L (-36%) + SystemSettingsTab 754L→173L (-77%),累计提取 14 个独立组件文件** |
|
||||||
|
|
||||||
### 3.2 🟡 中优先级
|
### 3.2 🟡 中优先级
|
||||||
|
|
||||||
|
|||||||
+32
@@ -479,6 +479,7 @@ services/api.ts (1324L)
|
|||||||
| U14 | ESLint 冗余 disable 指令清理(unused eslint-disable 6→0) | P3 | **done** |
|
| U14 | ESLint 冗余 disable 指令清理(unused eslint-disable 6→0) | P3 | **done** |
|
||||||
| Q6 | 前端巨型组件拆分(ResourceVisualEditor 882L→282L,提取 5 个独立编辑器) | P0 | **done** |
|
| Q6 | 前端巨型组件拆分(ResourceVisualEditor 882L→282L,提取 5 个独立编辑器) | P0 | **done** |
|
||||||
| Q7 | 前端巨型组件拆分(AgentCLI 814L→521L,提取工具栏+常量+空状态+滚动按钮) | P0 | **done** |
|
| Q7 | 前端巨型组件拆分(AgentCLI 814L→521L,提取工具栏+常量+空状态+滚动按钮) | P0 | **done** |
|
||||||
|
| Q8 | 前端巨型组件拆分(SystemSettingsTab 754L→173L,提取通用/安全/LDAP 3 个子 Tab) | P0 | **done** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -677,4 +678,35 @@ pending → in_progress → done / blocked
|
|||||||
- [x] ESLint warnings 保持 **59**(无新增)
|
- [x] ESLint warnings 保持 **59**(无新增)
|
||||||
- [x] `go build` 零错误
|
- [x] `go build` 零错误
|
||||||
- [x] 17/17 集成回归测试通过(零回归)
|
- [x] 17/17 集成回归测试通过(零回归)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Q8: 前端巨型组件拆分 — SystemSettingsTab [done]
|
||||||
|
|
||||||
|
**问题**: `SystemSettingsTab.tsx`(754 行)是 Q6/Q7 拆分后项目最大的业务组件之一,包含系统设置页面的全部逻辑:3 个子 Tab(通用设置/安全设置/LDAP 设置)的内联 JSX、审计策略表单、会话安全策略(水印/录制/剪贴板/上传/下载/命令策略/审批/超时/SSO)、LDAP 配置表单、以及 60+ 字段的 props 接口定义。单文件过大导致维护困难、代码导航低效。
|
||||||
|
|
||||||
|
**已完成 (2026-07-07)**:
|
||||||
|
|
||||||
|
将 3 个子 Tab 的 JSX 内容提取为 `system-settings/` 子目录下的独立文件:
|
||||||
|
|
||||||
|
| 新文件 | 行数 | 职责 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `GeneralSettingsTab.tsx` | 154 | 通用设置 Tab(审计策略卡片 + 系统信息卡片) |
|
||||||
|
| `SecuritySettingsTab.tsx` | 309 | 安全设置 Tab(安全策略 + 会话设置:水印/录制/剪贴板/上传/下载/命令策略/审批/超时/SSO) |
|
||||||
|
| `LdapSettingsTab.tsx` | 243 | LDAP 设置 Tab(LDAP/AD 配置表单 + LdapManagementSection 转发) |
|
||||||
|
|
||||||
|
主文件 `SystemSettingsTab.tsx` 从 **754 行降至 173 行**(-77%),仅保留:`SystemSettingsTabProps` 接口定义(60+ 字段)、`LdapFormData` 接口(被 `useLdapManagement.ts` 引用)、Tab 路由逻辑(URL searchParams 同步)、子 Tab 分发(`{subTab === "general" && <GeneralSettingsTab {...props} />}`)。
|
||||||
|
|
||||||
|
**提取原则**: 每个子 Tab 是自包含的函数组件,通过 `Pick<SystemSettingsTabProps, ...>` 精确声明所需的 props 子集(GeneralSettingsTab 取 9 个审计字段、SecuritySettingsTab 取 17 个安全/会话字段、LdapSettingsTab 取全部 LDAP + 管理透传字段)。主文件通过 `{...props}` 展开传递,零逻辑变更。
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- [x] SystemSettingsTab.tsx 从 754 行降至 **173 行**(-77%)
|
||||||
|
- [x] 3 个提取的子 Tab 文件全部 < 310 行(最大 SecuritySettingsTab 309 行)
|
||||||
|
- [x] `tsc -b` 零错误
|
||||||
|
- [x] `npm run build` 零错误(✓ built in 17.71s)
|
||||||
|
- [x] ESLint errors 保持 **0**(无新增)
|
||||||
|
- [x] ESLint warnings 保持 **59**(无新增)
|
||||||
|
- [x] `go build` 零错误
|
||||||
|
- [x] 17/17 集成回归测试通过(零回归)
|
||||||
- [x] Docker 部署 healthy
|
- [x] Docker 部署 healthy
|
||||||
|
- [x] 系统设置页 3 个 Tab 功能不变(通用/安全/LDAP 全部可切换)
|
||||||
+51
@@ -1151,3 +1151,54 @@ python3 /srv/aiops/tests/integration_test.py --host 127.0.0.1 --port 8080 | tail
|
|||||||
- [x] U6: `npm run build` 零错误(✓ built in 17.50s)
|
- [x] U6: `npm run build` 零错误(✓ built in 17.50s)
|
||||||
- [x] U7: ESLint errors=0(无新增)
|
- [x] U7: ESLint errors=0(无新增)
|
||||||
- [x] U8: 17/17 集成回归测试通过(零回归)
|
- [x] U8: 17/17 集成回归测试通过(零回归)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## P0-Q8: 前端巨型组件拆分 — SystemSettingsTab
|
||||||
|
|
||||||
|
### 验证用例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# U1: 主文件行数
|
||||||
|
wc -l /srv/aiops/frontend/src/components/system/SystemSettingsTab.tsx
|
||||||
|
# 期望: < 200 行
|
||||||
|
|
||||||
|
# U2: 子 Tab 文件存在
|
||||||
|
ls -la /srv/aiops/frontend/src/components/system/system-settings/
|
||||||
|
# 期望: GeneralSettingsTab.tsx, SecuritySettingsTab.tsx, LdapSettingsTab.tsx
|
||||||
|
|
||||||
|
# U3: TypeScript 编译零错误
|
||||||
|
cd /srv/aiops/frontend && npx tsc -b 2>&1 | grep 'error TS' | wc -l
|
||||||
|
# 期望: 0
|
||||||
|
|
||||||
|
# U4: Vite 构建
|
||||||
|
npm run build 2>&1 | tail -1
|
||||||
|
# 期望: built in Ns
|
||||||
|
|
||||||
|
# U5: ESLint 无新增 errors
|
||||||
|
npx eslint src/components/system/SystemSettingsTab.tsx src/components/system/system-settings/ --format json 2>/dev/null | python3 -c "
|
||||||
|
import json, sys
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
errors = sum(1 for r in data for m in r.get('messages', []) if m.get('severity') == 2)
|
||||||
|
assert errors == 0, f'{errors} errors'
|
||||||
|
print('✅ ESLint errors=0')
|
||||||
|
"
|
||||||
|
|
||||||
|
# U6: go build
|
||||||
|
cd /srv/aiops/backend && go build ./...
|
||||||
|
# 期望: 零错误
|
||||||
|
|
||||||
|
# U7: 集成回归
|
||||||
|
python3 /srv/aiops/tests/integration_test.py --host 127.0.0.1 --port 8080 | tail -3
|
||||||
|
# 期望: 17/17 passed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通过标准
|
||||||
|
- [x] U1: SystemSettingsTab.tsx 从 754 行降至 **173 行**(< 200 ✅)
|
||||||
|
- [x] U2: 3 个子 Tab 文件全部存在(GeneralSettingsTab/SecuritySettingsTab/LdapSettingsTab)
|
||||||
|
- [x] U3: `tsc -b` 零错误
|
||||||
|
- [x] U4: `npm run build` 零错误(✓ built in 17.71s)
|
||||||
|
- [x] U5: ESLint errors 保持 **0**(无新增)
|
||||||
|
- [x] U6: `go build` 零错误
|
||||||
|
- [x] U7: 17/17 集成回归测试通过(零回归)
|
||||||
|
|
||||||
@@ -1,37 +1,11 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
CardDescription,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import {
|
|
||||||
Lock,
|
|
||||||
History,
|
|
||||||
Key,
|
|
||||||
Settings,
|
|
||||||
Network,
|
|
||||||
RefreshCw,
|
|
||||||
Loader2,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { PageTabs } from "@/components/layout/PageTabs";
|
import { PageTabs } from "@/components/layout/PageTabs";
|
||||||
import { LdapManagementSection } from "./LdapManagementSection";
|
|
||||||
import type { JsonData } from "@/types/common";
|
import type { JsonData } from "@/types/common";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import { useUserStore } from "@/store";
|
import { GeneralSettingsTab } from "./system-settings/GeneralSettingsTab";
|
||||||
|
import { SecuritySettingsTab } from "./system-settings/SecuritySettingsTab";
|
||||||
|
import { LdapSettingsTab } from "./system-settings/LdapSettingsTab";
|
||||||
|
|
||||||
// 系统设置子 Tab:通用 / 安全 / LDAP // Logan.Li
|
// 系统设置子 Tab:通用 / 安全 / LDAP // Logan.Li
|
||||||
const SETTINGS_TABS = [
|
const SETTINGS_TABS = [
|
||||||
@@ -51,7 +25,7 @@ export interface LdapFormData {
|
|||||||
search_filter: string;
|
search_filter: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SystemSettingsTabProps {
|
export interface SystemSettingsTabProps {
|
||||||
// 审计策略 // Logan.Li
|
// 审计策略 // Logan.Li
|
||||||
auditRetentionDays: string;
|
auditRetentionDays: string;
|
||||||
setAuditRetentionDays: (v: string) => void;
|
setAuditRetentionDays: (v: string) => void;
|
||||||
@@ -157,44 +131,6 @@ interface SystemSettingsTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SystemSettingsTab(props: SystemSettingsTabProps) {
|
export function SystemSettingsTab(props: SystemSettingsTabProps) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const currentUsername = useUserStore(
|
|
||||||
(state) => state.user?.username || "admin",
|
|
||||||
);
|
|
||||||
const {
|
|
||||||
auditRetentionDays,
|
|
||||||
setAuditRetentionDays,
|
|
||||||
auditRecordAll,
|
|
||||||
setAuditRecordAll,
|
|
||||||
sessionWatermarkEnabled,
|
|
||||||
setSessionWatermarkEnabled,
|
|
||||||
sessionRecordEnabled,
|
|
||||||
setSessionRecordEnabled,
|
|
||||||
clipboardEnabled,
|
|
||||||
setClipboardEnabled,
|
|
||||||
fileUploadEnabled,
|
|
||||||
setFileUploadEnabled,
|
|
||||||
fileDownloadEnabled,
|
|
||||||
setFileDownloadEnabled,
|
|
||||||
commandPolicyEnabled,
|
|
||||||
setCommandPolicyEnabled,
|
|
||||||
approvalRequired,
|
|
||||||
setApprovalRequired,
|
|
||||||
auditLoading,
|
|
||||||
auditApplying,
|
|
||||||
handleAuditApply,
|
|
||||||
auditLastCleanupText,
|
|
||||||
auditRetentionOptions,
|
|
||||||
ldapConfig,
|
|
||||||
ldapForm,
|
|
||||||
setLdapForm,
|
|
||||||
ldapSaving,
|
|
||||||
ldapTesting,
|
|
||||||
ldapTestResult,
|
|
||||||
handleLdapSave,
|
|
||||||
handleLdapTest,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const tabFromUrl = searchParams.get("tab") as SettingsSubTab | null;
|
const tabFromUrl = searchParams.get("tab") as SettingsSubTab | null;
|
||||||
const [subTab, setSubTab] = useState<SettingsSubTab>(
|
const [subTab, setSubTab] = useState<SettingsSubTab>(
|
||||||
@@ -229,526 +165,9 @@ export function SystemSettingsTab(props: SystemSettingsTabProps) {
|
|||||||
onChange={changeSubTab}
|
onChange={changeSubTab}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{subTab === "general" && (
|
{subTab === "general" && <GeneralSettingsTab {...props} />}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pt-2">
|
{subTab === "security" && <SecuritySettingsTab {...props} />}
|
||||||
<Card className="dg-card">
|
{subTab === "ldap" && <LdapSettingsTab {...props} />}
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
|
||||||
<History className="h-5 w-5 dg-text-green" />
|
|
||||||
{t("system.audit")}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription className="dg-text-muted">
|
|
||||||
{t("system.auditSettingsDesc")}
|
|
||||||
</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.recordAllOps")}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs dg-text-muted">
|
|
||||||
{t("system.recordAllOpsDesc")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={auditRecordAll}
|
|
||||||
onCheckedChange={setAuditRecordAll}
|
|
||||||
disabled={auditLoading || auditApplying}
|
|
||||||
className="data-[state=checked]:bg-blue-600"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<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.logRetention")}
|
|
||||||
</p>
|
|
||||||
<p className="text-xs dg-text-muted">
|
|
||||||
{t("system.logRetentionHint")}
|
|
||||||
</p>
|
|
||||||
{auditLastCleanupText && (
|
|
||||||
<p className="text-xs dg-text-muted mt-1">
|
|
||||||
{auditLastCleanupText}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Select
|
|
||||||
value={auditRetentionDays}
|
|
||||||
onValueChange={setAuditRetentionDays}
|
|
||||||
disabled={auditLoading || auditApplying}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-28 h-8 dg-input shrink-0">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent className="dg-input">
|
|
||||||
{auditRetentionOptions.map((d) => (
|
|
||||||
<SelectItem key={d} value={String(d)}>
|
|
||||||
{d}天
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</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>
|
|
||||||
|
|
||||||
<Card className="dg-card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
|
||||||
<Settings className="h-5 w-5 dg-text-purple" />
|
|
||||||
{t("system.systemInfo")}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription className="dg-text-muted">
|
|
||||||
{t("system.systemInfoDesc")}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm dg-text-secondary">
|
|
||||||
{t("system.systemVersion")}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm text-[var(--dg-text-primary)]">
|
|
||||||
v2.5.1
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm dg-text-secondary">
|
|
||||||
{t("system.uptime")}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm text-[var(--dg-text-primary)]">
|
|
||||||
{t("system.uptimeValue")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{subTab === "security" && (
|
|
||||||
<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">30分钟</SelectItem>
|
|
||||||
<SelectItem value="60">
|
|
||||||
{t("monitor.traceMgr.hour1")}
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="120">2小时</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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{subTab === "ldap" && (
|
|
||||||
<>
|
|
||||||
<Card className="dg-card pt-2">
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
|
||||||
<Network className="h-5 w-5 dg-text-cyan" />
|
|
||||||
LDAP / Active Directory
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription className="dg-text-muted mt-1">
|
|
||||||
{t("system.ldapConfigHint")}
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<label className="text-xs dg-text-muted">
|
|
||||||
{t("system.ldapServerAddr")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={ldapForm.ldap_host}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLdapForm({ ...ldapForm, ldap_host: e.target.value })
|
|
||||||
}
|
|
||||||
className="dg-input"
|
|
||||||
placeholder="ldap.example.com"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<label className="text-xs dg-text-muted">
|
|
||||||
{t("system.ldapPort")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={ldapForm.ldap_port}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLdapForm({
|
|
||||||
...ldapForm,
|
|
||||||
ldap_port: Number(e.target.value),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="dg-input"
|
|
||||||
placeholder="389"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1.5 col-span-2">
|
|
||||||
<label className="text-xs dg-text-muted">
|
|
||||||
{t("system.baseDnHint")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={ldapForm.base_dn}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLdapForm({ ...ldapForm, base_dn: e.target.value })
|
|
||||||
}
|
|
||||||
className="dg-input"
|
|
||||||
placeholder="ou=users,dc=example,dc=com"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<label className="text-xs dg-text-muted">
|
|
||||||
{t("system.adminBindDn")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={ldapForm.admin_dn}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLdapForm({ ...ldapForm, admin_dn: e.target.value })
|
|
||||||
}
|
|
||||||
className="dg-input"
|
|
||||||
placeholder="cn=admin,dc=example,dc=com"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
<label className="text-xs dg-text-muted">
|
|
||||||
管理员密码 {ldapConfig && t("system.leaveBlankNoModify")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
type="password"
|
|
||||||
value={ldapForm.admin_password}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLdapForm({
|
|
||||||
...ldapForm,
|
|
||||||
admin_password: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="dg-input"
|
|
||||||
placeholder="******"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1.5 col-span-2">
|
|
||||||
<label className="text-xs dg-text-muted">
|
|
||||||
{t("system.searchFilterHint")}
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
value={ldapForm.search_filter}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLdapForm({
|
|
||||||
...ldapForm,
|
|
||||||
search_filter: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="dg-input"
|
|
||||||
placeholder="(uid=%s)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 col-span-2">
|
|
||||||
<Switch
|
|
||||||
checked={ldapForm.use_ldaps}
|
|
||||||
onCheckedChange={(v) =>
|
|
||||||
setLdapForm({ ...ldapForm, use_ldaps: v })
|
|
||||||
}
|
|
||||||
className="data-[state=checked]:bg-blue-600"
|
|
||||||
/>
|
|
||||||
<span className="text-sm text-[var(--dg-text-secondary)]">
|
|
||||||
{t("system.enableLdaps")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 测试/保存按钮 */}
|
|
||||||
<div className="flex items-center gap-3 mt-4 pt-4 border-t border-[var(--dg-border)]">
|
|
||||||
{ldapTestResult && (
|
|
||||||
<span
|
|
||||||
className={`text-xs ${ldapTestResult.includes("成功") ? "dg-text-green" : "dg-text-red"}`}
|
|
||||||
>
|
|
||||||
{ldapTestResult}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-2 ml-auto">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="dg-btn-secondary"
|
|
||||||
onClick={handleLdapTest}
|
|
||||||
disabled={ldapTesting}
|
|
||||||
>
|
|
||||||
{ldapTesting ? (
|
|
||||||
<Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<RefreshCw className="h-3.5 w-3.5 mr-1" />
|
|
||||||
)}
|
|
||||||
测试连接
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="dg-btn-primary"
|
|
||||||
onClick={handleLdapSave}
|
|
||||||
disabled={ldapSaving}
|
|
||||||
>
|
|
||||||
{ldapSaving ? t("k8s.topology.saving") : t("aiops.llmSave")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{ldapConfig && <LdapManagementSection {...props} />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
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 { History, Settings, Loader2 } from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import type { SystemSettingsTabProps } from "../SystemSettingsTab";
|
||||||
|
|
||||||
|
// 通用设置 Tab:审计策略 + 系统信息 // Logan.Li
|
||||||
|
type GeneralSettingsTabProps = Pick<
|
||||||
|
SystemSettingsTabProps,
|
||||||
|
| "auditRetentionDays"
|
||||||
|
| "setAuditRetentionDays"
|
||||||
|
| "auditRecordAll"
|
||||||
|
| "setAuditRecordAll"
|
||||||
|
| "auditLoading"
|
||||||
|
| "auditApplying"
|
||||||
|
| "handleAuditApply"
|
||||||
|
| "auditLastCleanupText"
|
||||||
|
| "auditRetentionOptions"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function GeneralSettingsTab(props: GeneralSettingsTabProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
auditRetentionDays,
|
||||||
|
setAuditRetentionDays,
|
||||||
|
auditRecordAll,
|
||||||
|
setAuditRecordAll,
|
||||||
|
auditLoading,
|
||||||
|
auditApplying,
|
||||||
|
handleAuditApply,
|
||||||
|
auditLastCleanupText,
|
||||||
|
auditRetentionOptions,
|
||||||
|
} = 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">
|
||||||
|
<History className="h-5 w-5 dg-text-green" />
|
||||||
|
{t("system.audit")}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="dg-text-muted">
|
||||||
|
{t("system.auditSettingsDesc")}
|
||||||
|
</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.recordAllOps")}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs dg-text-muted">
|
||||||
|
{t("system.recordAllOpsDesc")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={auditRecordAll}
|
||||||
|
onCheckedChange={setAuditRecordAll}
|
||||||
|
disabled={auditLoading || auditApplying}
|
||||||
|
className="data-[state=checked]:bg-blue-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<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.logRetention")}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs dg-text-muted">
|
||||||
|
{t("system.logRetentionHint")}
|
||||||
|
</p>
|
||||||
|
{auditLastCleanupText && (
|
||||||
|
<p className="text-xs dg-text-muted mt-1">
|
||||||
|
{auditLastCleanupText}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
value={auditRetentionDays}
|
||||||
|
onValueChange={setAuditRetentionDays}
|
||||||
|
disabled={auditLoading || auditApplying}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-28 h-8 dg-input shrink-0">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="dg-input">
|
||||||
|
{auditRetentionOptions.map((d) => (
|
||||||
|
<SelectItem key={d} value={String(d)}>
|
||||||
|
{d}天
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<Card className="dg-card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||||
|
<Settings className="h-5 w-5 dg-text-purple" />
|
||||||
|
{t("system.systemInfo")}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="dg-text-muted">
|
||||||
|
{t("system.systemInfoDesc")}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm dg-text-secondary">
|
||||||
|
{t("system.systemVersion")}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-[var(--dg-text-primary)]">
|
||||||
|
v2.5.1
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm dg-text-secondary">
|
||||||
|
{t("system.uptime")}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-[var(--dg-text-primary)]">
|
||||||
|
{t("system.uptimeValue")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Network, RefreshCw, Loader2 } from "lucide-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { LdapManagementSection } from "../LdapManagementSection";
|
||||||
|
import type { SystemSettingsTabProps } from "../SystemSettingsTab";
|
||||||
|
|
||||||
|
// LDAP 设置 Tab:LDAP/AD 配置表单 + 组织用户管理区 // Logan.Li
|
||||||
|
// 需要转发全部 props 给 LdapManagementSection,所以接收整个 props 对象
|
||||||
|
type LdapSettingsTabProps = Pick<
|
||||||
|
SystemSettingsTabProps,
|
||||||
|
| "ldapConfig"
|
||||||
|
| "ldapForm"
|
||||||
|
| "setLdapForm"
|
||||||
|
| "ldapSaving"
|
||||||
|
| "ldapTesting"
|
||||||
|
| "ldapTestResult"
|
||||||
|
| "handleLdapSave"
|
||||||
|
| "handleLdapTest"
|
||||||
|
> &
|
||||||
|
// LdapManagementSection 需要的透传字段
|
||||||
|
Pick<
|
||||||
|
SystemSettingsTabProps,
|
||||||
|
| "ldapMgmtOpen"
|
||||||
|
| "setLdapMgmtOpen"
|
||||||
|
| "ldapOUs"
|
||||||
|
| "selectedOUDN"
|
||||||
|
| "setSelectedOUDN"
|
||||||
|
| "ldapUsers"
|
||||||
|
| "selectedLDAPUsers"
|
||||||
|
| "setSelectedLDAPUsers"
|
||||||
|
| "loadLdapOUs"
|
||||||
|
| "loadLdapOUUsers"
|
||||||
|
| "handleLdapImport"
|
||||||
|
| "importing"
|
||||||
|
| "importFeedback"
|
||||||
|
| "importResultOpen"
|
||||||
|
| "setImportResultOpen"
|
||||||
|
| "createOUOpen"
|
||||||
|
| "setCreateOUOpen"
|
||||||
|
| "ouForm"
|
||||||
|
| "setOuForm"
|
||||||
|
| "handleCreateOU"
|
||||||
|
| "createUserOpen"
|
||||||
|
| "setCreateUserOpen"
|
||||||
|
| "newUser"
|
||||||
|
| "setNewUser"
|
||||||
|
| "handleCreateLDAPUser"
|
||||||
|
| "ldapUserTestOpen"
|
||||||
|
| "setLdapUserTestOpen"
|
||||||
|
| "ldapTestUser"
|
||||||
|
| "setLdapTestUser"
|
||||||
|
| "ldapTestPass"
|
||||||
|
| "setLdapTestPass"
|
||||||
|
| "ldapUserTesting"
|
||||||
|
| "ldapUserTestResult"
|
||||||
|
| "handleLdapUserTest"
|
||||||
|
| "editLdapUser"
|
||||||
|
| "setEditLdapUser"
|
||||||
|
| "editLdapForm"
|
||||||
|
| "setEditLdapForm"
|
||||||
|
| "handleUpdateLdapUser"
|
||||||
|
| "handleDeleteLDAPEntry"
|
||||||
|
| "openEditLdapUser"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function LdapSettingsTab(props: LdapSettingsTabProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
ldapConfig,
|
||||||
|
ldapForm,
|
||||||
|
setLdapForm,
|
||||||
|
ldapSaving,
|
||||||
|
ldapTesting,
|
||||||
|
ldapTestResult,
|
||||||
|
handleLdapSave,
|
||||||
|
handleLdapTest,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card className="dg-card pt-2">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-[var(--dg-text-primary)] flex items-center gap-2">
|
||||||
|
<Network className="h-5 w-5 dg-text-cyan" />
|
||||||
|
LDAP / Active Directory
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="dg-text-muted mt-1">
|
||||||
|
{t("system.ldapConfigHint")}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs dg-text-muted">
|
||||||
|
{t("system.ldapServerAddr")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={ldapForm.ldap_host}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLdapForm({ ...ldapForm, ldap_host: e.target.value })
|
||||||
|
}
|
||||||
|
className="dg-input"
|
||||||
|
placeholder="ldap.example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs dg-text-muted">
|
||||||
|
{t("system.ldapPort")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={ldapForm.ldap_port}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLdapForm({
|
||||||
|
...ldapForm,
|
||||||
|
ldap_port: Number(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="dg-input"
|
||||||
|
placeholder="389"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5 col-span-2">
|
||||||
|
<label className="text-xs dg-text-muted">
|
||||||
|
{t("system.baseDnHint")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={ldapForm.base_dn}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLdapForm({ ...ldapForm, base_dn: e.target.value })
|
||||||
|
}
|
||||||
|
className="dg-input"
|
||||||
|
placeholder="ou=users,dc=example,dc=com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs dg-text-muted">
|
||||||
|
{t("system.adminBindDn")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={ldapForm.admin_dn}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLdapForm({ ...ldapForm, admin_dn: e.target.value })
|
||||||
|
}
|
||||||
|
className="dg-input"
|
||||||
|
placeholder="cn=admin,dc=example,dc=com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs dg-text-muted">
|
||||||
|
管理员密码 {ldapConfig && t("system.leaveBlankNoModify")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={ldapForm.admin_password}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLdapForm({
|
||||||
|
...ldapForm,
|
||||||
|
admin_password: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="dg-input"
|
||||||
|
placeholder="******"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5 col-span-2">
|
||||||
|
<label className="text-xs dg-text-muted">
|
||||||
|
{t("system.searchFilterHint")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={ldapForm.search_filter}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLdapForm({
|
||||||
|
...ldapForm,
|
||||||
|
search_filter: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="dg-input"
|
||||||
|
placeholder="(uid=%s)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 col-span-2">
|
||||||
|
<Switch
|
||||||
|
checked={ldapForm.use_ldaps}
|
||||||
|
onCheckedChange={(v) =>
|
||||||
|
setLdapForm({ ...ldapForm, use_ldaps: v })
|
||||||
|
}
|
||||||
|
className="data-[state=checked]:bg-blue-600"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-[var(--dg-text-secondary)]">
|
||||||
|
{t("system.enableLdaps")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 测试/保存按钮 */}
|
||||||
|
<div className="flex items-center gap-3 mt-4 pt-4 border-t border-[var(--dg-border)]">
|
||||||
|
{ldapTestResult && (
|
||||||
|
<span
|
||||||
|
className={`text-xs ${ldapTestResult.includes("成功") ? "dg-text-green" : "dg-text-red"}`}
|
||||||
|
>
|
||||||
|
{ldapTestResult}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center gap-2 ml-auto">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="dg-btn-secondary"
|
||||||
|
onClick={handleLdapTest}
|
||||||
|
disabled={ldapTesting}
|
||||||
|
>
|
||||||
|
{ldapTesting ? (
|
||||||
|
<Loader2 className="h-3.5 w-3.5 mr-1 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<RefreshCw className="h-3.5 w-3.5 mr-1" />
|
||||||
|
)}
|
||||||
|
测试连接
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="dg-btn-primary"
|
||||||
|
onClick={handleLdapSave}
|
||||||
|
disabled={ldapSaving}
|
||||||
|
>
|
||||||
|
{ldapSaving ? t("k8s.topology.saving") : t("aiops.llmSave")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{ldapConfig && <LdapManagementSection {...props} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
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">30分钟</SelectItem>
|
||||||
|
<SelectItem value="60">
|
||||||
|
{t("monitor.traceMgr.hour1")}
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="120">2小时</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user