@liujitcn/kratos-admin-system 0.0.50 → 0.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/package/declarations/src/api/system/admin/v1/base_redact_output_policy.d.ts +4 -1
- package/dist/package/declarations/src/api/system/admin/v1/base_redact_storage_policy.d.ts +3 -1
- package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +1 -2
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +10 -0
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +8 -3
- package/dist/package/declarations/src/index.d.ts +1 -1
- package/dist/package/declarations/src/locales/generated.d.ts +24 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +8 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_storage_policy.d.ts +25 -0
- package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +37 -0
- package/dist/package/src/api/system/admin/v1/base_redact_output_policy.ts +7 -0
- package/dist/package/src/api/system/admin/v1/base_redact_storage_policy.ts +7 -0
- package/dist/package/src/components/tenant-project/TenantProjectManager.vue +77 -60
- package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +39 -1
- package/dist/package/src/components/tenant-project/tenant-project-manager.ts +19 -4
- package/dist/package/src/index.ts +2 -0
- package/dist/package/src/locales/en-US.json +6 -0
- package/dist/package/src/locales/ja-JP.json +6 -0
- package/dist/package/src/locales/zh-CN.json +6 -0
- package/dist/package/src/locales/zh-TW.json +6 -0
- package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +9 -0
- package/dist/package/src/rpc/system/admin/v1/base_redact_storage_policy.ts +30 -0
- package/dist/package/src/views/ai/chat/components/XSender.vue +18 -61
- package/dist/package/src/views/ai/chat/components/speech-recognition.ts +177 -0
- package/dist/package/src/views/base/job/index.vue +82 -158
- package/dist/package/src/views/base/login-policy/index.vue +12 -6
- package/dist/package/src/views/base/redact-output-policy/index.vue +19 -7
- package/dist/package/src/views/base/redact-rule/index.vue +70 -53
- package/dist/package/src/views/base/redact-storage-policy/index.vue +18 -7
- package/dist/package/src/views/base/role/index.vue +1 -1
- package/dist/package/src/views/base/{project-grant → tenant-project-grant}/index.vue +3 -1
- package/package.json +2 -2
- /package/dist/package/declarations/src/components/{tenant-project → tenant-project-grant}/ProjectGrantDialog.vue.d.ts +0 -0
- /package/dist/package/declarations/src/views/base/{project-grant → tenant-project-grant}/index.vue.d.ts +0 -0
- /package/dist/package/src/components/{tenant-project → tenant-project-grant}/ProjectGrantDialog.vue +0 -0
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
24
|
<script setup lang="ts">
|
|
25
|
-
import { computed, h, reactive, ref
|
|
26
|
-
import {
|
|
25
|
+
import { computed, h, reactive, ref } from "vue";
|
|
26
|
+
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
27
27
|
import { CirclePlus, Delete, EditPen, Promotion, Tickets, VideoPause, VideoPlay } from "@element-plus/icons-vue";
|
|
28
28
|
import type {
|
|
29
29
|
ColumnProps,
|
|
@@ -172,132 +172,6 @@ function renderJobNameCell(scope: RenderScope<BaseJob>) {
|
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
/**
|
|
176
|
-
* 渲染定时任务操作列。
|
|
177
|
-
*/
|
|
178
|
-
function renderOperationCell(scope: RenderScope<BaseJob>) {
|
|
179
|
-
const row = scope.row;
|
|
180
|
-
const actionNodes: VNode[] = [];
|
|
181
|
-
|
|
182
|
-
if (BUTTONS.value["base:job:update"]) {
|
|
183
|
-
actionNodes.push(
|
|
184
|
-
h(
|
|
185
|
-
ElButton,
|
|
186
|
-
{
|
|
187
|
-
key: `edit-${row.id}`,
|
|
188
|
-
type: "primary",
|
|
189
|
-
link: true,
|
|
190
|
-
icon: EditPen,
|
|
191
|
-
onClick: () => handleOpenDialog(row.id)
|
|
192
|
-
},
|
|
193
|
-
() => t("common.action.edit")
|
|
194
|
-
)
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (BUTTONS.value["base:job:delete"]) {
|
|
199
|
-
actionNodes.push(
|
|
200
|
-
h(
|
|
201
|
-
ElButton,
|
|
202
|
-
{
|
|
203
|
-
key: `delete-${row.id}`,
|
|
204
|
-
type: "danger",
|
|
205
|
-
link: true,
|
|
206
|
-
icon: Delete,
|
|
207
|
-
onClick: () => handleDelete(row)
|
|
208
|
-
},
|
|
209
|
-
() => t("common.action.delete")
|
|
210
|
-
)
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
row.status === Status.STATUS_ENABLE &&
|
|
216
|
-
(row.entry_id === undefined || row.entry_id === 0) &&
|
|
217
|
-
BUTTONS.value["base:job:start"]
|
|
218
|
-
) {
|
|
219
|
-
actionNodes.push(
|
|
220
|
-
h(
|
|
221
|
-
ElButton,
|
|
222
|
-
{
|
|
223
|
-
key: `start-${row.id}`,
|
|
224
|
-
type: "primary",
|
|
225
|
-
link: true,
|
|
226
|
-
icon: VideoPlay,
|
|
227
|
-
class: "job-action job-action--start",
|
|
228
|
-
onClick: () => handleStart(row.id, row.name)
|
|
229
|
-
},
|
|
230
|
-
() => t("system.base.job.action.start")
|
|
231
|
-
)
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (row.status === Status.STATUS_ENABLE && row.entry_id > 0 && BUTTONS.value["base:job:stop"]) {
|
|
236
|
-
actionNodes.push(
|
|
237
|
-
h(
|
|
238
|
-
ElButton,
|
|
239
|
-
{
|
|
240
|
-
key: `stop-${row.id}`,
|
|
241
|
-
type: "warning",
|
|
242
|
-
link: true,
|
|
243
|
-
icon: VideoPause,
|
|
244
|
-
class: "job-action job-action--stop",
|
|
245
|
-
onClick: () => handleStop(row.id, row.name)
|
|
246
|
-
},
|
|
247
|
-
() => t("system.base.job.action.stop")
|
|
248
|
-
)
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
row.status === Status.STATUS_ENABLE &&
|
|
254
|
-
(row.entry_id === undefined || row.entry_id === 0) &&
|
|
255
|
-
BUTTONS.value["base:job:exec"]
|
|
256
|
-
) {
|
|
257
|
-
actionNodes.push(
|
|
258
|
-
h(
|
|
259
|
-
ElButton,
|
|
260
|
-
{
|
|
261
|
-
key: `exec-${row.id}`,
|
|
262
|
-
type: "success",
|
|
263
|
-
link: true,
|
|
264
|
-
icon: Promotion,
|
|
265
|
-
class: "job-action job-action--exec",
|
|
266
|
-
onClick: () => handleExec(row.id, row.name)
|
|
267
|
-
},
|
|
268
|
-
() => t("system.base.job.action.execute")
|
|
269
|
-
)
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (BUTTONS.value["base:job:log"]) {
|
|
274
|
-
actionNodes.push(
|
|
275
|
-
h(
|
|
276
|
-
ElButton,
|
|
277
|
-
{
|
|
278
|
-
key: `log-${row.id}`,
|
|
279
|
-
type: "primary",
|
|
280
|
-
link: true,
|
|
281
|
-
icon: Tickets,
|
|
282
|
-
class: "job-action job-action--log",
|
|
283
|
-
onClick: () => handleOpenBaseJob(row.id, row.name)
|
|
284
|
-
},
|
|
285
|
-
() => t("system.base.job.action.log")
|
|
286
|
-
)
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (!actionNodes.length) return "--";
|
|
291
|
-
return h(
|
|
292
|
-
"div",
|
|
293
|
-
{
|
|
294
|
-
class: "job-operation",
|
|
295
|
-
key: `job-operation-${row.id}`
|
|
296
|
-
},
|
|
297
|
-
actionNodes
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
175
|
/** 定时任务表单字段配置。 */
|
|
302
176
|
const formFields = computed<ProFormField[]>(() => [
|
|
303
177
|
{
|
|
@@ -372,7 +246,86 @@ const columns = computed<ColumnProps[]>(() => [
|
|
|
372
246
|
{
|
|
373
247
|
prop: "operation",
|
|
374
248
|
label: t("common.field.action"),
|
|
375
|
-
|
|
249
|
+
cellType: "actions",
|
|
250
|
+
actions: [
|
|
251
|
+
{
|
|
252
|
+
label: t("common.action.edit"),
|
|
253
|
+
type: "primary",
|
|
254
|
+
link: true,
|
|
255
|
+
icon: EditPen,
|
|
256
|
+
hidden: () => !BUTTONS.value["base:job:update"],
|
|
257
|
+
onClick: scope => handleOpenDialog((scope.row as BaseJob).id)
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
label: t("common.action.delete"),
|
|
261
|
+
type: "danger",
|
|
262
|
+
link: true,
|
|
263
|
+
icon: Delete,
|
|
264
|
+
hidden: () => !BUTTONS.value["base:job:delete"],
|
|
265
|
+
onClick: scope => handleDelete(scope.row as BaseJob)
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
label: t("system.base.job.action.start"),
|
|
269
|
+
type: "primary",
|
|
270
|
+
link: true,
|
|
271
|
+
icon: VideoPlay,
|
|
272
|
+
hidden: scope => {
|
|
273
|
+
const row = scope.row as BaseJob;
|
|
274
|
+
return (
|
|
275
|
+
row.status !== Status.STATUS_ENABLE ||
|
|
276
|
+
(row.entry_id !== undefined && row.entry_id !== 0) ||
|
|
277
|
+
!BUTTONS.value["base:job:start"]
|
|
278
|
+
);
|
|
279
|
+
},
|
|
280
|
+
onClick: scope => {
|
|
281
|
+
const row = scope.row as BaseJob;
|
|
282
|
+
return handleStart(row.id, row.name);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
label: t("system.base.job.action.stop"),
|
|
287
|
+
type: "warning",
|
|
288
|
+
link: true,
|
|
289
|
+
icon: VideoPause,
|
|
290
|
+
hidden: scope => {
|
|
291
|
+
const row = scope.row as BaseJob;
|
|
292
|
+
return row.status !== Status.STATUS_ENABLE || !(row.entry_id > 0) || !BUTTONS.value["base:job:stop"];
|
|
293
|
+
},
|
|
294
|
+
onClick: scope => {
|
|
295
|
+
const row = scope.row as BaseJob;
|
|
296
|
+
return handleStop(row.id, row.name);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
label: t("system.base.job.action.execute"),
|
|
301
|
+
type: "success",
|
|
302
|
+
link: true,
|
|
303
|
+
icon: Promotion,
|
|
304
|
+
hidden: scope => {
|
|
305
|
+
const row = scope.row as BaseJob;
|
|
306
|
+
return (
|
|
307
|
+
row.status !== Status.STATUS_ENABLE ||
|
|
308
|
+
(row.entry_id !== undefined && row.entry_id !== 0) ||
|
|
309
|
+
!BUTTONS.value["base:job:exec"]
|
|
310
|
+
);
|
|
311
|
+
},
|
|
312
|
+
onClick: scope => {
|
|
313
|
+
const row = scope.row as BaseJob;
|
|
314
|
+
return handleExec(row.id, row.name);
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
label: t("system.base.job.action.log"),
|
|
319
|
+
type: "info",
|
|
320
|
+
link: true,
|
|
321
|
+
icon: Tickets,
|
|
322
|
+
hidden: () => !BUTTONS.value["base:job:log"],
|
|
323
|
+
onClick: scope => {
|
|
324
|
+
const row = scope.row as BaseJob;
|
|
325
|
+
return handleOpenBaseJob(row.id, row.name);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
]
|
|
376
329
|
}
|
|
377
330
|
]);
|
|
378
331
|
|
|
@@ -608,32 +561,3 @@ function handleOpenBaseJob(id: number, name: string) {
|
|
|
608
561
|
navigateTo(router, "/base/job/log", { jobId: id, title: t("system.base.job.title.log", { name }) });
|
|
609
562
|
}
|
|
610
563
|
</script>
|
|
611
|
-
|
|
612
|
-
<style scoped>
|
|
613
|
-
.job-operation {
|
|
614
|
-
display: flex;
|
|
615
|
-
flex-wrap: wrap;
|
|
616
|
-
gap: 4px 10px;
|
|
617
|
-
align-items: center;
|
|
618
|
-
white-space: nowrap;
|
|
619
|
-
}
|
|
620
|
-
.job-action {
|
|
621
|
-
margin-left: 0;
|
|
622
|
-
font-weight: 500;
|
|
623
|
-
}
|
|
624
|
-
.job-action:deep(.el-icon) {
|
|
625
|
-
margin-right: 4px;
|
|
626
|
-
}
|
|
627
|
-
.job-action--start {
|
|
628
|
-
--el-button-text-color: var(--el-color-primary);
|
|
629
|
-
}
|
|
630
|
-
.job-action--stop {
|
|
631
|
-
--el-button-text-color: var(--el-color-warning);
|
|
632
|
-
}
|
|
633
|
-
.job-action--exec {
|
|
634
|
-
--el-button-text-color: var(--el-color-success);
|
|
635
|
-
}
|
|
636
|
-
.job-action--log {
|
|
637
|
-
--el-button-text-color: var(--el-color-info);
|
|
638
|
-
}
|
|
639
|
-
</style>
|
|
@@ -94,7 +94,11 @@ import {
|
|
|
94
94
|
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_login_policy";
|
|
95
95
|
|
|
96
96
|
/** 登录策略表单状态,初始化密码仅在提交时转换为一次性密码密文。 */
|
|
97
|
-
interface BaseLoginPolicyFormState extends Omit<BaseLoginPolicyForm, "initial_password"> {
|
|
97
|
+
interface BaseLoginPolicyFormState extends Omit<BaseLoginPolicyForm, "initial_password" | "tenant_id" | "user_id"> {
|
|
98
|
+
/** 租户作用域目标,未选择时保持未定义。 */
|
|
99
|
+
tenant_id?: number;
|
|
100
|
+
/** 用户作用域目标,未选择时保持未定义。 */
|
|
101
|
+
user_id?: number;
|
|
98
102
|
/** 初始化密码明文只保留在当前表单,留空表示不修改已有值。 */
|
|
99
103
|
initial_password: string;
|
|
100
104
|
}
|
|
@@ -370,13 +374,13 @@ watch(
|
|
|
370
374
|
() => formData.scope_type,
|
|
371
375
|
value => {
|
|
372
376
|
if (value === BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_GLOBAL) {
|
|
373
|
-
formData.tenant_id =
|
|
374
|
-
formData.user_id =
|
|
377
|
+
formData.tenant_id = undefined;
|
|
378
|
+
formData.user_id = undefined;
|
|
375
379
|
userOptions.value = [];
|
|
376
380
|
return;
|
|
377
381
|
}
|
|
378
382
|
if (value === BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_TENANT) {
|
|
379
|
-
formData.user_id =
|
|
383
|
+
formData.user_id = undefined;
|
|
380
384
|
userOptions.value = [];
|
|
381
385
|
}
|
|
382
386
|
}
|
|
@@ -399,8 +403,8 @@ function defaultForm(): BaseLoginPolicyFormState {
|
|
|
399
403
|
return {
|
|
400
404
|
id: 0,
|
|
401
405
|
scope_type: BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_GLOBAL,
|
|
402
|
-
tenant_id:
|
|
403
|
-
user_id:
|
|
406
|
+
tenant_id: undefined,
|
|
407
|
+
user_id: undefined,
|
|
404
408
|
max_failed_attempts: 5,
|
|
405
409
|
lock_duration_minutes: 15,
|
|
406
410
|
allow_concurrent_login: false,
|
|
@@ -464,6 +468,8 @@ async function handleSubmit() {
|
|
|
464
468
|
|
|
465
469
|
const baseLoginPolicy: BaseLoginPolicyForm = {
|
|
466
470
|
...formData,
|
|
471
|
+
tenant_id: formData.tenant_id ?? 0,
|
|
472
|
+
user_id: formData.user_id ?? 0,
|
|
467
473
|
initial_password: formData.initial_password
|
|
468
474
|
? await encryptPassword(formData.initial_password, PASSWORD_CRYPTO_SCENE.PASSWORD_CRYPTO_SCENE_CONFIGURE_PASSWORD_POLICY)
|
|
469
475
|
: undefined
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
<template v-if="row.rule_type === 'MASK'">
|
|
57
57
|
<ParameterNumber v-model="row.params.keep_first" :label="t('system.base.redact_rule.parameter.keep_first')" />
|
|
58
58
|
<ParameterNumber v-model="row.params.keep_last" :label="t('system.base.redact_rule.parameter.keep_last')" />
|
|
59
|
+
<ParameterNumber v-model="row.params.min_mask" :label="t('system.base.redact_rule.parameter.min_mask')" />
|
|
59
60
|
<ParameterText v-model="row.params.mask_char" :label="t('system.base.redact_rule.parameter.mask_char')" />
|
|
60
61
|
</template>
|
|
61
62
|
<template v-else-if="row.rule_type === 'EMAIL'">
|
|
@@ -119,6 +120,7 @@ import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
|
|
|
119
120
|
interface RuleParams {
|
|
120
121
|
keep_first?: number;
|
|
121
122
|
keep_last?: number;
|
|
123
|
+
min_mask?: number;
|
|
122
124
|
mask_char?: string;
|
|
123
125
|
keep_local_first?: number;
|
|
124
126
|
mask_domain?: boolean;
|
|
@@ -260,7 +262,7 @@ function handleServiceChange(serviceName?: string) { form.service_name = service
|
|
|
260
262
|
/** API 变更后重新加载返回字段。 */
|
|
261
263
|
async function handleApiChange(apiId?: number) { const api = interfaceOptions.value.find(item => item.id === apiId); form.service_name = api?.service_name ?? form.service_name; form.operation = api?.operation ?? ""; form.field_rows = []; if (apiId !== undefined) await loadResponseFields(apiId); }
|
|
262
264
|
/** 加载 API 返回字段并初始化表格行。 */
|
|
263
|
-
async function loadResponseFields(apiId: number) { const api = apiCatalog.value.find(item => item.id === apiId); if (!api) return; const doc = await
|
|
265
|
+
async function loadResponseFields(apiId: number) { const api = apiCatalog.value.find(item => item.id === apiId); if (!api) return; const doc = await defBaseRedactOutputPolicyService.GetBaseRedactOutputFieldDoc({ api_id: apiId }); const rows: OutputFieldRow[] = []; for (const response of doc.responses ?? []) { if (response.body) collectFields(response.body, "", "", rows, true); } form.field_rows = [...new Map(rows.map(item => [String(item.value), item])).values()]; }
|
|
264
266
|
/** 递归收集可配置的返回叶子字段。 */
|
|
265
267
|
function collectFields(schema: BaseApiDocSchema, parentRef: string, parentPath: string, result: OutputFieldRow[], root: boolean) { const schemaRef = normalizeRef(schema.ref); const messageRef = schemaRef || parentRef; const startsNewMessage = Boolean(schemaRef && parentRef && schemaRef !== parentRef); const rootContainer = root || (!parentRef && (schema.name === "body" || schema.name === "body[]")); const path = rootContainer || startsNewMessage ? "" : schema.name ? (parentPath ? `${parentPath}.${schema.name}` : schema.name) : parentPath; if (!schema.children?.length) { const fieldName = path.split(".").pop()?.toLowerCase() ?? ""; if (path && !OUTPUT_SYSTEM_FIELD_NAMES.has(fieldName)) result.push(createFieldRow({ label: `${messageRef}.${path}${schema.description ? ` (${schema.description})` : ""}`, value: `${messageRef}\u0000${path}`, message_ref: messageRef, field_path: path, description: schema.description })); return; } for (const child of schema.children) collectFields(child, messageRef, path, result, false); }
|
|
266
268
|
/** 规范化 OpenAPI 引用名称。 */
|
|
@@ -274,7 +276,7 @@ async function submit() { const valid = await dialogRef.value?.validate(); if (!
|
|
|
274
276
|
/** 将表格行转换为出库策略请求。 */
|
|
275
277
|
function buildPayload(row: OutputFieldRow): BaseRedactOutputPolicyForm { syncRowParams(row); return { id: row.id, operation: form.operation, service_name: form.service_name, message_ref: row.message_ref, field_path: row.field_path, mode: row.mode, rule_id: row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE ? row.rule_id ?? 0 : 0, rule_params: row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE ? row.rule_params : "{}", status: form.status, remark: form.remark }; }
|
|
276
278
|
/** 将已保存的策略回填到对应字段行。 */
|
|
277
|
-
function applyPolicy(row: OutputFieldRow, data: BaseRedactOutputPolicyForm) { row.id = data.id; row.mode = data.mode || BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL; row.rule_id = data.rule_id || undefined; row.rule_params = data.rule_params; if (row.rule_id) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); row.rule_type = rule?.rule_type ?? ""; row.params = parseParams(row.rule_type, row.rule_params); } }
|
|
279
|
+
function applyPolicy(row: OutputFieldRow, data: BaseRedactOutputPolicyForm) { row.id = data.id; row.mode = data.mode || BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL; row.rule_id = data.rule_id || undefined; row.rule_params = data.rule_params; if (row.rule_id) { const rule = ruleCatalog.value.find(item => item.id === row.rule_id); row.rule_type = rule?.rule_type ?? ""; row.params = rule ? parseParams(row.rule_type, row.rule_params, rule.rule) : {}; } }
|
|
278
280
|
/** 处理行模式变更。 */
|
|
279
281
|
function handleModeChange(row: OutputTableRow) { if (row.mode !== BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE) { row.rule_id = undefined; row.rule_type = ""; row.params = {}; row.rule_params = "{}"; } }
|
|
280
282
|
/** 处理行规则变更。 */
|
|
@@ -285,7 +287,7 @@ function syncRowParams(row: OutputFieldRow) { const rule = ruleCatalog.value.fin
|
|
|
285
287
|
function isConfiguredRow(row: OutputFieldRow) { return row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_HIDE || row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL || (row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE && Boolean(row.rule_id)); }
|
|
286
288
|
/** 判断字段表格是否至少配置了一行。 */
|
|
287
289
|
function isConfiguredRowList(rows: OutputFieldRow[] | undefined) { return Boolean(rows?.some(isConfiguredRow)); }
|
|
288
|
-
/**
|
|
290
|
+
/** 判断字段行是否使用脱敏规则。 */
|
|
289
291
|
function isRuleRow(row: OutputTableRow) { return row.mode === BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_RULE && Boolean(row.rule_id); }
|
|
290
292
|
/** 格式化 API 下拉项。 */
|
|
291
293
|
function apiLabel(api: BaseApi) { return api.desc && api.desc !== api.operation ? `${api.desc}(${api.operation})` : api.operation; }
|
|
@@ -293,10 +295,20 @@ function apiLabel(api: BaseApi) { return api.desc && api.desc !== api.operation
|
|
|
293
295
|
function isGetApi(api: BaseApi) { return api.method.toUpperCase() === "GET"; }
|
|
294
296
|
/** 格式化服务下拉项。 */
|
|
295
297
|
function serviceLabel(serviceName: string) { const service = apiCatalog.value.find(item => item.service_name === serviceName); return service?.service_desc && service.service_desc !== serviceName ? `${service.service_desc}(${serviceName})` : serviceName; }
|
|
296
|
-
/**
|
|
297
|
-
function parseParams(ruleType: string, raw: string
|
|
298
|
-
|
|
299
|
-
|
|
298
|
+
/** 解析规则参数,空策略参数时回退到数据库规则参数。 */
|
|
299
|
+
function parseParams(ruleType: string, raw: string, fallbackRaw = ""): RuleParams {
|
|
300
|
+
const candidates = fallbackRaw && fallbackRaw !== raw ? [raw, fallbackRaw] : [raw];
|
|
301
|
+
for (const candidate of candidates) {
|
|
302
|
+
try {
|
|
303
|
+
const value = JSON.parse(candidate) as Record<string, RuleParams>;
|
|
304
|
+
const params = value[ruleType.toLowerCase()];
|
|
305
|
+
if (params && typeof params === "object" && !Array.isArray(params)) return params;
|
|
306
|
+
} catch {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return {};
|
|
311
|
+
}
|
|
300
312
|
/** 创建空的出库字段行。 */
|
|
301
313
|
function createFieldRow(option: Pick<OutputFieldRow, "label" | "value" | "message_ref" | "field_path"> & { description?: string }): OutputFieldRow { return { ...option, id: 0, mode: BaseRedactOutputPolicyMode.BASE_REDACT_OUTPUT_POLICY_MODE_FULL, rule_id: undefined, rule_type: "", params: {}, rule_params: "{}" }; }
|
|
302
314
|
/** 创建默认出库表单。 */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="table-box">
|
|
3
|
-
<ProTable ref="table" row-key="id" :columns="columns" :request-api="requestTable" />
|
|
3
|
+
<ProTable ref="table" row-key="id" :columns="columns" :header-actions="headerActions" :request-api="requestTable" />
|
|
4
4
|
<FormDialog
|
|
5
5
|
v-model="dialog.visible"
|
|
6
6
|
ref="dialogRef"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
>
|
|
15
15
|
<template #rule>
|
|
16
16
|
<div class="rule-editor">
|
|
17
|
-
<div class="rule-description">{{
|
|
17
|
+
<div v-if="form.remark" class="rule-description">{{ form.remark }}</div>
|
|
18
18
|
<div v-if="form.rule_type === 'MASK'" class="parameter-grid">
|
|
19
19
|
<div class="parameter-item">
|
|
20
20
|
<span>{{ t("system.base.redact_rule.parameter.keep_first") }}</span>
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
<span>{{ t("system.base.redact_rule.parameter.keep_last") }}</span>
|
|
25
25
|
<el-input-number v-model="form.params.keep_last" :min="0" :precision="0" controls-position="right" />
|
|
26
26
|
</div>
|
|
27
|
+
<div class="parameter-item">
|
|
28
|
+
<span>{{ t("system.base.redact_rule.parameter.min_mask") }}</span>
|
|
29
|
+
<el-input-number v-model="form.params.min_mask" :min="0" :precision="0" controls-position="right" />
|
|
30
|
+
</div>
|
|
27
31
|
<div class="parameter-item">
|
|
28
32
|
<span>{{ t("system.base.redact_rule.parameter.mask_char") }}</span>
|
|
29
33
|
<el-input v-model="form.params.mask_char" maxlength="8" />
|
|
@@ -109,13 +113,14 @@
|
|
|
109
113
|
<script setup lang="ts">
|
|
110
114
|
import { computed, reactive, ref } from "vue";
|
|
111
115
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
112
|
-
import {
|
|
113
|
-
import
|
|
116
|
+
import type { FormRules } from "element-plus";
|
|
117
|
+
import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
|
|
118
|
+
import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
114
119
|
import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
|
|
115
120
|
import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
|
|
116
121
|
import type { ProFormField } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
117
122
|
import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
|
|
118
|
-
import { buildPageRequest } from "@liujitcn/kratos-admin-core/table";
|
|
123
|
+
import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
|
|
119
124
|
import { t } from "@liujitcn/kratos-admin-core";
|
|
120
125
|
import { defBaseRedactRuleService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_redact_rule";
|
|
121
126
|
import type { BaseRedactRule, BaseRedactRuleForm, PageBaseRedactRuleRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_redact_rule";
|
|
@@ -124,6 +129,7 @@ import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
|
|
|
124
129
|
interface RuleParams {
|
|
125
130
|
keep_first?: number;
|
|
126
131
|
keep_last?: number;
|
|
132
|
+
min_mask?: number;
|
|
127
133
|
mask_char?: string;
|
|
128
134
|
keep_local_first?: number;
|
|
129
135
|
mask_domain?: boolean;
|
|
@@ -152,18 +158,21 @@ const statusOptions = computed(() => [
|
|
|
152
158
|
{ label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
|
|
153
159
|
{ label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
|
|
154
160
|
]);
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
/** 运行时支持的规则类型选项。 */
|
|
162
|
+
const ruleTypeOptions = ["MASK", "EMAIL", "REGEX", "TRUNCATE", "HASH", "UUID", "IP", "URL", "FIXED_LENGTH"].map(value => ({ label: value, value }));
|
|
157
163
|
const fields = computed<ProFormField[]>(() => [
|
|
158
|
-
{ prop: "code", label: t("system.base.redact_rule.field.code"), component: "input", props: { disabled:
|
|
159
|
-
{ prop: "name", label: t("system.base.redact_rule.field.name"), component: "input", props: { disabled:
|
|
160
|
-
{ prop: "rule_type", label: t("system.base.redact_rule.field.rule_type"), component: "
|
|
164
|
+
{ prop: "code", label: t("system.base.redact_rule.field.code"), component: "input", props: { disabled: Boolean(form.id) } },
|
|
165
|
+
{ prop: "name", label: t("system.base.redact_rule.field.name"), component: "input", props: { disabled: Boolean(form.id) } },
|
|
166
|
+
{ prop: "rule_type", label: t("system.base.redact_rule.field.rule_type"), component: "select", options: ruleTypeOptions, props: { disabled: Boolean(form.id), onChange: handleRuleTypeChange } },
|
|
161
167
|
{ prop: "rule", label: t("system.base.redact_rule.field.parameters"), component: "slot", slotName: "rule", colSpan: 24 },
|
|
162
168
|
{ prop: "status", label: t("common.field.status"), component: "radio-group", options: statusOptions.value },
|
|
163
169
|
{ prop: "remark", label: t("common.field.remark"), component: "textarea" }
|
|
164
170
|
]);
|
|
165
171
|
|
|
166
|
-
const rules = computed(() => ({
|
|
172
|
+
const rules = computed<FormRules>(() => ({
|
|
173
|
+
code: [{ required: true, message: t("system.base.redact_rule.validation.code"), trigger: "blur" }],
|
|
174
|
+
name: [{ required: true, message: t("system.base.redact_rule.validation.name"), trigger: "blur" }],
|
|
175
|
+
rule_type: [{ required: true, message: t("system.base.redact_rule.validation.rule_type"), trigger: "change" }],
|
|
167
176
|
rule: [{ required: true, message: t("system.base.redact_rule.validation.parameters"), trigger: "change" }]
|
|
168
177
|
}));
|
|
169
178
|
|
|
@@ -190,36 +199,40 @@ const columns = computed<ColumnProps[]>(() => [
|
|
|
190
199
|
prop: "actions",
|
|
191
200
|
label: t("common.field.operation"),
|
|
192
201
|
cellType: "actions",
|
|
193
|
-
actions: [{ label: t("common.action.edit"), type: "primary", link: true, icon: EditPen, hidden: () => !BUTTONS.value["base:redact-rule:update"], onClick: scope => openDialog((scope.row as BaseRedactRule).id) }]
|
|
202
|
+
actions: [{ label: t("common.action.edit"), type: "primary", link: true, icon: EditPen, hidden: () => !BUTTONS.value["base:redact-rule:update"], onClick: scope => openDialog((scope.row as BaseRedactRule).id) }, { label: t("common.action.delete"), type: "danger", link: true, icon: Delete, hidden: () => !BUTTONS.value["base:redact-rule:delete"], onClick: scope => deleteItems(scope.row as BaseRedactRule) }]
|
|
194
203
|
}
|
|
195
204
|
]);
|
|
205
|
+
const headerActions = computed<HeaderActionProps[]>(() => [{ label: t("common.action.create"), type: "success", icon: CirclePlus, hidden: () => !BUTTONS.value["base:redact-rule:create"], onClick: () => openDialog() }, { label: t("common.action.delete"), type: "danger", icon: Delete, hidden: () => !BUTTONS.value["base:redact-rule:delete"], disabled: scope => !scope.selectedList.length, onClick: scope => deleteItems(scope.selectedList as BaseRedactRule[]) }]);
|
|
196
206
|
|
|
197
|
-
/**
|
|
207
|
+
/** 请求固定规则列表。 */
|
|
198
208
|
async function requestTable(params: PageBaseRedactRuleRequest) {
|
|
199
209
|
const data = await defBaseRedactRuleService.PageBaseRedactRule(buildPageRequest(params));
|
|
200
210
|
return { data: { list: data.base_redact_rules ?? [], total: data.total } };
|
|
201
211
|
}
|
|
202
212
|
|
|
203
|
-
/**
|
|
204
|
-
async function openDialog(id
|
|
213
|
+
/** 打开新增或编辑弹窗。 */
|
|
214
|
+
async function openDialog(id?: number) {
|
|
205
215
|
resetForm();
|
|
206
|
-
|
|
207
|
-
|
|
216
|
+
if (id !== undefined) {
|
|
217
|
+
const data = await defBaseRedactRuleService.GetBaseRedactRule({ id });
|
|
218
|
+
Object.assign(form, data, { params: parseRuleParams(data.rule_type, data.rule) });
|
|
219
|
+
}
|
|
220
|
+
dialog.titleKey = id !== undefined ? "common.action.edit_resource" : "common.action.create_resource";
|
|
208
221
|
dialog.visible = true;
|
|
209
222
|
}
|
|
210
223
|
|
|
211
|
-
/**
|
|
224
|
+
/** 重置规则编辑表单。 */
|
|
212
225
|
function resetForm() {
|
|
213
226
|
dialog.visible = false;
|
|
214
227
|
dialogRef.value?.resetFields();
|
|
215
228
|
Object.assign(form, defaultForm());
|
|
216
229
|
}
|
|
217
230
|
|
|
218
|
-
/**
|
|
231
|
+
/** 提交新增或编辑的规则。 */
|
|
219
232
|
async function submit() {
|
|
233
|
+
syncRule();
|
|
220
234
|
const valid = await dialogRef.value?.validate();
|
|
221
235
|
if (!valid) return;
|
|
222
|
-
syncRule();
|
|
223
236
|
const payload: BaseRedactRuleForm = {
|
|
224
237
|
id: form.id,
|
|
225
238
|
code: form.code,
|
|
@@ -229,13 +242,17 @@ async function submit() {
|
|
|
229
242
|
status: form.status,
|
|
230
243
|
remark: form.remark
|
|
231
244
|
};
|
|
232
|
-
|
|
233
|
-
|
|
245
|
+
if (form.id) {
|
|
246
|
+
await defBaseRedactRuleService.UpdateBaseRedactRule({ base_redact_rule: payload });
|
|
247
|
+
} else {
|
|
248
|
+
await defBaseRedactRuleService.CreateBaseRedactRule({ base_redact_rule: payload });
|
|
249
|
+
}
|
|
250
|
+
ElMessage.success(t(form.id ? "common.message.update_success" : "common.message.create_success", { resource: t("system.base.redact_rule.title") }));
|
|
234
251
|
resetForm();
|
|
235
252
|
table.value?.getTableList();
|
|
236
253
|
}
|
|
237
254
|
|
|
238
|
-
/**
|
|
255
|
+
/** 切换固定规则状态。 */
|
|
239
256
|
async function changeStatus(row: BaseRedactRule) {
|
|
240
257
|
const next = row.status === Status.STATUS_ENABLE ? Status.STATUS_DISABLE : Status.STATUS_ENABLE;
|
|
241
258
|
try {
|
|
@@ -257,46 +274,46 @@ async function changeStatus(row: BaseRedactRule) {
|
|
|
257
274
|
}
|
|
258
275
|
}
|
|
259
276
|
|
|
277
|
+
/** 删除未被引用的规则。 */
|
|
278
|
+
function deleteItems(selected?: BaseRedactRule | BaseRedactRule[] | number | string | Array<number | string>) {
|
|
279
|
+
const items = Array.isArray(selected) ? selected.filter((item): item is BaseRedactRule => typeof item === "object") : selected && typeof selected === "object" ? [selected] : [];
|
|
280
|
+
const ids = items.length ? items.map(item => item.id) : normalizeSelectedIds(selected as number | string | Array<number | string>);
|
|
281
|
+
if (!ids.length) {
|
|
282
|
+
ElMessage.warning(t("common.message.select_delete_item"));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
ElMessageBox.confirm(t("common.dialog.delete_selected", { resource: t("system.base.redact_rule.title") }), t("common.title.warning"), { type: "warning" }).then(async () => {
|
|
286
|
+
await defBaseRedactRuleService.DeleteBaseRedactRule({ id: ids.join(",") });
|
|
287
|
+
ElMessage.success(t("common.message.delete_success", { resource: t("system.base.redact_rule.title") }));
|
|
288
|
+
table.value?.getTableList();
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
260
292
|
/** 将参数表单同步为运行时规则 JSON。 */
|
|
261
293
|
function syncRule() {
|
|
262
|
-
|
|
294
|
+
if (!form.rule_type) {
|
|
295
|
+
form.rule = "";
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
263
298
|
form.rule = JSON.stringify({ [form.rule_type.toLowerCase()]: form.params }, null, 2);
|
|
264
299
|
}
|
|
265
300
|
|
|
266
|
-
/**
|
|
301
|
+
/** 切换规则类型时重置参数。 */
|
|
302
|
+
function handleRuleTypeChange(ruleType?: string) {
|
|
303
|
+
form.rule_type = ruleType ?? "";
|
|
304
|
+
form.params = {};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** 解析数据库规则 JSON 中当前类型的参数。 */
|
|
267
308
|
function parseRuleParams(ruleType: string, rawRule: string): RuleParams {
|
|
268
309
|
try {
|
|
269
310
|
const value = JSON.parse(rawRule) as Record<string, unknown>;
|
|
270
311
|
const params = value[ruleType.toLowerCase()];
|
|
271
312
|
if (params && typeof params === "object" && !Array.isArray(params)) return params as RuleParams;
|
|
272
313
|
} catch {
|
|
273
|
-
return
|
|
274
|
-
}
|
|
275
|
-
return defaultRuleParams(ruleType);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** 返回固定规则模板的默认参数。 */
|
|
279
|
-
function defaultRuleParams(ruleType: string): RuleParams {
|
|
280
|
-
switch (ruleType) {
|
|
281
|
-
case "MASK":
|
|
282
|
-
return { keep_first: 3, keep_last: 4, mask_char: "*" };
|
|
283
|
-
case "EMAIL":
|
|
284
|
-
return { keep_local_first: 2, mask_domain: false, mask_char: "*" };
|
|
285
|
-
case "REGEX":
|
|
286
|
-
return { pattern: "(?s).+", replacement: "[REDACTED]" };
|
|
287
|
-
case "TRUNCATE":
|
|
288
|
-
return { length: 10, suffix: "..." };
|
|
289
|
-
case "HASH":
|
|
290
|
-
return { algo: "SHA256" };
|
|
291
|
-
case "IP":
|
|
292
|
-
return { keep_octets: 2, mask_char: "x" };
|
|
293
|
-
case "URL":
|
|
294
|
-
return { mask_query: true, mask_char: "*" };
|
|
295
|
-
case "FIXED_LENGTH":
|
|
296
|
-
return { char: "X" };
|
|
297
|
-
default:
|
|
298
|
-
return {};
|
|
314
|
+
return {};
|
|
299
315
|
}
|
|
316
|
+
return {};
|
|
300
317
|
}
|
|
301
318
|
|
|
302
319
|
/** 返回规则参数的紧凑展示文本。 */
|
|
@@ -304,9 +321,9 @@ function ruleSummary(row: BaseRedactRule) {
|
|
|
304
321
|
return JSON.stringify(parseRuleParams(row.rule_type, row.rule));
|
|
305
322
|
}
|
|
306
323
|
|
|
307
|
-
/**
|
|
324
|
+
/** 返回规则表单的初始值。 */
|
|
308
325
|
function defaultForm(): RuleFormState {
|
|
309
|
-
return { id: 0, code: "", name: "", rule_type: "
|
|
326
|
+
return { id: 0, code: "", name: "", rule_type: "", rule: "", status: Status.STATUS_ENABLE, remark: "", params: {} };
|
|
310
327
|
}
|
|
311
328
|
</script>
|
|
312
329
|
|