From 8057586dc1334014abc694befa44cea8ad9ca932 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Tue, 17 Dec 2024 10:27:35 +0800 Subject: [PATCH] chore: suite first --- docs/guide/use/cert/index.md | 10 + packages/libs/lib-server/src/index.ts | 1 - .../src/router/source/modules/sys.ts | 42 +++ .../src/views/sys/suite/order/api.ts | 75 ++++++ .../src/views/sys/suite/order/crud.tsx | 254 ++++++++++++++++++ .../src/views/sys/suite/order/index.vue | 57 ++++ .../src/views/sys/suite/payment/api.ts | 76 ++++++ .../src/views/sys/suite/payment/common.tsx | 194 +++++++++++++ .../src/views/sys/suite/payment/crud.tsx | 54 ++++ .../src/views/sys/suite/payment/index.vue | 41 +++ .../src/views/sys/suite/payment/selector.vue | 163 +++++++++++ .../src/views/sys/suite/product/api.ts | 75 ++++++ .../src/views/sys/suite/product/crud.tsx | 195 ++++++++++++++ .../src/views/sys/suite/product/index.vue | 57 ++++ .../src/views/sys/suite/user-suite/api.ts | 75 ++++++ .../src/views/sys/suite/user-suite/crud.tsx | 254 ++++++++++++++++++ .../src/views/sys/suite/user-suite/index.vue | 57 ++++ .../db/migration/v10018__suite.sql | 77 ++++++ .../plugin-host/plugin/copy-to-local/index.ts | 6 + .../plugin/upload-to-host/index.ts | 39 +-- 20 files changed, 1785 insertions(+), 17 deletions(-) create mode 100644 docs/guide/use/cert/index.md create mode 100644 packages/ui/certd-client/src/views/sys/suite/order/api.ts create mode 100644 packages/ui/certd-client/src/views/sys/suite/order/crud.tsx create mode 100644 packages/ui/certd-client/src/views/sys/suite/order/index.vue create mode 100644 packages/ui/certd-client/src/views/sys/suite/payment/api.ts create mode 100644 packages/ui/certd-client/src/views/sys/suite/payment/common.tsx create mode 100644 packages/ui/certd-client/src/views/sys/suite/payment/crud.tsx create mode 100644 packages/ui/certd-client/src/views/sys/suite/payment/index.vue create mode 100644 packages/ui/certd-client/src/views/sys/suite/payment/selector.vue create mode 100644 packages/ui/certd-client/src/views/sys/suite/product/api.ts create mode 100644 packages/ui/certd-client/src/views/sys/suite/product/crud.tsx create mode 100644 packages/ui/certd-client/src/views/sys/suite/product/index.vue create mode 100644 packages/ui/certd-client/src/views/sys/suite/user-suite/api.ts create mode 100644 packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx create mode 100644 packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue create mode 100644 packages/ui/certd-server/db/migration/v10018__suite.sql diff --git a/docs/guide/use/cert/index.md b/docs/guide/use/cert/index.md new file mode 100644 index 00000000..5ee385bf --- /dev/null +++ b/docs/guide/use/cert/index.md @@ -0,0 +1,10 @@ +# 证书申请失败情况 + + +## DNS记录问题 + +1. DNS 不要设置CAA记录,删除即可 + +2. DNSSEC相关报错,DNSSEC管理中删除即可 + +3. DNS 有其他平台申请过的_acme-challenge记录,删除即可 diff --git a/packages/libs/lib-server/src/index.ts b/packages/libs/lib-server/src/index.ts index 91aed475..72c93823 100644 --- a/packages/libs/lib-server/src/index.ts +++ b/packages/libs/lib-server/src/index.ts @@ -1,5 +1,4 @@ import { SysSettingsEntity } from './system/index.js'; - export * from './basic/index.js'; export * from './system/index.js'; export { LibServerConfiguration as Configuration } from './configuration.js'; diff --git a/packages/ui/certd-client/src/router/source/modules/sys.ts b/packages/ui/certd-client/src/router/source/modules/sys.ts index ac265199..414bab91 100644 --- a/packages/ui/certd-client/src/router/source/modules/sys.ts +++ b/packages/ui/certd-client/src/router/source/modules/sys.ts @@ -169,6 +169,48 @@ export const sysResources = [ icon: "ion:person-outline", permission: "sys:auth:user:view" } + }, + { + title: "套餐支付", + name: "SuiteManager", + path: "/sys/suite", + redirect: "/sys/suite/product", + meta: { + icon: "ion:golf-outline", + permission: "sys:settings:view" + }, + children: [ + { + title: "套餐管理", + name: "ProductManager", + path: "/sys/suite/product", + component: "/sys/suite/product/index.vue", + meta: { + icon: "ion:person-outline", + permission: "sys:settings:edit" + } + }, + { + title: "支付管理", + name: "PaymentManager", + path: "/sys/suite/payment", + component: "/sys/suite/payment/index.vue", + meta: { + icon: "ion:person-outline", + permission: "sys:settings:edit" + } + }, + { + title: "订单管理", + name: "OrderManager", + path: "/sys/suite/order", + component: "/sys/suite/order/index.vue", + meta: { + icon: "ion:person-outline", + permission: "sys:settings:edit" + } + } + ] } // { diff --git a/packages/ui/certd-client/src/views/sys/suite/order/api.ts b/packages/ui/certd-client/src/views/sys/suite/order/api.ts new file mode 100644 index 00000000..e4bca0f1 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/order/api.ts @@ -0,0 +1,75 @@ +import { request } from "/src/api/service"; + +const apiPrefix = "/sys/suite/order"; + +export async function GetList(query: any) { + return await request({ + url: apiPrefix + "/page", + method: "post", + data: query + }); +} + +export async function AddObj(obj: any) { + return await request({ + url: apiPrefix + "/add", + method: "post", + data: obj + }); +} + +export async function UpdateObj(obj: any) { + return await request({ + url: apiPrefix + "/update", + method: "post", + data: obj + }); +} + +export async function DelObj(id: any) { + return await request({ + url: apiPrefix + "/delete", + method: "post", + params: { id } + }); +} + +export async function GetObj(id: any) { + return await request({ + url: apiPrefix + "/info", + method: "post", + params: { id } + }); +} + +export async function GetDetail(id: any) { + return await request({ + url: apiPrefix + "/detail", + method: "post", + params: { id } + }); +} + +export async function DeleteBatch(ids: any[]) { + return await request({ + url: apiPrefix + "/deleteByIds", + method: "post", + data: { ids } + }); +} + +export async function SetDefault(id: any) { + return await request({ + url: apiPrefix + "/setDefault", + method: "post", + data: { id } + }); +} + +export async function SetDisabled(id: any, disabled: boolean) { + return await request({ + url: apiPrefix + "/setDisabled", + method: "post", + data: { id, disabled } + }); +} diff --git a/packages/ui/certd-client/src/views/sys/suite/order/crud.tsx b/packages/ui/certd-client/src/views/sys/suite/order/crud.tsx new file mode 100644 index 00000000..daa8c2db --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/order/crud.tsx @@ -0,0 +1,254 @@ +import * as api from "./api"; +import { useI18n } from "vue-i18n"; +import { computed, Ref, ref } from "vue"; +import { useRouter } from "vue-router"; +import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud"; +import { useUserStore } from "/@/store/modules/user"; +import { useSettingStore } from "/@/store/modules/settings"; +import { Modal } from "ant-design-vue"; + +export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const router = useRouter(); + const { t } = useI18n(); + const pageRequest = async (query: UserPageQuery): Promise => { + return await api.GetList(query); + }; + const editRequest = async ({ form, row }: EditReq) => { + form.id = row.id; + const res = await api.UpdateObj(form); + return res; + }; + const delRequest = async ({ row }: DelReq) => { + return await api.DelObj(row.id); + }; + + const addRequest = async ({ form }: AddReq) => { + form.content = JSON.stringify({ + title: form.title + }); + const res = await api.AddObj(form); + return res; + }; + + const userStore = useUserStore(); + const settingStore = useSettingStore(); + const selectedRowKeys: Ref = ref([]); + context.selectedRowKeys = selectedRowKeys; + + return { + crudOptions: { + settings: { + plugins: { + //这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并 + rowSelection: { + enabled: true, + order: -2, + before: true, + // handle: (pluginProps,useCrudProps)=>CrudOptions, + props: { + multiple: true, + crossPage: true, + selectedRowKeys + } + } + } + }, + request: { + pageRequest, + addRequest, + editRequest, + delRequest + }, + rowHandle: { + minWidth: 200, + fixed: "right" + }, + columns: { + id: { + title: "ID", + key: "id", + type: "number", + column: { + width: 100 + }, + form: { + show: false + } + }, + domain: { + title: "CNAME域名", + type: "text", + editForm: { + component: { + disabled: true + } + }, + search: { + show: true + }, + form: { + component: { + placeholder: "cname.handsfree.work" + }, + helper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名", + rules: [{ required: true, message: "此项必填" }] + }, + column: { + width: 200 + } + }, + dnsProviderType: { + title: "DNS提供商", + type: "dict-select", + search: { + show: true + }, + dict: dict({ + url: "pi/dnsProvider/list", + value: "key", + label: "title" + }), + form: { + rules: [{ required: true, message: "此项必填" }] + }, + column: { + width: 150, + component: { + color: "auto" + } + } + }, + accessId: { + title: "DNS提供商授权", + type: "dict-select", + dict: dict({ + url: "/pi/access/list", + value: "id", + label: "name" + }), + form: { + component: { + name: "access-selector", + vModel: "modelValue", + type: compute(({ form }) => { + return form.dnsProviderType; + }) + }, + rules: [{ required: true, message: "此项必填" }] + }, + column: { + width: 150, + component: { + color: "auto" + } + } + }, + isDefault: { + title: "是否默认", + type: "dict-switch", + dict: dict({ + data: [ + { label: "是", value: true, color: "success" }, + { label: "否", value: false, color: "default" } + ] + }), + form: { + value: false, + rules: [{ required: true, message: "请选择是否默认" }] + }, + column: { + align: "center", + width: 100 + } + }, + setDefault: { + title: "设置默认", + type: "text", + form: { + show: false + }, + column: { + width: 100, + align: "center", + conditionalRenderDisabled: true, + cellRender: ({ row }) => { + if (row.isDefault) { + return; + } + const onClick = async () => { + Modal.confirm({ + title: "提示", + content: `确定要设置为默认吗?`, + onOk: async () => { + await api.SetDefault(row.id); + await crudExpose.doRefresh(); + } + }); + }; + + return ( + + 设为默认 + + ); + } + } + }, + disabled: { + title: "禁用/启用", + type: "dict-switch", + dict: dict({ + data: [ + { label: "启用", value: false, color: "success" }, + { label: "禁用", value: true, color: "error" } + ] + }), + form: { + value: false + }, + column: { + width: 100, + component: { + title: "点击可禁用/启用", + on: { + async click({ value, row }) { + Modal.confirm({ + title: "提示", + content: `确定要${!value ? "禁用" : "启用"}吗?`, + onOk: async () => { + await api.SetDisabled(row.id, !value); + await crudExpose.doRefresh(); + } + }); + } + } + } + } + }, + createTime: { + title: "创建时间", + type: "datetime", + form: { + show: false + }, + column: { + sorter: true, + width: 160, + align: "center" + } + }, + updateTime: { + title: "更新时间", + type: "datetime", + form: { + show: false + }, + column: { + show: true, + width: 160 + } + } + } + } + }; +} diff --git a/packages/ui/certd-client/src/views/sys/suite/order/index.vue b/packages/ui/certd-client/src/views/sys/suite/order/index.vue new file mode 100644 index 00000000..331f1282 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/order/index.vue @@ -0,0 +1,57 @@ + + + + diff --git a/packages/ui/certd-client/src/views/sys/suite/payment/api.ts b/packages/ui/certd-client/src/views/sys/suite/payment/api.ts new file mode 100644 index 00000000..b2acd428 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/payment/api.ts @@ -0,0 +1,76 @@ +import { request } from "/src/api/service"; + +export function createPaymentApi() { + const apiPrefix = "/sys/suite/payment"; + return { + async GetList(query: any) { + return await request({ + url: apiPrefix + "/page", + method: "post", + data: query + }); + }, + + async AddObj(obj: any) { + return await request({ + url: apiPrefix + "/add", + method: "post", + data: obj + }); + }, + + async UpdateObj(obj: any) { + return await request({ + url: apiPrefix + "/update", + method: "post", + data: obj + }); + }, + + async DelObj(id: number) { + return await request({ + url: apiPrefix + "/delete", + method: "post", + params: { id } + }); + }, + + async GetObj(id: number) { + return await request({ + url: apiPrefix + "/info", + method: "post", + params: { id } + }); + }, + + async GetOptions(id: number) { + return await request({ + url: apiPrefix + "/options", + method: "post" + }); + }, + + async GetSimpleInfo(id: number) { + return await request({ + url: apiPrefix + "/simpleInfo", + method: "post", + params: { id } + }); + }, + + async GetDefineTypes() { + return await request({ + url: apiPrefix + "/getTypeDict", + method: "post" + }); + }, + + async GetProviderDefine(type: string) { + return await request({ + url: apiPrefix + "/define", + method: "post", + params: { type } + }); + } + }; +} diff --git a/packages/ui/certd-client/src/views/sys/suite/payment/common.tsx b/packages/ui/certd-client/src/views/sys/suite/payment/common.tsx new file mode 100644 index 00000000..3bf18feb --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/payment/common.tsx @@ -0,0 +1,194 @@ +import { ColumnCompositionProps, compute, dict } from "@fast-crud/fast-crud"; +import { computed, provide, ref, toRef } from "vue"; +import { useReference } from "/@/use/use-refrence"; +import { forEach, get, merge, set } from "lodash-es"; +import { Modal } from "ant-design-vue"; +import * as api from "/@/views/sys/cname/provider/api"; +import { mitter } from "/@/utils/util.mitt"; + +export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { + const notificationTypeDictRef = dict({ + url: "/sys/suite/payment/getList" + }); + const defaultPluginConfig = { + component: { + name: "a-input", + vModel: "value" + } + }; + + function buildDefineFields(define: any, form: any, mode: string) { + const formWrapperRef = crudExpose.getFormWrapperRef(); + const columnsRef = toRef(formWrapperRef.formOptions, "columns"); + + for (const key in columnsRef.value) { + if (key.indexOf(".") >= 0) { + delete columnsRef.value[key]; + } + } + console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value); + forEach(define.input, (value: any, mapKey: any) => { + const key = "body." + mapKey; + const field = { + ...value, + key + }; + const column = merge({ title: key }, defaultPluginConfig, field); + //eval + useReference(column); + + if (column.required) { + if (!column.rules) { + column.rules = []; + } + column.rules.push({ required: true, message: "此项必填" }); + } + + //设置默认值 + if (column.value != null && get(form, key) == null) { + set(form, key, column.value); + } + //字段配置赋值 + columnsRef.value[key] = column; + console.log("form", columnsRef.value, form); + }); + } + + const currentDefine = ref(); + + return { + id: { + title: "ID", + key: "id", + type: "number", + column: { + width: 100 + }, + form: { + show: false + } + }, + type: { + title: "支付类型", + type: "dict-select", + dict: notificationTypeDictRef, + search: { + show: false + }, + column: { + width: 200, + component: { + color: "auto" + } + }, + editForm: { + component: { + disabled: false + } + }, + form: { + component: { + disabled: false, + showSearch: true, + filterOption: (input: string, option: any) => { + input = input?.toLowerCase(); + return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0; + }, + renderLabel(item: any) { + return ( + + {item.label} + {item.needPlus && } + + ); + } + }, + rules: [{ required: true, message: "请选择通知类型" }], + valueChange: { + immediate: true, + async handle({ value, mode, form, immediate }) { + if (value == null) { + return; + } + const lastTitle = currentDefine.value?.title; + const define = await api.GetProviderDefine(value); + currentDefine.value = define; + console.log("define", define); + + if (!immediate) { + form.body = {}; + if (define.needPlus) { + mitter.emit("openVipModal"); + } + } + + if (!form.name || form.name === lastTitle) { + form.name = define.title; + } + buildDefineFields(define, form, mode); + } + }, + helper: computed(() => { + const define = currentDefine.value; + if (define == null) { + return ""; + } + return define.desc; + }) + } + } as ColumnCompositionProps, + name: { + title: "通知名称", + search: { + show: true + }, + type: ["text"], + form: { + rules: [{ required: true, message: "请填写名称" }], + helper: "随便填,当多个相同类型的通知时,便于区分" + }, + column: { + width: 200 + } + }, + test: { + title: "测试", + form: { + show: compute(({ form }) => { + return !!form.type; + }), + component: { + name: "api-test", + action: "TestRequest" + }, + order: 990, + col: { + span: 24 + } + }, + column: { + show: false + } + }, + setting: { + column: { show: false }, + form: { + show: false, + valueBuilder({ value, form }) { + form.body = {}; + if (!value) { + return; + } + const setting = JSON.parse(value); + for (const key in setting) { + form.body[key] = setting[key]; + } + }, + valueResolve({ form }) { + const setting = form.body; + form.setting = JSON.stringify(setting); + } + } + } as ColumnCompositionProps + }; +} diff --git a/packages/ui/certd-client/src/views/sys/suite/payment/crud.tsx b/packages/ui/certd-client/src/views/sys/suite/payment/crud.tsx new file mode 100644 index 00000000..ae2d1d27 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/payment/crud.tsx @@ -0,0 +1,54 @@ +import { ref } from "vue"; +import { getCommonColumnDefine } from "./common"; +import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; +import { createNotificationApi } from "/@/views/certd/notification/api"; +const api = createNotificationApi(); +export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const pageRequest = async (query: UserPageQuery): Promise => { + return await api.GetList(query); + }; + const editRequest = async (req: EditReq) => { + const { form, row } = req; + form.id = row.id; + const res = await api.UpdateObj(form); + return res; + }; + const delRequest = async (req: DelReq) => { + const { row } = req; + return await api.DelObj(row.id); + }; + + const addRequest = async (req: AddReq) => { + const { form } = req; + const res = await api.AddObj(form); + return res; + }; + + const typeRef = ref(); + const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api); + return { + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest + }, + form: { + labelCol: { + //固定label宽度 + span: null, + style: { + width: "145px" + } + } + }, + rowHandle: { + width: 200 + }, + columns: { + ...commonColumnsDefine + } + } + }; +} diff --git a/packages/ui/certd-client/src/views/sys/suite/payment/index.vue b/packages/ui/certd-client/src/views/sys/suite/payment/index.vue new file mode 100644 index 00000000..3ac0d220 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/payment/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/packages/ui/certd-client/src/views/sys/suite/payment/selector.vue b/packages/ui/certd-client/src/views/sys/suite/payment/selector.vue new file mode 100644 index 00000000..e851b86a --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/payment/selector.vue @@ -0,0 +1,163 @@ + + + + diff --git a/packages/ui/certd-client/src/views/sys/suite/product/api.ts b/packages/ui/certd-client/src/views/sys/suite/product/api.ts new file mode 100644 index 00000000..e28c3434 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/product/api.ts @@ -0,0 +1,75 @@ +import { request } from "/src/api/service"; + +const apiPrefix = "/sys/suite/product"; + +export async function GetList(query: any) { + return await request({ + url: apiPrefix + "/page", + method: "post", + data: query + }); +} + +export async function AddObj(obj: any) { + return await request({ + url: apiPrefix + "/add", + method: "post", + data: obj + }); +} + +export async function UpdateObj(obj: any) { + return await request({ + url: apiPrefix + "/update", + method: "post", + data: obj + }); +} + +export async function DelObj(id: any) { + return await request({ + url: apiPrefix + "/delete", + method: "post", + params: { id } + }); +} + +export async function GetObj(id: any) { + return await request({ + url: apiPrefix + "/info", + method: "post", + params: { id } + }); +} + +export async function GetDetail(id: any) { + return await request({ + url: apiPrefix + "/detail", + method: "post", + params: { id } + }); +} + +export async function DeleteBatch(ids: any[]) { + return await request({ + url: apiPrefix + "/deleteByIds", + method: "post", + data: { ids } + }); +} + +export async function SetDefault(id: any) { + return await request({ + url: apiPrefix + "/setDefault", + method: "post", + data: { id } + }); +} + +export async function SetDisabled(id: any, disabled: boolean) { + return await request({ + url: apiPrefix + "/setDisabled", + method: "post", + data: { id, disabled } + }); +} diff --git a/packages/ui/certd-client/src/views/sys/suite/product/crud.tsx b/packages/ui/certd-client/src/views/sys/suite/product/crud.tsx new file mode 100644 index 00000000..5e6aec26 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/product/crud.tsx @@ -0,0 +1,195 @@ +import * as api from "./api"; +import { useI18n } from "vue-i18n"; +import { computed, Ref, ref } from "vue"; +import { useRouter } from "vue-router"; +import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud"; +import { useUserStore } from "/@/store/modules/user"; +import { useSettingStore } from "/@/store/modules/settings"; +import { Modal } from "ant-design-vue"; + +export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const router = useRouter(); + const { t } = useI18n(); + const pageRequest = async (query: UserPageQuery): Promise => { + return await api.GetList(query); + }; + const editRequest = async ({ form, row }: EditReq) => { + form.id = row.id; + const res = await api.UpdateObj(form); + return res; + }; + const delRequest = async ({ row }: DelReq) => { + return await api.DelObj(row.id); + }; + + const addRequest = async ({ form }: AddReq) => { + form.content = JSON.stringify({ + title: form.title + }); + const res = await api.AddObj(form); + return res; + }; + + const userStore = useUserStore(); + const settingStore = useSettingStore(); + const selectedRowKeys: Ref = ref([]); + context.selectedRowKeys = selectedRowKeys; + + return { + crudOptions: { + settings: { + plugins: { + //这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并 + rowSelection: { + enabled: true, + order: -2, + before: true, + // handle: (pluginProps,useCrudProps)=>CrudOptions, + props: { + multiple: true, + crossPage: true, + selectedRowKeys + } + } + } + }, + request: { + pageRequest, + addRequest, + editRequest, + delRequest + }, + rowHandle: { + minWidth: 200, + fixed: "right" + }, + columns: { + id: { + title: "ID", + key: "id", + type: "number", + column: { + width: 100 + }, + form: { + show: false + } + }, + title: { + title: "套餐名称", + type: "text", + editForm: { + component: { + disabled: true + } + }, + search: { + show: true + }, + column: { + width: 200 + } + }, + type: { + title: "类型", + type: "dict-select", + dict: dict({ + data: [ + { label: "套餐", value: "suite" }, + { label: "加量包", value: "addon" } + ] + }) + }, + maxDomainCount: { + title: "域名数量", + type: "number" + }, + maxPipelineCount: { + title: "流水线数量", + type: "number" + }, + maxDeployCount: { + title: "部署次数", + type: "number" + }, + price: { + title: "单价", + type: "number" + }, + originPrice: { + title: "原价", + type: "number" + }, + duration: { + title: "有效时长", + type: "number" + }, + intro: { + title: "说明", + type: "textarea" + }, + order: { + title: "排序", + type: "number", + form: { + show: false + } + }, + disabled: { + title: "禁用/启用", + type: "dict-switch", + dict: dict({ + data: [ + { label: "启用", value: false, color: "success" }, + { label: "禁用", value: true, color: "error" } + ] + }), + form: { + value: false + }, + column: { + width: 100, + component: { + title: "点击可禁用/启用", + on: { + async click({ value, row }) { + Modal.confirm({ + title: "提示", + content: `确定要${!value ? "禁用" : "启用"}吗?`, + onOk: async () => { + await api.SetDisabled(row.id, !value); + await crudExpose.doRefresh(); + } + }); + } + } + } + } + }, + createTime: { + title: "创建时间", + type: "datetime", + form: { + show: false + }, + column: { + sorter: true, + width: 160, + align: "center" + } + }, + updateTime: { + title: "更新时间", + type: "datetime", + form: { + show: false + }, + column: { + show: true, + width: 160 + } + } + } + } + }; +} diff --git a/packages/ui/certd-client/src/views/sys/suite/product/index.vue b/packages/ui/certd-client/src/views/sys/suite/product/index.vue new file mode 100644 index 00000000..331f1282 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/product/index.vue @@ -0,0 +1,57 @@ + + + + diff --git a/packages/ui/certd-client/src/views/sys/suite/user-suite/api.ts b/packages/ui/certd-client/src/views/sys/suite/user-suite/api.ts new file mode 100644 index 00000000..4dd724c8 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/user-suite/api.ts @@ -0,0 +1,75 @@ +import { request } from "/src/api/service"; + +const apiPrefix = "/sys/suite/userSuite"; + +export async function GetList(query: any) { + return await request({ + url: apiPrefix + "/page", + method: "post", + data: query + }); +} + +export async function AddObj(obj: any) { + return await request({ + url: apiPrefix + "/add", + method: "post", + data: obj + }); +} + +export async function UpdateObj(obj: any) { + return await request({ + url: apiPrefix + "/update", + method: "post", + data: obj + }); +} + +export async function DelObj(id: any) { + return await request({ + url: apiPrefix + "/delete", + method: "post", + params: { id } + }); +} + +export async function GetObj(id: any) { + return await request({ + url: apiPrefix + "/info", + method: "post", + params: { id } + }); +} + +export async function GetDetail(id: any) { + return await request({ + url: apiPrefix + "/detail", + method: "post", + params: { id } + }); +} + +export async function DeleteBatch(ids: any[]) { + return await request({ + url: apiPrefix + "/deleteByIds", + method: "post", + data: { ids } + }); +} + +export async function SetDefault(id: any) { + return await request({ + url: apiPrefix + "/setDefault", + method: "post", + data: { id } + }); +} + +export async function SetDisabled(id: any, disabled: boolean) { + return await request({ + url: apiPrefix + "/setDisabled", + method: "post", + data: { id, disabled } + }); +} diff --git a/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx b/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx new file mode 100644 index 00000000..daa8c2db --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx @@ -0,0 +1,254 @@ +import * as api from "./api"; +import { useI18n } from "vue-i18n"; +import { computed, Ref, ref } from "vue"; +import { useRouter } from "vue-router"; +import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud"; +import { useUserStore } from "/@/store/modules/user"; +import { useSettingStore } from "/@/store/modules/settings"; +import { Modal } from "ant-design-vue"; + +export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const router = useRouter(); + const { t } = useI18n(); + const pageRequest = async (query: UserPageQuery): Promise => { + return await api.GetList(query); + }; + const editRequest = async ({ form, row }: EditReq) => { + form.id = row.id; + const res = await api.UpdateObj(form); + return res; + }; + const delRequest = async ({ row }: DelReq) => { + return await api.DelObj(row.id); + }; + + const addRequest = async ({ form }: AddReq) => { + form.content = JSON.stringify({ + title: form.title + }); + const res = await api.AddObj(form); + return res; + }; + + const userStore = useUserStore(); + const settingStore = useSettingStore(); + const selectedRowKeys: Ref = ref([]); + context.selectedRowKeys = selectedRowKeys; + + return { + crudOptions: { + settings: { + plugins: { + //这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并 + rowSelection: { + enabled: true, + order: -2, + before: true, + // handle: (pluginProps,useCrudProps)=>CrudOptions, + props: { + multiple: true, + crossPage: true, + selectedRowKeys + } + } + } + }, + request: { + pageRequest, + addRequest, + editRequest, + delRequest + }, + rowHandle: { + minWidth: 200, + fixed: "right" + }, + columns: { + id: { + title: "ID", + key: "id", + type: "number", + column: { + width: 100 + }, + form: { + show: false + } + }, + domain: { + title: "CNAME域名", + type: "text", + editForm: { + component: { + disabled: true + } + }, + search: { + show: true + }, + form: { + component: { + placeholder: "cname.handsfree.work" + }, + helper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名", + rules: [{ required: true, message: "此项必填" }] + }, + column: { + width: 200 + } + }, + dnsProviderType: { + title: "DNS提供商", + type: "dict-select", + search: { + show: true + }, + dict: dict({ + url: "pi/dnsProvider/list", + value: "key", + label: "title" + }), + form: { + rules: [{ required: true, message: "此项必填" }] + }, + column: { + width: 150, + component: { + color: "auto" + } + } + }, + accessId: { + title: "DNS提供商授权", + type: "dict-select", + dict: dict({ + url: "/pi/access/list", + value: "id", + label: "name" + }), + form: { + component: { + name: "access-selector", + vModel: "modelValue", + type: compute(({ form }) => { + return form.dnsProviderType; + }) + }, + rules: [{ required: true, message: "此项必填" }] + }, + column: { + width: 150, + component: { + color: "auto" + } + } + }, + isDefault: { + title: "是否默认", + type: "dict-switch", + dict: dict({ + data: [ + { label: "是", value: true, color: "success" }, + { label: "否", value: false, color: "default" } + ] + }), + form: { + value: false, + rules: [{ required: true, message: "请选择是否默认" }] + }, + column: { + align: "center", + width: 100 + } + }, + setDefault: { + title: "设置默认", + type: "text", + form: { + show: false + }, + column: { + width: 100, + align: "center", + conditionalRenderDisabled: true, + cellRender: ({ row }) => { + if (row.isDefault) { + return; + } + const onClick = async () => { + Modal.confirm({ + title: "提示", + content: `确定要设置为默认吗?`, + onOk: async () => { + await api.SetDefault(row.id); + await crudExpose.doRefresh(); + } + }); + }; + + return ( + + 设为默认 + + ); + } + } + }, + disabled: { + title: "禁用/启用", + type: "dict-switch", + dict: dict({ + data: [ + { label: "启用", value: false, color: "success" }, + { label: "禁用", value: true, color: "error" } + ] + }), + form: { + value: false + }, + column: { + width: 100, + component: { + title: "点击可禁用/启用", + on: { + async click({ value, row }) { + Modal.confirm({ + title: "提示", + content: `确定要${!value ? "禁用" : "启用"}吗?`, + onOk: async () => { + await api.SetDisabled(row.id, !value); + await crudExpose.doRefresh(); + } + }); + } + } + } + } + }, + createTime: { + title: "创建时间", + type: "datetime", + form: { + show: false + }, + column: { + sorter: true, + width: 160, + align: "center" + } + }, + updateTime: { + title: "更新时间", + type: "datetime", + form: { + show: false + }, + column: { + show: true, + width: 160 + } + } + } + } + }; +} diff --git a/packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue b/packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue new file mode 100644 index 00000000..331f1282 --- /dev/null +++ b/packages/ui/certd-client/src/views/sys/suite/user-suite/index.vue @@ -0,0 +1,57 @@ + + + + diff --git a/packages/ui/certd-server/db/migration/v10018__suite.sql b/packages/ui/certd-server/db/migration/v10018__suite.sql new file mode 100644 index 00000000..11604428 --- /dev/null +++ b/packages/ui/certd-server/db/migration/v10018__suite.sql @@ -0,0 +1,77 @@ +CREATE TABLE "cd_product" +( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "icon" varchar(100), + "title" varchar(100), + "type" varchar(100), + "max_domain_count" integer, + "max_pipeline_count" integer, + "max_deploy_count" integer, + "deploy_count_period" varchar(100), + "site_monitor" boolean, + "expires_time" integer, + "price" integer, + "origin_price" integer, + "intro" varchar(2048), + "order" integer, + "disabled" boolean NOT NULL DEFAULT (false), + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); + + + +CREATE TABLE "cd_payment" +( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "type" varchar(100), + "title" varchar(100), + "setting" text, + "order" integer, + "disabled" boolean NOT NULL DEFAULT (false), + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); + + +CREATE TABLE "cd_order" +( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "user_id" integer, + "product_id" integer, + "title" varchar(100), + "count" integer, + "price" integer, + "amount" integer, + "remark" varchar(100), + "status" varchar(100), + "pay_id" integer, + "pay_time" integer, + "pay_type" varchar(100), + "pay_no" varchar(100), + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); + + + +CREATE TABLE "cd_user_suite" +( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "user_id" integer, + "product_id" integer, + "icon" varchar(100), + "title" varchar(100), + "max_domain_count" integer, + "max_pipeline_count" integer, + "max_deploy_count" integer, + "used_deploy_count" integer, + "site_monitor" boolean, + "expires_time" integer, + "disabled" boolean NOT NULL DEFAULT (false), + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); + + + diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts index 928e4dee..ff757800 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/copy-to-local/index.ts @@ -208,31 +208,37 @@ export class CopyCertToLocalPlugin extends AbstractTaskPlugin { const handle = async ({ reader, tmpCrtPath, tmpKeyPath, tmpDerPath, tmpPfxPath, tmpIcPath, tmpJksPath }) => { this.logger.info('复制到目标路径'); if (crtPath) { + crtPath = crtPath.trim(); crtPath = crtPath.startsWith('/') ? crtPath : path.join(Constants.dataDir, crtPath); this.copyFile(tmpCrtPath, crtPath); this.hostCrtPath = crtPath; } if (keyPath) { + keyPath = keyPath.trim(); keyPath = keyPath.startsWith('/') ? keyPath : path.join(Constants.dataDir, keyPath); this.copyFile(tmpKeyPath, keyPath); this.hostKeyPath = keyPath; } if (icPath) { + icPath = icPath.trim(); icPath = icPath.startsWith('/') ? icPath : path.join(Constants.dataDir, icPath); this.copyFile(tmpIcPath, icPath); this.hostIcPath = icPath; } if (pfxPath) { + pfxPath = icPath.trim(); pfxPath = pfxPath.startsWith('/') ? pfxPath : path.join(Constants.dataDir, pfxPath); this.copyFile(tmpPfxPath, pfxPath); this.hostPfxPath = pfxPath; } if (derPath) { + derPath = derPath.trim(); derPath = derPath.startsWith('/') ? derPath : path.join(Constants.dataDir, derPath); this.copyFile(tmpDerPath, derPath); this.hostDerPath = derPath; } if (jksPath) { + jksPath = jksPath.trim(); jksPath = jksPath.startsWith('/') ? jksPath : path.join(Constants.dataDir, jksPath); this.copyFile(tmpJksPath, jksPath); this.hostJksPath = jksPath; diff --git a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts index 933ead20..8dc51916 100644 --- a/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-host/plugin/upload-to-host/index.ts @@ -239,7 +239,8 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { this.logger.info(`复制文件:${srcFile} => ${destFile}`); } async execute(): Promise { - const { crtPath, keyPath, cert, accessId } = this; + const { cert, accessId } = this; + let { crtPath, keyPath, icPath, pfxPath, derPath, jksPath } = this; const certReader = new CertReader(cert); const handle = async (opts: CertReaderHandleContext) => { @@ -269,6 +270,7 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { const transports: any = []; if (crtPath) { + crtPath = crtPath.trim(); transports.push({ localPath: tmpCrtPath, remotePath: crtPath, @@ -276,39 +278,44 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { this.logger.info(`上传证书到主机:${crtPath}`); } if (keyPath) { + keyPath = keyPath.trim(); transports.push({ localPath: tmpKeyPath, remotePath: keyPath, }); this.logger.info(`上传私钥到主机:${keyPath}`); } - if (this.icPath) { + if (icPath) { + icPath = icPath.trim(); transports.push({ localPath: tmpIcPath, - remotePath: this.icPath, + remotePath: icPath, }); - this.logger.info(`上传中间证书到主机:${this.icPath}`); + this.logger.info(`上传中间证书到主机:${icPath}`); } - if (this.pfxPath) { + if (pfxPath) { + pfxPath = pfxPath.trim(); transports.push({ localPath: tmpPfxPath, - remotePath: this.pfxPath, + remotePath: pfxPath, }); - this.logger.info(`上传PFX证书到主机:${this.pfxPath}`); + this.logger.info(`上传PFX证书到主机:${pfxPath}`); } - if (this.derPath) { + if (derPath) { + derPath = derPath.trim(); transports.push({ localPath: tmpDerPath, - remotePath: this.derPath, + remotePath: derPath, }); - this.logger.info(`上传DER证书到主机:${this.derPath}`); + this.logger.info(`上传DER证书到主机:${derPath}`); } if (this.jksPath) { + jksPath = jksPath.trim(); transports.push({ localPath: tmpJksPath, - remotePath: this.jksPath, + remotePath: jksPath, }); - this.logger.info(`上传jks证书到主机:${this.jksPath}`); + this.logger.info(`上传jks证书到主机:${jksPath}`); } this.logger.info('开始上传文件到服务器'); await sshClient.uploadFiles({ @@ -320,10 +327,10 @@ export class UploadCertToHostPlugin extends AbstractTaskPlugin { //输出 this.hostCrtPath = crtPath; this.hostKeyPath = keyPath; - this.hostIcPath = this.icPath; - this.hostPfxPath = this.pfxPath; - this.hostDerPath = this.derPath; - this.hostJksPath = this.jksPath; + this.hostIcPath = icPath; + this.hostPfxPath = pfxPath; + this.hostDerPath = derPath; + this.hostJksPath = jksPath; }; await certReader.readCertFile({