@liujitcn/kratos-admin-system 0.0.62 → 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.
Files changed (97) hide show
  1. package/README.md +16 -1
  2. package/dist/package/declarations/src/api/base/v1/ai_provider.d.ts +7 -0
  3. package/dist/package/declarations/src/api/system/admin/v1/ai_provider.d.ts +18 -0
  4. package/dist/package/declarations/src/api/system/admin/v1/base_api_rate_limit_policy.d.ts +19 -0
  5. package/dist/package/declarations/src/api/system/admin/v1/base_message_provider.d.ts +21 -0
  6. package/dist/package/declarations/src/api/system/admin/v1/base_message_template.d.ts +16 -0
  7. package/dist/package/declarations/src/api/system/admin/v1/base_oauth_provider.d.ts +18 -0
  8. package/dist/package/declarations/src/api/system/admin/v1/base_rate_limit_rule.d.ts +21 -0
  9. package/dist/package/declarations/src/components/api/ApiTransfer.vue.d.ts +35 -0
  10. package/dist/package/declarations/src/components/api/apiTransfer.d.ts +13 -0
  11. package/dist/package/declarations/src/components/i18n/DynamicI18nCell.vue.d.ts +2 -2
  12. package/dist/package/declarations/src/components/i18n/dynamicI18n.d.ts +3 -4
  13. package/dist/package/declarations/src/locales/generated.d.ts +900 -0
  14. package/dist/package/declarations/src/rpc/base/v1/ai_message.d.ts +4 -0
  15. package/dist/package/declarations/src/rpc/base/v1/ai_provider.d.ts +22 -0
  16. package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +20 -0
  17. package/dist/package/declarations/src/rpc/google/protobuf/struct.d.ts +81 -0
  18. package/dist/package/declarations/src/rpc/system/admin/v1/ai_provider.d.ts +110 -0
  19. package/dist/package/declarations/src/rpc/system/admin/v1/base_api_rate_limit_policy.d.ts +137 -0
  20. package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +4 -23
  21. package/dist/package/declarations/src/rpc/system/admin/v1/base_message.d.ts +4 -4
  22. package/dist/package/declarations/src/rpc/system/admin/v1/base_message_category.d.ts +19 -0
  23. package/dist/package/declarations/src/rpc/system/admin/v1/base_message_provider.d.ts +126 -0
  24. package/dist/package/declarations/src/rpc/system/admin/v1/base_message_template.d.ts +99 -0
  25. package/dist/package/declarations/src/rpc/system/admin/v1/base_oauth_provider.d.ts +133 -0
  26. package/dist/package/declarations/src/rpc/system/admin/v1/base_rate_limit_rule.d.ts +144 -0
  27. package/dist/package/declarations/src/utils/persisted-message.d.ts +4 -0
  28. package/dist/package/declarations/src/utils/rateLimit.d.ts +24 -0
  29. package/dist/package/declarations/src/views/ai/chat/components/ChatPanel.vue.d.ts +11 -2
  30. package/dist/package/declarations/src/views/ai/chat/components/XSender.vue.d.ts +9 -0
  31. package/dist/package/declarations/src/views/base/ai-provider/index.vue.d.ts +3 -0
  32. package/dist/package/declarations/src/views/base/api-rate-limit-policy/index.vue.d.ts +3 -0
  33. package/dist/package/declarations/src/views/base/message-provider/index.vue.d.ts +3 -0
  34. package/dist/package/declarations/src/views/base/message-template/index.vue.d.ts +3 -0
  35. package/dist/package/declarations/src/views/base/oauth-provider/index.vue.d.ts +3 -0
  36. package/dist/package/declarations/src/views/base/rate-limit-rule/index.vue.d.ts +3 -0
  37. package/dist/package/src/api/base/v1/ai_provider.ts +22 -0
  38. package/dist/package/src/api/system/admin/v1/ai_provider.ts +50 -0
  39. package/dist/package/src/api/system/admin/v1/base_api_rate_limit_policy.ts +51 -0
  40. package/dist/package/src/api/system/admin/v1/base_message_provider.ts +57 -0
  41. package/dist/package/src/api/system/admin/v1/base_message_template.ts +44 -0
  42. package/dist/package/src/api/system/admin/v1/base_oauth_provider.ts +50 -0
  43. package/dist/package/src/api/system/admin/v1/base_rate_limit_rule.ts +58 -0
  44. package/dist/package/src/components/ai/Ai.vue +1 -1
  45. package/dist/package/src/components/api/ApiTransfer.vue +117 -0
  46. package/dist/package/src/components/api/apiTransfer.ts +27 -0
  47. package/dist/package/src/components/i18n/DynamicI18nCell.vue +8 -8
  48. package/dist/package/src/components/i18n/DynamicI18nEditor.vue +6 -3
  49. package/dist/package/src/components/i18n/dynamicI18n.ts +4 -5
  50. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +2 -1
  51. package/dist/package/src/locales/en-US.json +229 -4
  52. package/dist/package/src/locales/ja-JP.json +229 -4
  53. package/dist/package/src/locales/zh-CN.json +229 -4
  54. package/dist/package/src/locales/zh-TW.json +229 -4
  55. package/dist/package/src/rpc/base/v1/ai_message.ts +7 -1
  56. package/dist/package/src/rpc/base/v1/ai_provider.ts +33 -0
  57. package/dist/package/src/rpc/base/v1/oauth.ts +16 -0
  58. package/dist/package/src/rpc/google/protobuf/struct.ts +80 -0
  59. package/dist/package/src/rpc/system/admin/v1/ai_provider.ts +129 -0
  60. package/dist/package/src/rpc/system/admin/v1/base_api_rate_limit_policy.ts +160 -0
  61. package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +4 -24
  62. package/dist/package/src/rpc/system/admin/v1/base_message.ts +4 -4
  63. package/dist/package/src/rpc/system/admin/v1/base_message_category.ts +20 -0
  64. package/dist/package/src/rpc/system/admin/v1/base_message_provider.ts +150 -0
  65. package/dist/package/src/rpc/system/admin/v1/base_message_template.ts +117 -0
  66. package/dist/package/src/rpc/system/admin/v1/base_oauth_provider.ts +156 -0
  67. package/dist/package/src/rpc/system/admin/v1/base_rate_limit_rule.ts +167 -0
  68. package/dist/package/src/utils/persisted-message.ts +21 -0
  69. package/dist/package/src/utils/rateLimit.ts +140 -0
  70. package/dist/package/src/views/ai/chat/components/ChatPanel.vue +36 -4
  71. package/dist/package/src/views/ai/chat/components/XSender.vue +112 -1
  72. package/dist/package/src/views/ai/chat/index.vue +30 -1
  73. package/dist/package/src/views/base/ai-provider/index.vue +512 -0
  74. package/dist/package/src/views/base/api-rate-limit-policy/index.vue +428 -0
  75. package/dist/package/src/views/base/backup-management/archive-record/index.vue +2 -0
  76. package/dist/package/src/views/base/backup-management/archive-restore/index.vue +2 -1
  77. package/dist/package/src/views/base/backup-management/backup-record/index.vue +2 -0
  78. package/dist/package/src/views/base/backup-management/backup-restore/index.vue +2 -1
  79. package/dist/package/src/views/base/config/index.vue +12 -13
  80. package/dist/package/src/views/base/dict/index.vue +2 -3
  81. package/dist/package/src/views/base/dict/item.vue +2 -3
  82. package/dist/package/src/views/base/job/index.vue +2 -3
  83. package/dist/package/src/views/base/job/log.vue +3 -2
  84. package/dist/package/src/views/base/menu/index.vue +27 -83
  85. package/dist/package/src/views/base/message/index.vue +9 -3
  86. package/dist/package/src/views/base/message-category/index.vue +34 -3
  87. package/dist/package/src/views/base/message-provider/index.vue +164 -0
  88. package/dist/package/src/views/base/message-template/index.vue +105 -0
  89. package/dist/package/src/views/base/migration/index.vue +1 -1
  90. package/dist/package/src/views/base/oauth-client/index.vue +6 -6
  91. package/dist/package/src/views/base/oauth-provider/index.vue +274 -0
  92. package/dist/package/src/views/base/rate-limit-rule/index.vue +322 -0
  93. package/dist/package/src/views/base/redact-output-policy/index.vue +1 -1
  94. package/dist/package/src/views/base/redact-rule/index.vue +12 -2
  95. package/dist/package/src/views/base/redact-storage-policy/index.vue +4 -0
  96. package/dist/package/src/views/base/tenant/index.vue +3 -3
  97. 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 || "default" }}
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")}:${rowList
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")}:${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}`
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>