Skip to content

Commit 25bbea0

Browse files
author
dushixiang
committed
v3.0.0
1 parent cf02f7e commit 25bbea0

File tree

217 files changed

+12413
-7998
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

217 files changed

+12413
-7998
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"js-base64": "^3.7.8",
3636
"lucide-react": "^0.553.0",
3737
"monaco-editor": "^0.55.1",
38-
"qs": "^6.14.0",
38+
"qs": "^6.14.1",
3939
"react": "^18.3.1",
4040
"react-countup": "^6.5.3",
4141
"react-dom": "^18.3.1",

scripts/i18n-audit.mjs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
const projectRoot = path.resolve(process.cwd(), 'web');
5+
const srcRoot = path.join(projectRoot, 'src');
6+
const localesDir = path.join(srcRoot, 'react-i18next', 'locales');
7+
const locales = ['zh-CN', 'en-US', 'zh-TW', 'ja-JP'];
8+
9+
const fileExts = new Set(['.ts', '.tsx', '.js', '.jsx']);
10+
const chinesePattern = /[\u4e00-\u9fff]/;
11+
12+
const readJson = (p) => JSON.parse(fs.readFileSync(p, 'utf8'));
13+
14+
const flatten = (obj, prefix = '', out = {}) => {
15+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
16+
Object.entries(obj).forEach(([key, value]) => {
17+
const next = prefix ? `${prefix}.${key}` : key;
18+
flatten(value, next, out);
19+
});
20+
return out;
21+
}
22+
out[prefix] = obj;
23+
return out;
24+
};
25+
26+
const walkFiles = (dir) => {
27+
const results = [];
28+
const stack = [dir];
29+
while (stack.length) {
30+
const current = stack.pop();
31+
const entries = fs.readdirSync(current, { withFileTypes: true });
32+
for (const entry of entries) {
33+
const full = path.join(current, entry.name);
34+
if (entry.isDirectory()) {
35+
stack.push(full);
36+
} else if (fileExts.has(path.extname(entry.name))) {
37+
results.push(full);
38+
}
39+
}
40+
}
41+
return results;
42+
};
43+
44+
const stripComments = (code) => {
45+
const withoutBlock = code.replace(/\/\*[\s\S]*?\*\//g, '');
46+
return withoutBlock.replace(/\/\/.*$/gm, '');
47+
};
48+
49+
const collectUsedKeys = (files) => {
50+
const used = new Set();
51+
const pattern = /\b(?:t|i18n\.t)\(\s*['"`]([^'"`]+)['"`]/g;
52+
for (const file of files) {
53+
const content = fs.readFileSync(file, 'utf8');
54+
let match;
55+
while ((match = pattern.exec(content)) !== null) {
56+
used.add(match[1]);
57+
}
58+
}
59+
return used;
60+
};
61+
62+
const collectRawChinese = (files) => {
63+
const findings = [];
64+
for (const file of files) {
65+
const raw = fs.readFileSync(file, 'utf8');
66+
const content = stripComments(raw);
67+
const lines = content.split(/\r?\n/);
68+
lines.forEach((line, index) => {
69+
if (!chinesePattern.test(line)) return;
70+
if (line.includes('t(') || line.includes('i18n.t')) return;
71+
const trimmed = line.trim();
72+
if (!trimmed) return;
73+
findings.push({
74+
file,
75+
line: index + 1,
76+
text: trimmed,
77+
});
78+
});
79+
}
80+
return findings;
81+
};
82+
83+
const main = () => {
84+
const localeData = {};
85+
const flatLocales = {};
86+
locales.forEach((locale) => {
87+
const jsonPath = path.join(localesDir, `${locale}.json`);
88+
localeData[locale] = readJson(jsonPath);
89+
flatLocales[locale] = flatten(localeData[locale]);
90+
});
91+
92+
const allKeys = new Set();
93+
locales.forEach((locale) => {
94+
Object.keys(flatLocales[locale]).forEach((key) => allKeys.add(key));
95+
});
96+
97+
const missingByLocale = {};
98+
locales.forEach((locale) => {
99+
missingByLocale[locale] = Array.from(allKeys).filter(
100+
(key) => !(key in flatLocales[locale])
101+
);
102+
});
103+
104+
const duplicateGroups = new Map();
105+
const localeKeys = locales.filter(Boolean);
106+
const sharedKeys = Array.from(allKeys).filter((key) =>
107+
localeKeys.every((locale) => key in flatLocales[locale])
108+
);
109+
sharedKeys.forEach((key) => {
110+
const values = localeKeys.map((locale) => flatLocales[locale][key]);
111+
const signature = JSON.stringify(values);
112+
if (!duplicateGroups.has(signature)) {
113+
duplicateGroups.set(signature, []);
114+
}
115+
duplicateGroups.get(signature).push(key);
116+
});
117+
118+
const duplicateList = Array.from(duplicateGroups.values()).filter(
119+
(group) => group.length > 1
120+
);
121+
122+
const codeFiles = walkFiles(srcRoot);
123+
const usedKeys = collectUsedKeys(codeFiles);
124+
const unusedKeys = Array.from(allKeys).filter((key) => !usedKeys.has(key));
125+
126+
const rawChinese = collectRawChinese(codeFiles);
127+
128+
console.log(`Locales: ${locales.join(', ')}`);
129+
console.log(`Total keys: ${allKeys.size}`);
130+
locales.forEach((locale) => {
131+
console.log(`${locale} keys: ${Object.keys(flatLocales[locale]).length}`);
132+
});
133+
134+
console.log('\nMissing keys per locale:');
135+
locales.forEach((locale) => {
136+
console.log(`${locale}: ${missingByLocale[locale].length}`);
137+
});
138+
139+
console.log('\nDuplicate value groups (same text across all locales):');
140+
console.log(`Groups: ${duplicateList.length}`);
141+
duplicateList.slice(0, 20).forEach((group) => {
142+
console.log(`- ${group.length} :: ${group.slice(0, 6).join(', ')}${group.length > 6 ? ' ...' : ''}`);
143+
});
144+
145+
console.log('\nUnused keys (by t("...") scan):');
146+
console.log(`Count: ${unusedKeys.length}`);
147+
console.log(unusedKeys.slice(0, 50).join('\n'));
148+
149+
console.log('\nRaw Chinese strings (non-i18n, code scan):');
150+
console.log(`Count: ${rawChinese.length}`);
151+
rawChinese.slice(0, 50).forEach((item) => {
152+
console.log(`${path.relative(projectRoot, item.file)}:${item.line} ${item.text}`);
153+
});
154+
};
155+
156+
main();

src/App.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@
109109
scrollbar-gutter: stable;
110110
}
111111

112+
/* 暗黑模式 */
113+
.dark .ant-table-container .ant-table-body,
114+
.dark .ant-table-container .ant-table-content {
115+
scrollbar-color: #444 transparent;
116+
}
117+
112118
/* 移动端全局样式优化 */
113119
@media (max-width: 768px) {
114120
/* 页面容器优化 */

src/App.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@ const LoginPolicyPostPage = lazy(() => import("@/pages/identity/LoginPolicyPostP
3636
const LoginPolicyDetailPage = lazy(() => import("@/pages/identity/LoginPolicyDetailPage"));
3737
const OidcClientPage = lazy(() => import("@/pages/identity/OidcClientPage"));
3838
const AssetsPage = lazy(() => import("@/pages/assets/AssetPage"));
39+
const DatabaseAssetPage = lazy(() => import("@/pages/assets/DatabaseAssetPage"));
3940
const CredentialPage = lazy(() => import("@/pages/assets/CredentialPage"));
4041
const CertificatePage = lazy(() => import("@/pages/assets/CertificatePage"));
4142
const SnippetPage = lazy(() => import("@/pages/assets/SnippetPage"));
4243
const StrategyPage = lazy(() => import("@/pages/authorised/StrategyPage"));
4344
const CommandFilterPage = lazy(() => import("@/pages/authorised/CommandFilterPage"));
4445
const CommandFilterDetail = lazy(() => import("@/pages/authorised/CommandFilterDetail"));
46+
const AuthorisedDatabaseAssetPage = lazy(() => import("@/pages/authorised/AuthorisedDatabaseAssetPage"));
47+
const AuthorisedDatabaseAssetPost = lazy(() => import("@/pages/authorised/AuthorisedDatabaseAssetPost"));
4548
const ScheduledTaskPage = lazy(() => import("@/pages/sysops/ScheduledTaskPage"));
4649
const ToolsPage = lazy(() => import("@/pages/sysops/ToolsPage"));
4750
const LoginLogPage = lazy(() => import("@/pages/audit/LoginLogPage"));
@@ -55,14 +58,15 @@ const GuacdMonitor = lazy(() => import("@/pages/access/GuacdMonitor"));
5558
const FileSystemLogPage = lazy(() => import("@/pages/audit/FileSystemLogPage"));
5659
const AccessLogPage = lazy(() => import("@/pages/audit/AccessLogPage"));
5760
const AccessLogStatsPage = lazy(() => import("@/pages/audit/AccessLogStatsPage"));
61+
const DatabaseSQLLogPage = lazy(() => import("@/pages/dbproxy/DatabaseSQLLogPage"));
5862
const SshGatewayPage = lazy(() => import("@/pages/gateway/SshGatewayPage"));
5963
const AgentGatewayPage = lazy(() => import("@/pages/gateway/AgentGatewayPage"));
6064
const GatewayGroupPage = lazy(() => import("@/pages/gateway/GatewayGroupPage"));
6165
const ErrorPage = lazy(() => import("@/components/ErrorPage"));
6266
const StoragePage = lazy(() => import("@/pages/assets/StoragePage"));
6367
const WebsitePage = lazy(() => import("@/pages/assets/WebsitePage"));
6468
const BrowserPage = lazy(() => import("@/pages/access/BrowserPage"));
65-
const FacadePage = lazy(() => import("@/pages/facade/FacadePage"));
69+
const FacadePage = lazy(() => import("@/pages/facade/AssetFacadePage.tsx"));
6670
const WebsiteFacadePage = lazy(() => import("@/pages/facade/WebsiteFacadePage"));
6771
const RedirectPage = lazy(() => import("@/layout/RedirectPage"));
6872
const UserLayout = lazy(() => import("@/layout/UserLayout"));
@@ -71,6 +75,8 @@ const UserInfoPage = lazy(() => import("@/pages/facade/UserInfoPage"));
7175
const SnippetUserPage = lazy(() => import("@/pages/facade/SnippetUserPage"));
7276
const SystemMonitorPage = lazy(() => import("@/pages/sysops/SystemMonitorPage"));
7377
const SetupPage = lazy(() => import("@/pages/identity/SetupPage"));
78+
const DatabaseWorkOrderPage = lazy(() => import("@/pages/dbproxy/DatabaseWorkOrderPage"));
79+
const DatabaseWorkOrderUserPage = lazy(() => import("@/pages/facade/DatabaseWorkOrderUserPage"));
7480

7581
const router = createBrowserRouter([
7682
{path: "/setup", element: <SetupPage/>},
@@ -95,6 +101,7 @@ const router = createBrowserRouter([
95101
{path: "/x-website", element: <WebsiteFacadePage/>,},
96102
{path: "/x-snippet", element: <SnippetUserPage/>,},
97103
{path: "/x-info", element: <UserInfoPage/>,},
104+
{path: "/x-db-work-order", element: <DatabaseWorkOrderUserPage/>},
98105
]
99106
},
100107
{
@@ -116,11 +123,13 @@ const router = createBrowserRouter([
116123
{path: "/operation-log", element: <OperationLogPage/>},
117124

118125
{path: "/asset", element: <AssetsPage/>},
126+
{path: "/database-asset", element: <DatabaseAssetPage/>},
119127
{path: "/credential", element: <CredentialPage/>},
120128
{path: "/snippet", element: <SnippetPage/>},
121129
{path: "/storage", element: <StoragePage/>},
122130
{path: "/website", element: <WebsitePage/>},
123131
{path: "/certificate", element: <CertificatePage/>},
132+
{path: "/db-work-order", element: <DatabaseWorkOrderPage/>},
124133

125134
{path: "/strategy", element: <StrategyPage/>},
126135
{path: "/command-filter", element: <CommandFilterPage/>},
@@ -129,6 +138,8 @@ const router = createBrowserRouter([
129138
{path: "/authorised-asset/post", element: <AuthorisedAssetPost/>},
130139
{path: "/authorised-website", element: <AuthorisedWebsitePage/>},
131140
{path: "/authorised-website/post", element: <AuthorisedWebsitePost/>},
141+
{path: "/authorised-database-asset", element: <AuthorisedDatabaseAssetPage/>},
142+
{path: "/authorised-database-asset/post", element: <AuthorisedDatabaseAssetPost/>},
132143

133144
{path: "/scheduled-task", element: <ScheduledTaskPage/>},
134145
{path: "/tools", element: <ToolsPage/>},
@@ -139,6 +150,7 @@ const router = createBrowserRouter([
139150
{path: "/filesystem-log", element: <FileSystemLogPage/>},
140151
{path: "/access-log", element: <AccessLogPage/>},
141152
{path: "/access-log-stats", element: <AccessLogStatsPage/>},
153+
{path: "/database-sql-log", element: <DatabaseSQLLogPage/>},
142154

143155
{path: "/ssh-gateway", element: <SshGatewayPage/>},
144156
{path: "/agent-gateway", element: <AgentGatewayPage/>},

src/api/account-api.ts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,39 @@
1-
import requests from "./core/requests";
1+
import requests, {baseUrl, getToken} from "./core/requests";
22
import {setCurrentUser} from "@/utils/permission";
33
import eventEmitter from "@/api/core/event-emitter";
4+
import {browserDownload} from "@/utils/utils";
45
// @ts-ignore
56
import {PublicKeyCredentialCreationOptionsJSON} from "@simplewebauthn/browser/script/types";
67
// @ts-ignore
78
import type {PublicKeyCredentialRequestOptionsJSON} from "@simplewebauthn/browser/esm/types";
89

9-
interface AccessToken {
10+
export interface AccessTokenItem {
1011
id: string;
11-
userId: string;
12-
userType: string;
12+
token: string;
1313
type: string;
1414
createdAt: number;
1515
}
1616

17+
export interface AccessTokenCreateResult {
18+
id: string;
19+
token: string;
20+
mask: string;
21+
type: string;
22+
createdAt: number;
23+
}
24+
25+
export interface UserClientCertInfo {
26+
id: string;
27+
serialNumber: string;
28+
fingerprint: string;
29+
notBefore: number;
30+
notAfter: number;
31+
status: string;
32+
revokedAt: number;
33+
lastUsedAt: number;
34+
createdAt: number;
35+
}
36+
1737
export interface PasswordPolicy {
1838
minLength: number;
1939
minCharacterType: number;
@@ -61,6 +81,7 @@ export type AccountInfo = {
6181
nickname: string;
6282
type: string;
6383
enabledTotp: boolean;
84+
mfaEnabled: boolean;
6485
roles: string[];
6586
menus?: Menu[];
6687
language: string;
@@ -151,16 +172,29 @@ class AccountApi {
151172
return data;
152173
}
153174

154-
getAccessToken = async () => {
155-
return await requests.get(`/${this.group}/access-token`) as AccessToken;
175+
getAccessTokens = async () => {
176+
return await requests.get(`/${this.group}/access-token`) as AccessTokenItem[];
177+
}
178+
179+
createAccessToken = async (type: string = 'api') => {
180+
return await requests.post(`/${this.group}/access-token`, {type}) as AccessTokenCreateResult;
181+
}
182+
183+
deleteAccessToken = async (id: string) => {
184+
return await requests.delete(`/${this.group}/access-token/${id}`);
185+
}
186+
187+
getClientCert = async () => {
188+
return await requests.get(`/${this.group}/client-cert`) as UserClientCertInfo | null;
156189
}
157190

158-
createAccessToken = async () => {
159-
return await requests.post(`/${this.group}/access-token`);
191+
downloadClientCert = async () => {
192+
let u = `${baseUrl()}/${this.group}/client-cert/download?X-Auth-Token=${getToken()}`
193+
browserDownload(u)
160194
}
161195

162-
deleteAccessToken = async () => {
163-
return await requests.delete(`/${this.group}/access-token`);
196+
revokeClientCert = async () => {
197+
return await requests.delete(`/${this.group}/client-cert`);
164198
}
165199

166200
getPasswordPolicy = async () => {
@@ -199,12 +233,14 @@ class AccountApi {
199233
await requests.put(`/${this.group}/webauthn/credentials/${id}`, val);
200234
}
201235

202-
deleteWebauthnCredentials = async (id: string) => {
203-
await requests.delete(`/${this.group}/webauthn/credentials/${id}`);
236+
deleteWebauthnCredentials = async (id: string, securityToken?: string) => {
237+
const tokenParam = securityToken ? `?securityToken=${encodeURIComponent(securityToken)}` : '';
238+
await requests.delete(`/${this.group}/webauthn/credentials/${id}${tokenParam}`);
204239
}
205240

206-
webauthnCredentialStart = async () => {
207-
return await requests.post(`/${this.group}/webauthn/credentials/start`) as WebauthnCredentialCreation;
241+
webauthnCredentialStart = async (securityToken?: string) => {
242+
const tokenParam = securityToken ? `?securityToken=${encodeURIComponent(securityToken)}` : '';
243+
return await requests.post(`/${this.group}/webauthn/credentials/start${tokenParam}`) as WebauthnCredentialCreation;
208244
}
209245

210246
webauthnCredentialFinish = async (val: any) => {
@@ -277,4 +313,4 @@ class AccountApi {
277313
}
278314

279315
let accountApi = new AccountApi();
280-
export default accountApi;
316+
export default accountApi;

src/api/asset-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface Asset {
66
id: string;
77
logo: string;
88
name: string;
9+
alias?: string;
910
protocol: string;
1011
ip: string;
1112
port: number;

0 commit comments

Comments
 (0)