@liujitcn/kratos-admin-system 0.0.61 → 0.0.64
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 +16 -1
- package/dist/package/declarations/src/api/base/v1/ai_provider.d.ts +7 -0
- package/dist/package/declarations/src/api/system/admin/v1/ai_provider.d.ts +18 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_api_rate_limit_policy.d.ts +19 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_message_provider.d.ts +21 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_message_template.d.ts +16 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_oauth_provider.d.ts +18 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_rate_limit_rule.d.ts +21 -0
- package/dist/package/declarations/src/components/api/ApiTransfer.vue.d.ts +35 -0
- package/dist/package/declarations/src/components/api/apiTransfer.d.ts +13 -0
- package/dist/package/declarations/src/components/i18n/DynamicI18nCell.vue.d.ts +2 -2
- package/dist/package/declarations/src/components/i18n/dynamicI18n.d.ts +3 -4
- package/dist/package/declarations/src/locales/generated.d.ts +900 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_message.d.ts +4 -0
- package/dist/package/declarations/src/rpc/base/v1/ai_provider.d.ts +22 -0
- package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +20 -0
- package/dist/package/declarations/src/rpc/google/protobuf/struct.d.ts +81 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +110 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_api_rate_limit_policy.d.ts +137 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +4 -23
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message.d.ts +4 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message_category.d.ts +19 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message_provider.d.ts +126 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_message_template.d.ts +99 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_oauth_provider.d.ts +133 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_rate_limit_rule.d.ts +144 -0
- package/dist/package/declarations/src/utils/persisted-message.d.ts +4 -0
- package/dist/package/declarations/src/utils/rateLimit.d.ts +24 -0
- package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +11 -2
- package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +9 -0
- package/dist/package/declarations/src/views/base/ai-provider/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/api-rate-limit-policy/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/message-provider/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/message-template/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/oauth-provider/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/rate-limit-rule/index.vue.d.ts +3 -0
- package/dist/package/src/api/base/v1/ai_provider.ts +22 -0
- package/dist/package/src/api/system/admin/v1/ai_provider.ts +50 -0
- package/dist/package/src/api/system/admin/v1/base_api_rate_limit_policy.ts +51 -0
- package/dist/package/src/api/system/admin/v1/base_message_provider.ts +57 -0
- package/dist/package/src/api/system/admin/v1/base_message_template.ts +44 -0
- package/dist/package/src/api/system/admin/v1/base_oauth_provider.ts +50 -0
- package/dist/package/src/api/system/admin/v1/base_rate_limit_rule.ts +58 -0
- package/dist/package/src/components/ai/Ai.vue +1 -1
- package/dist/package/src/components/api/ApiTransfer.vue +117 -0
- package/dist/package/src/components/api/apiTransfer.ts +27 -0
- package/dist/package/src/components/i18n/DynamicI18nCell.vue +8 -8
- package/dist/package/src/components/i18n/DynamicI18nEditor.vue +6 -3
- package/dist/package/src/components/i18n/dynamicI18n.ts +4 -5
- package/dist/package/src/components/tenant-project/TenantProjectManager.vue +2 -1
- package/dist/package/src/locales/en-US.json +229 -4
- package/dist/package/src/locales/ja-JP.json +229 -4
- package/dist/package/src/locales/zh-CN.json +229 -4
- package/dist/package/src/locales/zh-TW.json +229 -4
- package/dist/package/src/rpc/base/v1/ai_message.ts +7 -1
- package/dist/package/src/rpc/base/v1/ai_provider.ts +33 -0
- package/dist/package/src/rpc/base/v1/oauth.ts +16 -0
- package/dist/package/src/rpc/google/protobuf/struct.ts +80 -0
- package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +129 -0
- package/dist/package/src/rpc/system/admin/v1/base_api_rate_limit_policy.ts +160 -0
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +4 -24
- package/dist/package/src/rpc/system/admin/v1/base_message.ts +4 -4
- package/dist/package/src/rpc/system/admin/v1/base_message_category.ts +20 -0
- package/dist/package/src/rpc/system/admin/v1/base_message_provider.ts +150 -0
- package/dist/package/src/rpc/system/admin/v1/base_message_template.ts +117 -0
- package/dist/package/src/rpc/system/admin/v1/base_oauth_provider.ts +156 -0
- package/dist/package/src/rpc/system/admin/v1/base_rate_limit_rule.ts +167 -0
- package/dist/package/src/utils/persisted-message.ts +21 -0
- package/dist/package/src/utils/rateLimit.ts +140 -0
- package/dist/package/src/views/ai/chat/components/ChatPanel.vue +36 -4
- package/dist/package/src/views/ai/chat/components/XSender.vue +112 -1
- package/dist/package/src/views/ai/chat/index.vue +30 -1
- package/dist/package/src/views/base/ai-provider/index.vue +512 -0
- package/dist/package/src/views/base/api-rate-limit-policy/index.vue +428 -0
- package/dist/package/src/views/base/backup-management/archive-record/index.vue +2 -0
- package/dist/package/src/views/base/backup-management/archive-restore/index.vue +2 -1
- package/dist/package/src/views/base/backup-management/backup-record/index.vue +2 -0
- package/dist/package/src/views/base/backup-management/backup-restore/index.vue +2 -1
- package/dist/package/src/views/base/config/index.vue +12 -13
- package/dist/package/src/views/base/dict/index.vue +2 -3
- package/dist/package/src/views/base/dict/item.vue +2 -3
- package/dist/package/src/views/base/job/index.vue +2 -3
- package/dist/package/src/views/base/job/log.vue +3 -2
- package/dist/package/src/views/base/menu/index.vue +27 -83
- package/dist/package/src/views/base/message/index.vue +9 -3
- package/dist/package/src/views/base/message-category/index.vue +34 -3
- package/dist/package/src/views/base/message-provider/index.vue +164 -0
- package/dist/package/src/views/base/message-template/index.vue +105 -0
- package/dist/package/src/views/base/migration/index.vue +1 -1
- package/dist/package/src/views/base/oauth-client/index.vue +6 -6
- package/dist/package/src/views/base/oauth-provider/index.vue +274 -0
- package/dist/package/src/views/base/rate-limit-rule/index.vue +322 -0
- package/dist/package/src/views/base/redact-output-policy/index.vue +1 -1
- package/dist/package/src/views/base/redact-rule/index.vue +12 -2
- package/dist/package/src/views/base/redact-storage-policy/index.vue +4 -0
- package/dist/package/src/views/base/tenant/index.vue +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="table-box">
|
|
3
|
+
<ProTable ref="proTable" row-key="id" :columns="columns" :header-actions="headerActions" :request-api="requestTable" />
|
|
4
|
+
<FormDialog
|
|
5
|
+
v-model="dialog.visible"
|
|
6
|
+
ref="formDialogRef"
|
|
7
|
+
:title="t(dialog.editing ? 'common.action.edit' : 'common.action.create')"
|
|
8
|
+
width="min(1000px, calc(100vw - 32px))"
|
|
9
|
+
:model="formData"
|
|
10
|
+
:fields="formFields"
|
|
11
|
+
:rules="rules"
|
|
12
|
+
@confirm="handleSubmit"
|
|
13
|
+
@close="handleClose"
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { computed, onMounted, reactive, ref } from "vue";
|
|
20
|
+
import { ElMessage, ElMessageBox } from "element-plus";
|
|
21
|
+
import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
|
|
22
|
+
import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
|
|
23
|
+
import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
|
|
24
|
+
import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
25
|
+
import type { ProFormField, ProFormOption } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
26
|
+
import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
|
|
27
|
+
import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
|
|
28
|
+
import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
|
|
29
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
30
|
+
import { defBaseMessageCategoryService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_message_category";
|
|
31
|
+
import { defBaseMessageProviderService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_message_provider";
|
|
32
|
+
import { defBaseMessageTemplateService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_message_template";
|
|
33
|
+
import type { BaseMessageCategory } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_message_category";
|
|
34
|
+
import type { BaseMessageProvider } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_message_provider";
|
|
35
|
+
import type { BaseMessageTemplate, BaseMessageTemplateForm, PageBaseMessageTemplateRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_message_template";
|
|
36
|
+
import { MessageContentFormat } from "@liujitcn/kratos-admin-system/rpc/base/v1/notification";
|
|
37
|
+
|
|
38
|
+
defineOptions({ name: "BaseMessageTemplate", inheritAttrs: false });
|
|
39
|
+
|
|
40
|
+
interface ConfigItem { key: string; value: string; }
|
|
41
|
+
interface FormState extends Omit<BaseMessageTemplateForm, "category_id" | "provider_id"> {
|
|
42
|
+
category_id: number | undefined;
|
|
43
|
+
provider_id: number | undefined;
|
|
44
|
+
config_items: ConfigItem[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { BUTTONS } = useAuthButtons();
|
|
48
|
+
const { isDefaultTenant } = useTenantScope();
|
|
49
|
+
const proTable = ref<ProTableInstance>();
|
|
50
|
+
const formDialogRef = ref<InstanceType<typeof FormDialog>>();
|
|
51
|
+
const dialog = reactive({ visible: false, editing: false });
|
|
52
|
+
const formData = reactive<FormState>(defaultForm());
|
|
53
|
+
const categories = ref<BaseMessageCategory[]>([]);
|
|
54
|
+
const providers = ref<BaseMessageProvider[]>([]);
|
|
55
|
+
|
|
56
|
+
const contentFormatOptions = computed<ProFormOption[]>(() => [
|
|
57
|
+
{ label: t("system.base.message_template.content_format.plain_text"), value: MessageContentFormat.MESSAGE_CONTENT_FORMAT_PLAIN_TEXT },
|
|
58
|
+
{ label: t("system.base.message_template.content_format.safe_markdown"), value: MessageContentFormat.MESSAGE_CONTENT_FORMAT_SAFE_MARKDOWN },
|
|
59
|
+
{ label: t("system.base.message_template.content_format.rich_text"), value: MessageContentFormat.MESSAGE_CONTENT_FORMAT_RICH_TEXT }
|
|
60
|
+
]);
|
|
61
|
+
const categoryOptions = computed<ProFormOption[]>(() => categories.value.map(item => ({ label: item.name, value: item.id })));
|
|
62
|
+
const providerOptions = computed<ProFormOption[]>(() => providers.value.map(item => ({ label: item.name, value: item.id, disabled: item.status !== 1 })));
|
|
63
|
+
const rules = computed(() => ({ category_id: [{ required: true, message: t("system.base.message_template.placeholder.category"), trigger: "change" }], provider_id: [{ required: true, message: t("system.base.message_template.placeholder.provider"), trigger: "change" }], title: [{ required: true, message: t("system.base.message_template.placeholder.title"), trigger: "blur" }] }));
|
|
64
|
+
const formFields = computed<ProFormField[]>(() => [
|
|
65
|
+
{ prop: "category_id", label: t("system.base.message_template.field.category"), component: "select", options: categoryOptions.value, props: { disabled: dialog.editing, filterable: true } },
|
|
66
|
+
{ prop: "provider_id", label: t("system.base.message_template.field.provider"), component: "select", options: providerOptions.value, props: { disabled: dialog.editing, filterable: true } },
|
|
67
|
+
{ prop: "title", label: t("system.base.message_template.field.title"), component: "input", props: { maxlength: 200 } },
|
|
68
|
+
{ prop: "content", label: t("system.base.message_template.field.content"), component: "textarea", colSpan: 24, props: { maxlength: 20000, rows: 8, showWordLimit: true } },
|
|
69
|
+
{ prop: "content_format", label: t("system.base.message_template.field.content_format"), component: "select", options: contentFormatOptions.value },
|
|
70
|
+
{ prop: "config_items", label: t("system.base.message_template.field.config"), component: "kv-list", props: { keyInputProps: { maxlength: 128 } } }
|
|
71
|
+
]);
|
|
72
|
+
const columns = computed<ColumnProps[]>(() => [
|
|
73
|
+
{ type: "selection", width: 55 },
|
|
74
|
+
{ prop: "category_name", label: t("system.base.message_template.field.category"), minWidth: 150 },
|
|
75
|
+
{ prop: "provider_name", label: t("system.base.message_template.field.provider"), minWidth: 150 },
|
|
76
|
+
{ prop: "title", label: t("system.base.message_template.field.title"), minWidth: 180 },
|
|
77
|
+
{ prop: "content_format", label: t("system.base.message_template.field.content_format"), minWidth: 120, render: scope => contentFormatLabel((scope.row as BaseMessageTemplate).content_format) },
|
|
78
|
+
{ prop: "updated_at", label: t("common.field.updated_at"), minWidth: 180, align: "center" },
|
|
79
|
+
{ prop: "operation", label: t("common.field.operation"), cellType: "actions", actions: [
|
|
80
|
+
{ label: t("common.action.edit"), link: true, icon: EditPen, hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:message-template:update"], onClick: scope => openDialog((scope.row as BaseMessageTemplate).id) },
|
|
81
|
+
{ label: t("common.action.delete"), type: "danger", link: true, icon: Delete, hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:message-template:delete"], onClick: scope => handleDelete(scope.row as BaseMessageTemplate) }
|
|
82
|
+
] }
|
|
83
|
+
]);
|
|
84
|
+
const headerActions = computed<HeaderActionProps[]>(() => [
|
|
85
|
+
{ label: t("common.action.create"), type: "primary", icon: CirclePlus, hidden: !isDefaultTenant.value || !BUTTONS.value["base:message-template:create"], onClick: () => openDialog() },
|
|
86
|
+
{ label: t("common.action.delete"), type: "danger", icon: Delete, hidden: !isDefaultTenant.value || !BUTTONS.value["base:message-template:delete"], disabled: scope => !scope.isSelected, onClick: scope => handleDelete(scope.selectedListIds as number[]) }
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
onMounted(loadOptions);
|
|
90
|
+
|
|
91
|
+
function defaultForm(): FormState { return { id: 0, category_id: undefined, provider_id: undefined, title: "", content: "", content_format: MessageContentFormat.MESSAGE_CONTENT_FORMAT_PLAIN_TEXT, config: {}, config_items: [] }; }
|
|
92
|
+
async function loadOptions() {
|
|
93
|
+
const [categoryData, providerData] = await Promise.all([defBaseMessageCategoryService.OptionBaseMessageCategory({}), defBaseMessageProviderService.OptionBaseMessageProvider({})]);
|
|
94
|
+
categories.value = (categoryData.list ?? []).map(item => ({ id: Number(item.value), name: item.label } as BaseMessageCategory));
|
|
95
|
+
providers.value = (providerData.list ?? []).map(item => ({ id: Number(item.value), name: item.label, status: item.disabled ? 2 : 1 } as BaseMessageProvider));
|
|
96
|
+
}
|
|
97
|
+
async function requestTable(params: Record<string, unknown>) { const data = await defBaseMessageTemplateService.PageBaseMessageTemplate(buildPageRequest<PageBaseMessageTemplateRequest>(params as unknown as PageBaseMessageTemplateRequest)); return { data: { list: data.base_message_templates ?? [], total: data.total } }; }
|
|
98
|
+
async function openDialog(id?: number) { await loadOptions(); await formDialogRef.value?.open({ load: () => (id ? defBaseMessageTemplateService.GetBaseMessageTemplate({ id }) : undefined), commit: data => { Object.assign(formData, defaultForm(), data ?? {}); dialog.editing = Boolean(id); formData.config_items = configToItems(formData.config); } }); }
|
|
99
|
+
async function handleSubmit() { if (!(await formDialogRef.value?.validate())) return; const config = configItemsToMap(formData.config_items); if (!config) return; const payload = JSON.parse(JSON.stringify(formData)) as BaseMessageTemplateForm & { config_items?: ConfigItem[] }; delete payload.config_items; payload.config = config; if (payload.id) await defBaseMessageTemplateService.UpdateBaseMessageTemplate({ base_message_template: payload }); else await defBaseMessageTemplateService.CreateBaseMessageTemplate({ base_message_template: payload }); ElMessage.success(t("common.message.operation_success")); dialog.visible = false; await proTable.value?.getTableList(); }
|
|
100
|
+
async function handleDelete(value: BaseMessageTemplate | number | number[]) { const ids = (Array.isArray(value) ? value : typeof value === "object" ? [value.id] : normalizeSelectedIds(value)).join(","); if (!ids) return; await ElMessageBox.confirm(t("common.confirm.delete"), t("common.title.warning"), { type: "warning" }); await defBaseMessageTemplateService.DeleteBaseMessageTemplate({ id: ids }); await proTable.value?.getTableList(); ElMessage.success(t("common.message.operation_success")); }
|
|
101
|
+
function handleClose() { Object.assign(formData, defaultForm()); formDialogRef.value?.resetFields(); }
|
|
102
|
+
function contentFormatLabel(value: MessageContentFormat) { return contentFormatOptions.value.find(item => item.value === value)?.label ?? ""; }
|
|
103
|
+
function configToItems(config: Record<string, any> | undefined): ConfigItem[] { return Object.entries(config ?? {}).map(([key, value]) => ({ key, value: typeof value === "string" ? value : JSON.stringify(value) })); }
|
|
104
|
+
function configItemsToMap(items: ConfigItem[]): Record<string, unknown> | undefined { const result: Record<string, unknown> = {}; for (const item of items) { const key = item.key.trim(); if (!key || Object.prototype.hasOwnProperty.call(result, key)) { ElMessage.error(t("system.base.message_template.message.config_invalid")); return undefined; } const value = item.value.trim(); if (!value) { result[key] = ""; continue; } try { result[key] = JSON.parse(value); } catch { result[key] = value; } } return result; }
|
|
105
|
+
</script>
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<strong>{{ history.version }}</strong>
|
|
56
56
|
</div>
|
|
57
57
|
<div class="migration-list-item__data-source">
|
|
58
|
-
{{ history.module || t("system.base.migration.value.default_module") }} · {{ history.data_source || "
|
|
58
|
+
{{ history.module || t("system.base.migration.value.default_module") }} · {{ history.data_source || t("system.base.migration.value.default_source") }}
|
|
59
59
|
</div>
|
|
60
60
|
<time :datetime="history.created_at">{{ formatDateTime(history.created_at) }}</time>
|
|
61
61
|
</button>
|
|
@@ -355,10 +355,10 @@ function handleDelete(selected?: number | string | Array<number | string> | Oaut
|
|
|
355
355
|
return;
|
|
356
356
|
}
|
|
357
357
|
const resource = rowList.length
|
|
358
|
-
? `${t("system.base.oauth_client.resource")}
|
|
358
|
+
? `${t("system.base.oauth_client.resource")}: ${rowList
|
|
359
359
|
.map(item => item.client_name)
|
|
360
360
|
.filter(Boolean)
|
|
361
|
-
.join("
|
|
361
|
+
.join(", ")}`
|
|
362
362
|
: t("system.base.oauth_client.resource");
|
|
363
363
|
ElMessageBox.confirm(t("common.dialog.delete_selected", { resource }), t("common.title.warning"), {
|
|
364
364
|
type: "warning",
|
|
@@ -382,10 +382,10 @@ async function handleCopyCredentials(row: OauthClient) {
|
|
|
382
382
|
);
|
|
383
383
|
const credentials = await defOauthClientService.RotateOauthClientCredentials({ id: row.id });
|
|
384
384
|
const content = [
|
|
385
|
-
`${t("system.base.oauth_client.field.client_id")}
|
|
386
|
-
`${t("system.base.oauth_client.field.client_secret")}
|
|
387
|
-
`${t("system.base.oauth_client.field.crypto_type")}
|
|
388
|
-
`${t("system.base.oauth_client.field.crypto_key")}
|
|
385
|
+
`${t("system.base.oauth_client.field.client_id")}: ${credentials.client_id}`,
|
|
386
|
+
`${t("system.base.oauth_client.field.client_secret")}: ${credentials.client_secret}`,
|
|
387
|
+
`${t("system.base.oauth_client.field.crypto_type")}: ${formatCrypto(credentials.crypto_type)}`,
|
|
388
|
+
`${t("system.base.oauth_client.field.crypto_key")}: ${credentials.crypto_key}`
|
|
389
389
|
].join("\n");
|
|
390
390
|
await copyText(content);
|
|
391
391
|
ElMessage.success(t("core.clipboard.success"));
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="table-box">
|
|
3
|
+
<ProTable ref="proTable" row-key="id" :columns="columns" :header-actions="headerActions" :request-api="requestTable" />
|
|
4
|
+
<FormDialog
|
|
5
|
+
v-model="dialog.visible"
|
|
6
|
+
ref="formDialogRef"
|
|
7
|
+
:title="t(dialog.editing ? 'common.action.edit' : 'common.action.create')"
|
|
8
|
+
class="oauth-provider-dialog"
|
|
9
|
+
width="min(1280px, calc(100vw - 32px))"
|
|
10
|
+
top="3vh"
|
|
11
|
+
label-width="9em"
|
|
12
|
+
:col-span="12"
|
|
13
|
+
:gutter="24"
|
|
14
|
+
:model="formData"
|
|
15
|
+
:fields="formFields"
|
|
16
|
+
:rules="rules"
|
|
17
|
+
@confirm="handleSubmit"
|
|
18
|
+
@close="handleClose"
|
|
19
|
+
>
|
|
20
|
+
<template #nameI18ns>
|
|
21
|
+
<DynamicI18nEditor v-model="nameI18nValues" :source="formData.name" :maxlength="50" />
|
|
22
|
+
</template>
|
|
23
|
+
<template #descriptionI18ns>
|
|
24
|
+
<DynamicI18nEditor v-model="descriptionI18nValues" :source="formData.description" :maxlength="255" multiline />
|
|
25
|
+
</template>
|
|
26
|
+
</FormDialog>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import { computed, reactive, ref } from "vue";
|
|
32
|
+
import { ElMessage, ElMessageBox } from "element-plus";
|
|
33
|
+
import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
|
|
34
|
+
import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
|
|
35
|
+
import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
|
|
36
|
+
import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
37
|
+
import type { ProFormField, ProFormOption } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
38
|
+
import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
|
|
39
|
+
import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
|
|
40
|
+
import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
|
|
41
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
42
|
+
import { defBaseOauthProviderService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_oauth_provider";
|
|
43
|
+
import { loadEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_language";
|
|
44
|
+
import type {
|
|
45
|
+
BaseOauthProvider,
|
|
46
|
+
BaseOauthProviderForm,
|
|
47
|
+
PageBaseOauthProviderRequest
|
|
48
|
+
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_oauth_provider";
|
|
49
|
+
import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
|
|
50
|
+
import DynamicI18nEditor from "@liujitcn/kratos-admin-system/components/i18n/DynamicI18nEditor.vue";
|
|
51
|
+
import {
|
|
52
|
+
normalizeDynamicI18ns,
|
|
53
|
+
serializeDynamicI18ns,
|
|
54
|
+
type DynamicI18nValue
|
|
55
|
+
} from "@liujitcn/kratos-admin-system/components/i18n/dynamicI18n";
|
|
56
|
+
|
|
57
|
+
defineOptions({ name: "BaseOauthProvider", inheritAttrs: false });
|
|
58
|
+
|
|
59
|
+
const { BUTTONS } = useAuthButtons();
|
|
60
|
+
const { isDefaultTenant } = useTenantScope();
|
|
61
|
+
const proTable = ref<ProTableInstance>();
|
|
62
|
+
const formDialogRef = ref<InstanceType<typeof FormDialog>>();
|
|
63
|
+
const dialog = reactive({ editing: false, visible: false });
|
|
64
|
+
const formData = reactive<OauthProviderFormState>(defaultForm());
|
|
65
|
+
const nameI18nValues = ref<DynamicI18nValue[]>(normalizeDynamicI18ns(undefined));
|
|
66
|
+
const descriptionI18nValues = ref<DynamicI18nValue[]>(normalizeDynamicI18ns(undefined));
|
|
67
|
+
|
|
68
|
+
/** OAuth Provider 个性化配置键值编辑项。 */
|
|
69
|
+
interface OauthProviderConfigItem {
|
|
70
|
+
key: string;
|
|
71
|
+
value: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** OAuth Provider 页面表单状态。 */
|
|
75
|
+
interface OauthProviderFormState extends BaseOauthProviderForm {
|
|
76
|
+
config_items: OauthProviderConfigItem[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const statusOptions = computed<ProFormOption[]>(() => [
|
|
80
|
+
{ label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
|
|
81
|
+
{ label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const rules = computed(() => ({
|
|
85
|
+
provider: [{ required: true, message: t("system.base.oauth_provider.placeholder.provider"), trigger: "blur" }],
|
|
86
|
+
name: [{ required: true, message: t("system.base.oauth_provider.placeholder.name"), trigger: "blur" }],
|
|
87
|
+
icon: [{ required: true, message: t("system.base.oauth_provider.placeholder.icon"), trigger: "blur" }],
|
|
88
|
+
client_id: [{ required: true, message: t("system.base.oauth_provider.placeholder.client_id"), trigger: "blur" }],
|
|
89
|
+
client_secret: [
|
|
90
|
+
{
|
|
91
|
+
validator: (_rule: unknown, value: string, callback: (error?: Error) => void) => {
|
|
92
|
+
if (!formData.id && !value) callback(new Error(t("system.base.oauth_provider.placeholder.client_secret")));
|
|
93
|
+
else callback();
|
|
94
|
+
},
|
|
95
|
+
trigger: "blur"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const formFields = computed<ProFormField[]>(() => [
|
|
101
|
+
{ prop: "provider", label: t("system.base.oauth_provider.field.provider"), component: "input", colSpan: 12, props: { disabled: dialog.editing, maxlength: 32, placeholder: t("system.base.oauth_provider.placeholder.provider") } },
|
|
102
|
+
{ prop: "name", suffixSlotName: "nameI18ns", label: t("system.base.oauth_provider.field.name"), component: "input", colSpan: 12, props: { maxlength: 50, placeholder: t("system.base.oauth_provider.placeholder.name") } },
|
|
103
|
+
{ prop: "description", suffixSlotName: "descriptionI18ns", label: t("system.base.oauth_provider.field.description"), component: "input", colSpan: 24, props: { maxlength: 255, placeholder: t("system.base.oauth_provider.placeholder.description") } },
|
|
104
|
+
{ prop: "icon", label: t("system.base.oauth_provider.field.icon"), component: "input", colSpan: 12, props: { maxlength: 255, placeholder: t("system.base.oauth_provider.placeholder.icon") } },
|
|
105
|
+
{ prop: "client_id", label: t("system.base.oauth_provider.field.client_id"), component: "input", colSpan: 12, props: { maxlength: 255, placeholder: t("system.base.oauth_provider.placeholder.client_id") } },
|
|
106
|
+
{ prop: "client_secret", label: t("system.base.oauth_provider.field.client_secret"), component: "input", colSpan: 12, props: { type: "password", showPassword: true, maxlength: 512, placeholder: dialog.editing ? t("system.base.oauth_provider.placeholder.keep_secret") : t("system.base.oauth_provider.placeholder.client_secret") } },
|
|
107
|
+
{ prop: "redirect_uri", label: t("system.base.oauth_provider.field.redirect_uri"), component: "input", colSpan: 12, props: { maxlength: 512, placeholder: t("system.base.oauth_provider.placeholder.redirect_uri") } },
|
|
108
|
+
{ prop: "scopes", label: t("system.base.oauth_provider.field.scopes"), component: "dynamic-list", colSpan: 12, props: { inputProps: { maxlength: 128, placeholder: t("system.base.oauth_provider.placeholder.scopes") } } },
|
|
109
|
+
{ prop: "config_items", label: t("system.base.oauth_provider.field.config"), component: "kv-list", colSpan: 12, props: { keyInputProps: { maxlength: 128 } } },
|
|
110
|
+
{ prop: "sort", label: t("common.field.sort"), component: "input-number", colSpan: 12, props: { min: 0, precision: 0 } },
|
|
111
|
+
{ prop: "status", label: t("common.field.status"), component: "radio-group", colSpan: 12, options: statusOptions.value }
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const columns = computed<ColumnProps[]>(() => [
|
|
115
|
+
{ type: "selection", width: 55 },
|
|
116
|
+
{ prop: "name", label: t("system.base.oauth_provider.field.name"), minWidth: 150, search: { el: "input" } },
|
|
117
|
+
{ prop: "provider", label: t("system.base.oauth_provider.field.provider"), minWidth: 130, search: { el: "input" } },
|
|
118
|
+
{ prop: "client_id", label: t("system.base.oauth_provider.field.client_id"), minWidth: 180 },
|
|
119
|
+
{ prop: "redirect_uri", label: t("system.base.oauth_provider.field.redirect_uri"), minWidth: 240 },
|
|
120
|
+
{ prop: "sort", label: t("common.field.sort"), width: 80, align: "right" },
|
|
121
|
+
{
|
|
122
|
+
prop: "status",
|
|
123
|
+
label: t("common.field.status"),
|
|
124
|
+
width: 110,
|
|
125
|
+
search: { el: "select", enum: statusOptions.value },
|
|
126
|
+
cellType: "status",
|
|
127
|
+
statusProps: {
|
|
128
|
+
activeValue: Status.STATUS_ENABLE,
|
|
129
|
+
inactiveValue: Status.STATUS_DISABLE,
|
|
130
|
+
activeText: t("common.status.enabled"),
|
|
131
|
+
inactiveText: t("common.status.disabled"),
|
|
132
|
+
disabled: () => !isDefaultTenant.value || !BUTTONS.value["base:oauth-provider:status"],
|
|
133
|
+
beforeChange: scope => handleSetStatus(scope.row as BaseOauthProvider)
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{ prop: "updated_at", label: t("common.field.updated_at"), minWidth: 180, align: "center" },
|
|
137
|
+
{
|
|
138
|
+
prop: "operation",
|
|
139
|
+
label: t("common.field.operation"),
|
|
140
|
+
cellType: "actions",
|
|
141
|
+
actions: [
|
|
142
|
+
{ label: t("common.action.edit"), link: true, icon: EditPen, hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:oauth-provider:update"], onClick: scope => openDialog((scope.row as BaseOauthProvider).id) },
|
|
143
|
+
{ label: t("common.action.delete"), type: "danger", link: true, icon: Delete, hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:oauth-provider:delete"], onClick: scope => handleDelete(scope.row as BaseOauthProvider) }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
const headerActions = computed<HeaderActionProps[]>(() => [
|
|
149
|
+
{ label: t("common.action.create"), type: "primary", icon: CirclePlus, hidden: !isDefaultTenant.value || !BUTTONS.value["base:oauth-provider:create"], onClick: () => openDialog() },
|
|
150
|
+
{ label: t("common.action.delete"), type: "danger", icon: Delete, hidden: !isDefaultTenant.value || !BUTTONS.value["base:oauth-provider:delete"], disabled: scope => !scope.isSelected, onClick: scope => handleDelete(scope.selectedListIds as number[]) }
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
/** 创建默认 OAuth 登录方式表单。 */
|
|
154
|
+
function defaultForm(): OauthProviderFormState {
|
|
155
|
+
return { id: 0, provider: "", name: "", description: "", icon: "", client_id: "", client_secret: "", secret_configured: false, redirect_uri: "", scopes: [], config: {}, config_items: [], sort: 0, status: Status.STATUS_DISABLE, name_i18ns: [], description_i18ns: [] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 请求 OAuth 登录方式表格数据。 */
|
|
159
|
+
async function requestTable(params: Record<string, unknown>) {
|
|
160
|
+
const data = await defBaseOauthProviderService.PageBaseOauthProvider(buildPageRequest<PageBaseOauthProviderRequest>(params as unknown as PageBaseOauthProviderRequest));
|
|
161
|
+
return { data: { list: data.base_oauth_providers ?? [], total: data.total } };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 打开 OAuth 登录方式表单。 */
|
|
165
|
+
async function openDialog(id?: number) {
|
|
166
|
+
await loadEnabledBaseLanguages();
|
|
167
|
+
await formDialogRef.value?.open({
|
|
168
|
+
load: () => (id ? defBaseOauthProviderService.GetBaseOauthProvider({ id }) : undefined),
|
|
169
|
+
commit: data => {
|
|
170
|
+
Object.assign(formData, defaultForm(), data ?? {});
|
|
171
|
+
dialog.editing = Boolean(id);
|
|
172
|
+
formData.config_items = configToItems(formData.config);
|
|
173
|
+
nameI18nValues.value = normalizeDynamicI18ns(formData.name_i18ns);
|
|
174
|
+
descriptionI18nValues.value = normalizeDynamicI18ns(formData.description_i18ns);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** 提交 OAuth 登录方式表单。 */
|
|
180
|
+
async function handleSubmit() {
|
|
181
|
+
const valid = await formDialogRef.value?.validate();
|
|
182
|
+
if (!valid) return;
|
|
183
|
+
const config = configItemsToMap(formData.config_items);
|
|
184
|
+
if (!config) return;
|
|
185
|
+
const payload = JSON.parse(JSON.stringify(formData)) as BaseOauthProviderForm & { config_items?: OauthProviderConfigItem[] };
|
|
186
|
+
delete payload.config_items;
|
|
187
|
+
payload.config = config;
|
|
188
|
+
payload.scopes = [...new Set(formData.scopes.map(value => value.trim()).filter(Boolean))];
|
|
189
|
+
payload.name_i18ns = serializeDynamicI18ns(nameI18nValues.value, "base_oauth_provider.name", payload.id, payload.name);
|
|
190
|
+
payload.description_i18ns = serializeDynamicI18ns(descriptionI18nValues.value, "base_oauth_provider.description", payload.id, payload.description);
|
|
191
|
+
if (payload.id) await defBaseOauthProviderService.UpdateBaseOauthProvider({ base_oauth_provider: payload });
|
|
192
|
+
else await defBaseOauthProviderService.CreateBaseOauthProvider({ base_oauth_provider: payload });
|
|
193
|
+
ElMessage.success(t("common.message.operation_success"));
|
|
194
|
+
dialog.visible = false;
|
|
195
|
+
await proTable.value?.getTableList();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** 删除 OAuth 登录方式。 */
|
|
199
|
+
async function handleDelete(value: BaseOauthProvider | number | number[]) {
|
|
200
|
+
const ids = (Array.isArray(value) ? value : typeof value === "object" ? [value.id] : normalizeSelectedIds(value)).join(",");
|
|
201
|
+
if (!ids) return;
|
|
202
|
+
await ElMessageBox.confirm(t("common.confirm.delete"), t("common.title.warning"), { type: "warning" });
|
|
203
|
+
await defBaseOauthProviderService.DeleteBaseOauthProvider({ id: ids });
|
|
204
|
+
await proTable.value?.getTableList();
|
|
205
|
+
ElMessage.success(t("common.message.operation_success"));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 设置 OAuth 登录方式状态。 */
|
|
209
|
+
async function handleSetStatus(row: BaseOauthProvider) {
|
|
210
|
+
const status = row.status === Status.STATUS_ENABLE ? Status.STATUS_DISABLE : Status.STATUS_ENABLE;
|
|
211
|
+
try {
|
|
212
|
+
await ElMessageBox.confirm(
|
|
213
|
+
t("common.dialog.status_change", {
|
|
214
|
+
action: t(status === Status.STATUS_ENABLE ? "common.status.enabled" : "common.status.disabled"),
|
|
215
|
+
resource: t("system.base.oauth_provider.resource"),
|
|
216
|
+
field: t("system.base.oauth_provider.field.name"),
|
|
217
|
+
value: row.name
|
|
218
|
+
}),
|
|
219
|
+
t("common.title.notice"),
|
|
220
|
+
{ type: "warning" }
|
|
221
|
+
);
|
|
222
|
+
await defBaseOauthProviderService.SetBaseOauthProviderStatus({ id: row.id, status });
|
|
223
|
+
return true;
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** 关闭并重置 OAuth 登录方式表单。 */
|
|
230
|
+
function handleClose() {
|
|
231
|
+
Object.assign(formData, defaultForm());
|
|
232
|
+
nameI18nValues.value = normalizeDynamicI18ns(undefined);
|
|
233
|
+
descriptionI18nValues.value = normalizeDynamicI18ns(undefined);
|
|
234
|
+
formDialogRef.value?.resetFields();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** 将结构化配置转换为键值编辑列表。 */
|
|
238
|
+
function configToItems(config: Record<string, any> | undefined): OauthProviderConfigItem[] {
|
|
239
|
+
return Object.entries(config ?? {}).map(([key, value]) => ({
|
|
240
|
+
key,
|
|
241
|
+
value: typeof value === "string" ? value : JSON.stringify(value)
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** 将键值编辑列表转换为结构化配置,并校验空键和重复键。 */
|
|
246
|
+
function configItemsToMap(items: OauthProviderConfigItem[]): Record<string, unknown> | undefined {
|
|
247
|
+
const result: Record<string, unknown> = {};
|
|
248
|
+
for (const item of items) {
|
|
249
|
+
const key = item.key.trim();
|
|
250
|
+
if (!key || Object.prototype.hasOwnProperty.call(result, key)) {
|
|
251
|
+
ElMessage.error(t("system.base.oauth_provider.message.config_invalid"));
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const value = item.value.trim();
|
|
255
|
+
if (!value) {
|
|
256
|
+
result[key] = "";
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
result[key] = JSON.parse(value);
|
|
261
|
+
} catch {
|
|
262
|
+
result[key] = value;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
</script>
|
|
268
|
+
|
|
269
|
+
<style scoped lang="scss">
|
|
270
|
+
:global(.oauth-provider-dialog .el-dialog__body) {
|
|
271
|
+
max-height: calc(88vh - 130px);
|
|
272
|
+
overflow-y: auto;
|
|
273
|
+
}
|
|
274
|
+
</style>
|