Files
AllinSSL/frontend/apps/allin-ssl/src/views/authApiManage/useController.tsx
2026-01-16 11:52:32 +08:00

1992 lines
57 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
FormItemRule,
FormProps,
FormRules,
NAlert,
NButton,
NFlex,
NFormItem,
NFormItemGi,
NGrid,
NInput,
NInputNumber,
NSelect,
NSpace,
NTag,
NText,
NTooltip,
NEmpty,
type DataTableColumns,
} from 'naive-ui'
import {
useModal,
useDialog,
useTable,
useSearch,
useModalHooks,
useFormHooks,
useForm,
useLoadingMask,
} from '@baota/naive-ui/hooks'
import { useError } from '@baota/hooks/error'
import { isEmail, isIp, isPort, isUrl, isDomain } from '@baota/utils/business'
import { $t } from '@locales/index'
import { useStore } from './useStore'
import { ApiProjectConfig } from '@config/data'
import type {
AccessItem,
AccessListParams,
AddAccessParams,
SshAccessConfig,
UpdateAccessParams,
PanelAccessConfig,
NamecheapAccessConfig,
NS1AccessConfig,
CloudnsAccessConfig,
AwsAccessConfig,
AzureAccessConfig,
NamesiloAccessConfig,
NamedotcomAccessConfig,
BunnyAccessConfig,
GcoreAccessConfig,
JdcloudAccessConfig,
DogeAccessConfig,
PluginAccessConfig,
LecdnAccessConfig,
ConstellixAccessConfig,
WebhookAccessConfig,
SpaceshipAccessConfig,
BTDomainAccessConfig,
} from "@/types/access";
import type { VNode, Ref } from "vue";
import { testAccess, getPlugins } from "@/api/access";
// import { useLocalStorage } from '@vueuse/core'
import ApiManageForm from "./components/ApiManageModel";
import SvgIcon from "@components/svgIcon";
import TypeIcon from "@components/typeIcon";
import { noSideSpace } from "@lib/utils";
import { JSX } from "vue/jsx-runtime";
/**
* 授权API管理控制器接口
*/
interface AuthApiManageControllerExposes {
loading: Ref<boolean>;
fetch: (resetPage?: boolean) => Promise<void>;
TableComponent: (
props: Record<string, unknown>,
context: Record<string, unknown>
) => VNode;
PageComponent: (
props: Record<string, unknown>,
context: Record<string, unknown>
) => VNode;
SearchComponent: (
props: Record<string, unknown>,
context: Record<string, unknown>
) => VNode;
param: Ref<AccessListParams>;
openAddForm: () => void;
}
/**
* API表单控制器接口
*/
interface ApiFormControllerExposes {
ApiManageForm: (
props: FormProps,
context: Record<string, unknown>
) => any;
}
// 获取Store中的状态和方法
const {
accessTypeMap,
apiFormProps,
fetchAccessList,
deleteExistingAccess,
addNewAccess,
updateExistingAccess,
resetApiForm,
} = useStore();
// 错误处理钩子
const { handleError } = useError();
/**
* 授权API管理业务逻辑控制器
* @description 处理授权API相关的业务逻辑包括列表展示、表单操作等
* @returns {AuthApiManageControllerExposes} 返回授权API相关的状态数据和处理方法
*/
export const useController = (): AuthApiManageControllerExposes => {
/**
* 测试授权API
* @param {AccessItem} row - 授权API信息
*/
const handleTestAccess = async (row: AccessItem): Promise<void> => {
try {
const { fetch, message } = testAccess({ id: row.id, type: row.type });
message.value = true;
await fetch();
} catch (error) {
handleError(error);
}
};
/**
* 创建表格列配置
* @returns {DataTableColumns<AccessItem>} 返回表格列配置数组
*/
const createColumns = (): DataTableColumns<AccessItem> => [
{
title: $t("t_2_1745289353944"),
key: "name",
width: 200,
ellipsis: {
tooltip: true,
},
},
{
title: $t("t_1_1746754499371"),
key: "type",
width: 140,
render: (row) => <TypeIcon round class="!py-[4px] !px-[10px]" icon={row.type} type="success" />,
},
{
title: $t("t_2_1746754500270"),
key: "type",
width: 200,
render: (row) => (
<NSpace>
{row.access_type?.map((type) => {
return (
<NTag
key={type}
type={type === "dns" ? "success" : "info"}
size="small"
round
>
{accessTypeMap[type as keyof typeof accessTypeMap]}
</NTag>
);
})}
</NSpace>
),
},
{
title: $t("t_7_1745215914189"),
key: "create_time",
width: 180,
},
{
title: $t("t_0_1745295228865"),
key: "update_time",
width: 180,
},
{
title: $t("t_8_1745215914610"),
key: "actions",
width: 200,
align: "right",
fixed: "right",
render: (row) => {
return (
<NSpace justify="end">
<NButton
size="tiny"
strong
secondary
type="primary"
class="table-action-btn"
onClick={() => handleTestAccess(row)}
>
{$t("t_16_1746676855270")}
</NButton>
<NButton
size="tiny"
strong
secondary
type="primary"
class="table-action-btn"
onClick={() => openEditForm(row)}
>
{$t("t_11_1745215915429")}
</NButton>
<NButton
size="tiny"
strong
secondary
type="error"
class="table-action-btn-danger"
onClick={() => confirmDelete(row.id)}
>
{$t("t_12_1745215914312")}
</NButton>
</NSpace>
);
},
},
];
// 表格实例
const { TableComponent, PageComponent, loading, param, fetch } = useTable<
AccessItem,
AccessListParams
>({
config: createColumns(),
request: fetchAccessList,
watchValue: ["p", "limit"],
storage: "authApiManage",
alias: { page: "p", pageSize: "limit" },
defaultValue: {
p: 1,
limit: 10,
search: "",
},
});
// 搜索实例
const { SearchComponent, search } = useSearch({
onSearch: (value) => {
param.value.search = value;
fetch();
},
});
/**
* 打开添加授权API弹窗
*/
const openAddForm = (): void => {
useModal({
title: $t("t_0_1745289355714"),
area: 500,
component: ApiManageForm,
footer: true,
onUpdateShow: (show) => {
if (!show) fetch();
resetApiForm();
},
});
};
/**
* 打开编辑授权API弹窗
* @param {AccessItem} row - 授权API信息
*/
const openEditForm = (row: AccessItem): void => {
useModal({
title: $t("t_4_1745289354902"),
area: 500,
component: ApiManageForm,
componentProps: { data: row },
footer: true,
onUpdateShow: (show) => {
if (!show) fetch();
resetApiForm();
},
});
};
/**
* 确认删除授权API
* @param {string} id - 授权API ID
*/
const confirmDelete = (id: string): void => {
useDialog({
title: $t("t_5_1745289355718"),
content: $t("t_6_1745289358340"),
confirmText: $t("t_5_1744870862719"),
cancelText: $t("t_4_1744870861589"),
onPositiveClick: async () => {
await deleteExistingAccess(id);
await fetch();
},
});
};
// 挂载时,获取数据
onMounted(fetch);
return {
loading,
fetch,
TableComponent,
PageComponent,
SearchComponent,
param,
openAddForm,
};
};
/**
* 表单控制器Props接口
*/
interface ApiFormControllerProps {
data?: AccessItem;
}
interface PluginOption {
label: string;
value: string;
description?: string;
pluginName?: string;
config?: ConfigItem[];
params?: string;
}
interface ConfigItem {
name: string;
type: 'string' | 'boolean' | 'enum';
description: string;
required: boolean;
options?: Array<string | { label: string; value: string }>;
config?: any
}
/**
* 授权API表单控制器
* @description 处理授权API表单相关的业务逻辑
* @param {ApiFormControllerProps} props - 表单控制器属性
* @returns {ApiFormControllerExposes} 返回表单控制器对象
*/
export const useApiFormController = (
props: ApiFormControllerProps
): ApiFormControllerExposes => {
const { confirm } = useModalHooks(); // 弹窗挂载方法
const { open: openLoad, close: closeLoad } = useLoadingMask({
text: $t("t_0_1746667592819"),
});
const {
useFormInput,
useFormRadioButton,
useFormSwitch,
useFormTextarea,
useFormCustom,
useFormSelect,
useFormHelp,
} = useFormHooks();
const param = (
props.data?.id
? ref({ ...props.data, config: JSON.parse(props.data.config) })
: apiFormProps
) as Ref<AddAccessParams | UpdateAccessParams>;
const pluginActionTips = ref("");
// 插件列表
const pluginList = ref<Array<PluginOption>>([]);
// 生成插件配置表单控件的辅助函数
const generatePluginFormControl = (configItem: ConfigItem, fieldPath: string, noSideSpace: any) => {
const itemAttrs = {
required: configItem.required || false,
showRequireMark: configItem.required || false,
trigger: configItem.required ? 'input' : undefined
};
// 根据配置项类型渲染不同的表单控件
switch (configItem.type) {
case 'string':
return useFormInput(
configItem.description,
fieldPath,
{ allowInput: noSideSpace },
itemAttrs
);
case 'boolean':
return useFormSwitch(
configItem.description,
fieldPath,
{
checkedValue: true,
uncheckedValue: false
},
itemAttrs
);
case 'enum':
// 确保options存在且是数组
const options = configItem.options || [];
return useFormSelect(
configItem.description,
fieldPath,
options.map((opt: any) => ({
label: typeof opt === 'string' ? opt : opt.label || opt,
value: typeof opt === 'string' ? opt : opt.value || opt
})),
{},
itemAttrs
);
default:
// 默认使用字符串输入框
return useFormInput(
configItem.description,
fieldPath,
{ allowInput: noSideSpace },
itemAttrs
);
}
};
// 表单规则
const rules: Record<string, any> = {
name: {
required: true,
message: $t("t_27_1745289355721"),
trigger: "input",
},
type: {
required: true,
message: $t("t_28_1745289356040"),
trigger: "change",
},
config: {
host: {
required: true,
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!isIp(value) && !isDomain(value)) {
return callback(new Error($t("t_0_1749119980577")));
}
callback();
},
},
port: {
required: true,
trigger: "input",
validator: (
rule: FormItemRule,
value: number,
callback: (error?: Error) => void
) => {
if (!isPort(value.toString())) {
return callback(new Error($t("t_1_1745317313096")));
}
callback();
},
},
user: {
required: true,
trigger: "input",
message: $t("t_3_1744164839524"),
},
username: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!value) {
const mapTips = {
westcn: $t("t_0_1747365600180"),
namedotcom: "请输入用户名",
lecdn: "请输入用户名",
};
return callback(
new Error(
mapTips[param.value.type as keyof typeof mapTips] ||
$t("t_0_1747365600180")
)
);
}
callback();
},
},
password: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
// SSH 类型的密码字段在密码模式下是必填的,在密钥模式下是可选的
if (param.value.type === "ssh") {
const sshConfig = param.value.config as SshAccessConfig;
if (sshConfig?.mode === "password" && !value) {
return callback(new Error($t("t_0_1747711335067")));
}
// 密钥模式下密码是可选的,不需要验证
callback();
return;
}
if (!value) {
const mapTips = {
westcn: $t("t_1_1747365603108"),
lecdn: "请输入密码",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
key: {
required: true,
message: $t("t_31_1745289355715"),
trigger: "input",
},
url: {
required: true,
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!isUrl(value)) {
const mapTips = {
btpanel: $t("t_2_1745317314362"),
btwaf: $t("t_0_1747271295174"),
safeline: $t("t_0_1747300383756"),
lecdn: "请输入正确的URL地址",
webhook: "请输入回调地址",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
api_key: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!value || !value.length) {
const mapTips = {
cloudflare: $t("t_0_1747042966820"),
btpanel: $t("t_1_1747042969705"),
btwaf: $t("t_1_1747300384579"),
godaddy: $t("t_0_1747984137443"),
ns1: "请输入API Key",
namecheap: "请输入API Key",
constellix: "请输入API Key",
spaceship: "请输入 Spaceship API Key",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
api_secret: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!value) {
const mapTips = {
godaddy: $t("t_1_1747984133312"),
spaceship: "请输入 Spaceship API Secret",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
account_id: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!value) {
const mapTips = {
btdomain: "请输入 宝塔域名 Account ID",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
access_secret: {
required: true,
message: $t("t_2_1747984134626"),
trigger: "input",
},
api_token: {
required: true,
message: $t("t_0_1747617113090"),
trigger: "input",
},
// access_key_id: {
// trigger: 'input',
// validator: (rule: FormItemRule, value: string, callback: (error?: Error) => void) => {
// if (!value) {
// const mapTips = {
// aliyun: $t('t_4_1745317314054'),
// doge: '请输入多吉云AccessKeyId',
// }
// return callback(new Error(mapTips[param.value.type as keyof typeof mapTips] || $t('t_4_1745317314054')))
// }
// callback()
// },
// },
// access_key_secret: {
// trigger: 'input',
// validator: (rule: FormItemRule, value: string, callback: (error?: Error) => void) => {
// if (!value) {
// const mapTips = {
// aliyun: $t('t_5_1745317315285'),
// doge: '请输入多吉云AccessKeySecret',
// }
// return callback(new Error(mapTips[param.value.type as keyof typeof mapTips] || $t('t_5_1745317315285')))
// }
// callback()
// },
// },
secret_access_key: {
required: true,
message: "请输入Secret Access Key",
trigger: "input",
},
api_user: {
required: true,
message: "请输入API User",
trigger: "input",
},
auth_id: {
required: true,
message: "请输入Auth ID",
trigger: "input",
},
auth_password: {
required: true,
message: "请输入Auth Password",
trigger: "input",
},
tenant_id: {
required: true,
message: "请输入Tenant ID",
trigger: "input",
},
client_id: {
required: true,
message: "请输入Client ID",
trigger: "input",
},
client_secret: {
required: true,
message: "请输入Client Secret",
trigger: "input",
},
secret_id: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!value || !value.length) {
const mapTips = {
tencentcloud: $t("t_2_1747042967277"),
edgeone: "请输入正确的 EdgeOne SecretId",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
access_key: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!value) {
const mapTips = {
huawei: $t("t_2_1747271295877"),
baidu: $t("t_3_1747271294475"),
volcengine: $t("t_3_1747365600828"),
qiniu: $t("t_3_1747984134586"),
doge: $t("t_0_1750320239265"),
btdomain: "请输入 宝塔域名 Access Key",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
secret_key: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (!value || !value.length) {
const mapTips = {
tencentcloud: $t("t_2_1747042967277"),
edgeone: "请输入 EdgeOne Secret Key",
huawei: $t("t_3_1747042967608"),
baidu: $t("t_4_1747271294621"),
volcengine: $t("t_4_1747365600137"),
doge: $t("t_1_1750320241427"),
constellix: "请输入Secret Key",
btdomain: "请输入 宝塔域名 Secret Key",
};
return callback(
new Error(mapTips[param.value.type as keyof typeof mapTips])
);
}
callback();
},
},
email: {
trigger: "input",
validator: (
rule: FormItemRule,
value: string,
callback: (error?: Error) => void
) => {
if (
param.value.type === "cloudflare" &&
(!value || value.trim() === "")
) {
return callback();
}
if (!isEmail(value)) {
return callback(new Error($t("t_5_1747042965911")));
}
callback();
},
},
region: {
required: true,
message: "请输入Region",
trigger: "input",
},
"config.name": {
required: true,
message: $t("t_0_1750144125193"),
trigger: "change",
},
},
};
// 类型列表
const typeList = Object.entries(ApiProjectConfig)
.filter(
([_, value]) => !(typeof value.notApi === "boolean" && !value.notApi)
)
.map(([key, value]) => ({
label: value.name,
value: key,
access: value.type || [],
}));
const typeUrlMap = new Map<string, string>([
["btwaf", "宝塔WAF-URL"],
["btpanel", "宝塔面板-URL"],
["1panel", "1Panel-URL"],
["safeline", "雷池WAF-URL"],
]);
// 表单配置
const config = computed(() => {
const items = [
useFormInput($t("t_2_1745289353944"), "name"),
useFormCustom(() => {
return (
<NFormItem label={$t("t_41_1745289354902")} path="type">
<NSelect
class="w-full"
options={typeList}
renderLabel={renderTypeLabel}
renderTag={renderSingleSelectTag}
disabled={!!props.data?.id}
filterable
placeholder={$t("t_0_1745833934390")}
v-model:value={param.value.type}
v-slots={{
empty: () => {
return (
<span class="text-[1.4rem]">{$t("t_0_1745833934390")}</span>
);
},
}}
/>
</NFormItem>
);
}),
] as any;
// 根据不同类型渲染不同的表单项
switch (param.value.type) {
case "ssh": {
// SSH 基础配置项
const sshBaseItems = [
useFormCustom(() => {
return (
<NGrid cols={24} xGap={4}>
<NFormItemGi
label={$t("t_1_1747711335336")}
span={16}
path="config.host"
>
<NInput
v-model:value={(param.value.config as SshAccessConfig).host}
placeholder={$t("t_2_1747711337958")}
allow-input={noSideSpace}
/>
</NFormItemGi>
<NFormItemGi
label={$t("t_2_1745833931404")}
span={8}
path="config.port"
>
<NInputNumber
v-model:value={(param.value.config as SshAccessConfig).port}
showButton={false}
/>
</NFormItemGi>
</NGrid>
);
}),
useFormInput($t("t_44_1745289354583"), "config.user"),
useFormRadioButton($t("t_45_1745289355714"), "config.mode", [
{ label: $t("t_48_1745289355714"), value: "password" },
{ label: $t("t_1_1746667588689"), value: "key" },
]),
];
// 根据认证模式添加对应的字段
const sshAuthItems = [];
if ((param.value.config as SshAccessConfig)?.mode === "password") {
sshAuthItems.push(
useFormInput($t("t_48_1745289355714"), "config.password", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
} else if ((param.value.config as SshAccessConfig)?.mode === "key") {
sshAuthItems.push(
useFormTextarea($t("t_1_1746667588689"), "config.key", {
rows: 3,
placeholder: $t("t_0_1747709067998"),
}),
// 私钥密码输入框(使用 password 字段)
useFormInput(
"私钥密码",
"config.password",
{
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
placeholder: "请输入私钥密码(可选)",
},
{ showRequireMark: false }
)
);
}
// 合并所有 SSH 配置项
items.push(...sshBaseItems, ...sshAuthItems);
break;
}
case "1panel":
items.push(
// 1Panel版本选择下拉框
useFormCustom(() => {
const config = param.value.config as PanelAccessConfig;
// 如果version为空或者没有version字段默认为v1
const currentVersion = config.version || "v1";
return (
<NFormItem
label="版本"
path="config.version"
showRequireMark={false}
>
<NSelect
class="w-full"
options={[
{ label: "v1", value: "v1" },
{ label: "v2", value: "v2" },
]}
placeholder="请选择版本"
value={currentVersion}
onUpdateValue={(value: "v1" | "v2") => {
(param.value.config as PanelAccessConfig).version = value;
}}
/>
</NFormItem>
);
}),
useFormInput(typeUrlMap.get(param.value.type) || "", "config.url", {
allowInput: noSideSpace,
}),
useFormInput($t("t_55_1745289355715"), "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormSwitch(
$t("t_3_1746667592270"),
"config.ignore_ssl",
{ checkedValue: "1", uncheckedValue: "0" },
{ showRequireMark: false }
)
);
break;
case "btpanel":
case "btwaf":
case "safeline":
items.push(
useFormInput(typeUrlMap.get(param.value.type) || "", "config.url", {
allowInput: noSideSpace,
}),
useFormInput(
param.value.type === "safeline"
? $t("t_1_1747617105179")
: $t("t_55_1745289355715"),
param.value.type === "safeline"
? "config.api_token"
: "config.api_key",
{
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}
),
useFormSwitch(
$t("t_3_1746667592270"),
"config.ignore_ssl",
{ checkedValue: "1", uncheckedValue: "0" },
{ showRequireMark: false }
)
);
break;
case "aliyun":
items.push(
useFormInput("AccessKeyId", "config.access_key_id", {
allowInput: noSideSpace,
}),
useFormInput("AccessKeySecret", "config.access_key_secret", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "tencentcloud":
items.push(
useFormInput("SecretId", "config.secret_id", {
allowInput: noSideSpace,
}),
useFormInput("SecretKey", "config.secret_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "huaweicloud":
case "baidu":
case "volcengine":
case "doge":
items.push(
useFormInput("AccessKey", "config.access_key", {
allowInput: noSideSpace,
}),
useFormInput("SecretKey", "config.secret_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "cloudflare":
items.push(
useFormInput(
"邮箱",
"config.email",
{ allowInput: noSideSpace },
{ showRequireMark: false }
),
useFormInput("APIKey", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormCustom(() => {
return (
<NAlert
type="error"
class="mt-[1.2rem] whitespace-nowrap"
showIcon={false}
>
<span class="text-[1.3rem]">
使API令牌时不要填写邮箱Global Key请求Cloudflare
</span>
</NAlert>
);
})
);
break;
case "westcn":
items.push(
useFormInput("Username", "config.username", {
allowInput: noSideSpace,
}),
useFormInput("Password", "config.password", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "godaddy":
items.push(
useFormInput("API Key", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormInput("API Secret", "config.api_secret", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "qiniu":
items.push(
useFormInput("AccessKey", "config.access_key", {
allowInput: noSideSpace,
}),
useFormInput("AccessSecret", "config.access_secret", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "namecheap":
items.push(
useFormInput("API User", "config.api_user", {
allowInput: noSideSpace,
}),
useFormInput("API Key", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "ns1":
items.push(
useFormInput("API Key", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "cloudns":
items.push(
useFormInput("Auth ID", "config.auth_id", {
allowInput: noSideSpace,
}),
useFormInput("Auth Password", "config.auth_password", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "aws":
items.push(
useFormInput("Access Key ID", "config.access_key_id", {
allowInput: noSideSpace,
}),
useFormInput("Secret Access Key", "config.secret_access_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormInput("Region", "config.region", {
allowInput: noSideSpace,
})
);
break;
case "azure":
items.push(
useFormInput("Tenant ID", "config.tenant_id", {
allowInput: noSideSpace,
}),
useFormInput("Client ID", "config.client_id", {
allowInput: noSideSpace,
}),
useFormInput("Client Secret", "config.client_secret", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormInput("Environment", "config.environment", {
allowInput: noSideSpace,
placeholder: "public",
})
);
break;
case "namesilo":
items.push(
useFormInput("API Key", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "namedotcom":
items.push(
useFormInput("Username", "config.username", {
allowInput: noSideSpace,
}),
useFormInput("API Token", "config.api_token", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "bunny":
items.push(
useFormInput("API Key", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "gcore":
items.push(
useFormInput("API Token", "config.api_token", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "jdcloud":
items.push(
useFormInput("Access Key ID", "config.access_key_id", {
allowInput: noSideSpace,
}),
useFormInput("Secret Access Key", "config.secret_access_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "lecdn":
items.push(
useFormInput("URL", "config.url", { allowInput: noSideSpace }),
useFormInput("Username", "config.username", {
allowInput: noSideSpace,
}),
useFormInput("Password", "config.password", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormSwitch(
$t("t_3_1746667592270"),
"config.ignore_ssl",
{ checkedValue: "1", uncheckedValue: "0" },
{ showRequireMark: false }
)
);
break;
case "constellix":
items.push(
useFormInput("API Key", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormInput("Secret Key", "config.secret_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "webhook":
items.push(
useFormInput("WebHook回调地址", "config.url"),
useFormSelect("请求方式", "config.method", [
{ label: "POST", value: "POST" },
{ label: "GET", value: "GET" },
]),
useFormTextarea(
"WebHook请求头可选",
"config.headers",
{ rows: 3, placeholder: "Content-Type: application/json" },
{ showRequireMark: false }
),
useFormTextarea(
"WebHook推送通知回调数据可选",
"config.data",
{
rows: 3,
placeholder:
'请使用JSON格式例如{"title":"test","content":"test"}',
},
{ showRequireMark: false }
),
useFormSwitch(
$t("t_3_1746667592270"),
"config.ignore_ssl",
{ checkedValue: "1", uncheckedValue: "0" },
{ showRequireMark: false }
),
useFormCustom(() => {
return (
<div class="mt-4 p-4 bg-[var(--form-log-bg)] rounded-md">
<div class="space-y-4 text-lg">
<div>
<div class="font-medium mb-3 text-xl">
</div>
<div class="space-y-2 ml-4">
<div>
<code class="px-2 py-1 bg-[var(--form-log-code-bg)] rounded text-lg font-mono">
__cert__
</code>
<span class="text-color5"></span>
</div>
<div>
<code class="px-2 py-1 bg-[var(--form-log-code-bg)] rounded text-lg font-mono">
__key__
</code>
<span class="text-color5"></span>
</div>
</div>
</div>
<div>
<div class="font-medium mb-3 text-xl">
</div>
<div class="space-y-2 ml-4">
<div>
<code class="px-2 py-1 bg-[var(--form-log-code-bg)] rounded text-lg font-mono">
__domain__
</code>
<span class="text-color5">_acme-challenge.allinssl.com</span>
</div>
<div>
<code class="px-2 py-1 bg-[var(--form-log-code-bg)] rounded text-lg font-mono">
__keyAuth__
</code>
<span class="text-color5"></span>
</div>
<div>
<code class="px-2 py-1 bg-[var(--form-log-code-bg)] rounded text-lg font-mono">
__action__
</code>
<span class="text-color5">present时执行写入cleanup清理记录</span>
</div>
</div>
</div>
</div>
</div>
);
})
);
break;
case "spaceship":
items.push(
useFormInput("API Key", "config.api_key", {
allowInput: noSideSpace,
}),
useFormInput("API Secret", "config.api_secret", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "btdomain":
items.push(
useFormInput("Access Key", "config.access_key", {
allowInput: noSideSpace,
}),
useFormInput("Secret Key", "config.secret_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
useFormInput("Account ID", "config.account_id", {
allowInput: noSideSpace,
})
);
break;
case "rainyun":
items.push(
useFormInput("API Key", "config.api_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
}),
);
break;
case "edgeone":
items.push(
useFormInput("SecretId", "config.secret_id", {
allowInput: noSideSpace,
}),
useFormInput("SecretKey", "config.secret_key", {
type: "password",
showPasswordOn: "click",
allowInput: noSideSpace,
})
);
break;
case "plugin":
// 插件名称选择器
items.push(
useFormCustom(function() {
return (
<NFormItem
label={$t("t_1_1750144122230")}
path="config.name"
showRequireMark={true}
>
<NSelect
class="w-full"
options={pluginList.value}
placeholder={$t("t_2_1750144123753")}
filterable
renderLabel={renderPluginLabel}
renderTag={renderPluginTag}
v-model:value={param.value.config.name}
onUpdateValue={(value, option) => {
param.value.config.name = value;
pluginActionTips.value = renderPluginTips(option.config || {});
if (!param.value.config.config || typeof param.value.config.config === 'string') {
param.value.config.config = {};
}
}}
v-slots={{
empty: function() { return <span class="text-[1.4rem]">{$t("t_0_1750210698345")}</span>; }
}}
/>
</NFormItem>
);
})
);
// 配置模式选择器
items.push(
useFormRadioButton(
"配置模式",
"config.mode",
[
{ label: "默认", value: "default" },
{ label: "自定义", value: "custom" }
],
{},
{ showRequireMark: false }
)
);
// 获取插件配置
var pluginConfig = param.value.config as PluginAccessConfig;
pluginConfig.mode = pluginConfig.mode || "default";
var selectedPlugin = pluginList.value.find(function(item) {
return item.value === pluginConfig.name;
});
// 根据配置模式初始化不同类型的config
if (pluginConfig.mode === "default") {
// 默认模式下确保config是对象类型
if (!pluginConfig.config || typeof pluginConfig.config !== 'object') {
// 如果是字符串尝试解析为JSON对象
if (typeof pluginConfig.config === 'string' && pluginConfig.config.trim()) {
try {
pluginConfig.config = JSON.parse(pluginConfig.config.trim());
} catch {
pluginConfig.config = {};
}
} else {
pluginConfig.config = {};
}
}
} else {
// 自定义模式下确保config是字符串类型
if (pluginConfig.config === undefined || pluginConfig.config === null) {
pluginConfig.config = '';
} else if (typeof pluginConfig.config === 'object') {
// 空对象时,根据插件配置生成默认的空值对象
if (Object.keys(pluginConfig.config).length === 0 && selectedPlugin?.config) {
const defaultConfig: Record<string, string> = {};
selectedPlugin.config.forEach(configItem => {
defaultConfig[configItem.name] = '';
});
pluginConfig.config = JSON.stringify(defaultConfig);
} else {
try {
// 不使用缩进生成紧凑的JSON字符串
pluginConfig.config = JSON.stringify(pluginConfig.config);
} catch {
pluginConfig.config = '';
}
}
}
}
// 未选择插件时显示提示
if (!selectedPlugin) {
items.push(
useFormCustom(function() {
return (
<NFormItem path="config.placeholder" showRequireMark={false}>
<NEmpty description="请选择插件以查看配置项" />
</NFormItem>
);
})
);
} else if (pluginConfig.mode === "default") {
// 默认模式 - 动态表单
if (!selectedPlugin.config || selectedPlugin.config.length === 0) {
items.push(
useFormCustom(function() {
return (
<NFormItem path="config.placeholder" showRequireMark={false}>
<NEmpty description="该插件暂无配置项" />
</NFormItem>
);
})
);
} else {
selectedPlugin.config.forEach(function(configItem) {
console.log('configItem', configItem)
var fieldPath = 'config.config.' + configItem.name;
// 为动态字段添加验证规则
if (!rules.config.config) rules.config.config = {};
rules.config.config[configItem.name] = {
required: configItem.required,
trigger: 'input',
message: '请输入' + (configItem.description || configItem.name)
};
items.push(generatePluginFormControl(configItem, fieldPath, noSideSpace));
});
}
} else {
// 自定义模式 - JSON文本框
items.push(
useFormCustom(function() {
const getConfigValue = () => {
// 确保当config为undefined、null或空对象时返回空字符串
if (pluginConfig.config === undefined || pluginConfig.config === null) {
return '';
}
// 如果是对象且为空对象,也返回空字符串
if (typeof pluginConfig.config === 'object' && Object.keys(pluginConfig.config).length === 0) {
return '';
}
return typeof pluginConfig.config === "string"
? pluginConfig.config
: JSON.stringify(pluginConfig.config, null, 2);
};
return (
<NFormItem
path="config.config"
v-slots={{
label: function() {
return (
<div>
<NText></NText>
<NTooltip
v-slots={{
trigger: function() {
return (
<span class="inline-flex ml-2 -mt-1 cursor-pointer text-base rounded-full w-[14px] h-[14px] justify-center items-center text-orange-600 border border-orange-600">
?
</span>
);
}
}}
>
{pluginActionTips.value}
</NTooltip>
</div>
);
}
}}
>
<NInput
type="textarea"
value={getConfigValue()}
onUpdateValue={(value) => {
pluginConfig.config = value;
}}
placeholder={pluginActionTips.value}
rows={4}
/>
</NFormItem>
);
}),
useFormHelp([{
content: '<span class="text-[1.4rem] text-[#FF4314]">未来版本将取消自定义参数填写。</span>',
isHtml: true,
}])
);
}
break;
default:
break;
}
return items;
});
// 切换类型时,重置表单
watch(
() => param.value.type,
(newVal) => {
switch (newVal) {
case "ssh":
param.value.config = {
host: "",
port: 22,
user: "root",
mode: "password",
password: "",
} as SshAccessConfig;
break;
case "1panel":
param.value.config = {
url: "",
api_key: "",
ignore_ssl: "0",
version: "v1", // 默认选择 v1 版本
} as PanelAccessConfig;
break;
case "btpanel":
case "btwaf":
param.value.config = {
url: "",
api_key: "",
ignore_ssl: "0",
};
break;
case "aliyun":
param.value.config = {
access_key_id: "",
access_key_secret: "",
};
break;
case "baidu":
case "huaweicloud":
param.value.config = {
access_key: "",
secret_key: "",
};
break;
case "cloudflare":
param.value.config = {
email: "",
api_key: "",
};
break;
case "tencentcloud":
param.value.config = {
secret_id: "",
secret_key: "",
};
break;
case "edgeone":
param.value.config = {
secret_id: "",
secret_key: "",
};
break;
case "godaddy":
param.value.config = {
api_key: "",
api_secret: "",
};
break;
case "qiniu":
param.value.config = {
access_key: "",
access_secret: "",
};
break;
case "namecheap":
param.value.config = {
api_user: "",
api_key: "",
} as NamecheapAccessConfig;
break;
case "ns1":
param.value.config = {
api_key: "",
} as NS1AccessConfig;
break;
case "cloudns":
param.value.config = {
auth_id: "",
auth_password: "",
} as CloudnsAccessConfig;
break;
case "aws":
param.value.config = {
access_key_id: "",
secret_access_key: "",
region: "",
} as AwsAccessConfig;
break;
case "azure":
param.value.config = {
tenant_id: "",
client_id: "",
client_secret: "",
environment: "",
} as AzureAccessConfig;
break;
case "namesilo":
param.value.config = {
api_key: "",
} as NamesiloAccessConfig;
break;
case "namedotcom":
param.value.config = {
username: "",
api_token: "",
} as NamedotcomAccessConfig;
break;
case "bunny":
param.value.config = {
api_key: "",
} as BunnyAccessConfig;
break;
case "gcore":
param.value.config = {
api_token: "",
} as GcoreAccessConfig;
break;
case "jdcloud":
param.value.config = {
access_key_id: "",
secret_access_key: "",
} as JdcloudAccessConfig;
break;
case "lecdn":
param.value.config = {
url: "",
username: "",
password: "",
ignore_ssl: "0",
} as LecdnAccessConfig;
break;
case "constellix":
param.value.config = {
api_key: "",
secret_key: "",
} as ConstellixAccessConfig;
break;
case "doge":
param.value.config = {
access_key: "",
secret_key: "",
} as DogeAccessConfig;
break;
case "webhook":
param.value.config = {
url: "",
method: "POST",
headers: "Content-Type: application/json",
data: "",
ignore_ssl: "0",
} as WebhookAccessConfig;
break;
case "spaceship":
param.value.config = {
api_key: "",
api_secret: "",
} as SpaceshipAccessConfig;
break;
case "btdomain":
param.value.config = {
access_key: "",
secret_key: "",
account_id: "",
} as BTDomainAccessConfig;
break;
case "plugin":
param.value.config = {
name: pluginList.value[0]?.value || "",
mode: "default",
config: {},
} as PluginAccessConfig;
break;
}
}
);
// 获取插件列表
const loadPlugins = async (): Promise<void> => {
try {
const { data } = await getPlugins().fetch();
if (data && Array.isArray(data)) {
// 处理插件数据,将每个插件的 actions 展开为选项
const pluginOptions: Array<PluginOption> = [];
data.forEach(
(plugin: {
name: string;
actions: { name: string; description: string }[];
params?: string;
config?: any;
}) => {
// 如果没有 actions直接使用插件名称
pluginOptions.push({
label: plugin.name,
value: plugin.name,
description: plugin.actions
.map((action: { description: string }) => action.description)
.join("、"),
pluginName: plugin.name,
config: plugin.config,
});
}
);
pluginList.value = pluginOptions;
pluginActionTips.value = renderPluginTips(
pluginOptions[0]?.config || {}
);
}
} catch (error) {
console.error($t("t_1_1750210699272"), error);
}
};
/**
* 渲染单选标签
* @param {Record<string, any>} option - 选项
* @returns {VNode} 渲染后的VNode
*/
const renderSingleSelectTag = ({ option }: Record<string, any>): VNode => {
return (
<NFlex class="w-full">
{option.label ? (
renderTypeLabel(option)
) : (
<span class="text-[1.4rem] text-gray-400">
{$t("t_0_1745833934390")}
</span>
)}
</NFlex>
);
};
/**
* 类型选项接口
*/
interface TypeOption {
label: string;
value: string;
access: string[];
}
/**
* 渲染类型选择器标签
* @param {TypeOption} option - 类型选项
* @returns {VNode} 渲染后的VNode
*/
const renderTypeLabel = (option: TypeOption): VNode => {
const accessTypeMap = {
dns: $t("t_3_1745735765112"),
host: $t("t_0_1746754500246"),
plugin: "插件",
};
return (
<NFlex align="center" size="small" class="w-full py-1">
<SvgIcon icon={`resources-${option.value}`} size="1.6rem" />
<NText class="flex-1">{option.label}</NText>
{option.access && option.access.length > 0 && (
<NFlex size="small" class="ml-auto">
{option.access.map((type: string) => (
<NTag
key={type}
type={type === "dns" ? "success" : "info"}
size="small"
>
{accessTypeMap[type as keyof typeof accessTypeMap] || type}
</NTag>
))}
</NFlex>
)}
</NFlex>
);
};
/**
* 渲染插件标签
* @param {Record<string, any>} option - 选项
* @returns {VNode} 渲染后的VNode
*/
const renderPluginLabel = (option: PluginOption): VNode => {
return (
<NFlex justify="space-between" class="w-[38rem]">
<NFlex size="small" vertical={true} class="py-[8px]">
<div class="flex items-center gap-2">
<SvgIcon icon={`resources-${option.value}`} size="1.6rem" />
<NText class="text-[var(--color-card-title)]">{option.label}</NText>
</div>
<div title={option.description}>
{option.description && (
<div class="text-[1.2rem] text-color5 mt-[0.2rem]">
{option.description}
</div>
)}
</div>
</NFlex>
</NFlex>
);
};
/**
* 渲染插件选中标签
* @param props - 属性对象
* @returns {VNode} 渲染后的VNode
*/
const renderPluginTag = (props: { option: PluginOption }): VNode => {
const { option } = props;
return (
<NFlex class="w-full">
{option?.label ? (
<NFlex align="center" size="small">
<SvgIcon icon={`resources-${option.value}`} size="1.4rem" />
<NText>{option.label}</NText>
</NFlex>
) : (
<span class="text-[1.4rem] text-gray-400">
{$t("t_2_1750210698518")}
</span>
)}
</NFlex>
);
};
/**
* 提交授权API表单
* @param {UpdateAccessParams | AddAccessParams} param 请求参数
* @param {Ref<FormInst>} formRef 表单实例
*/
const submitApiManageForm = async (
param: UpdateAccessParams | AddAccessParams
): Promise<void> => {
try {
let data: UpdateAccessParams<string> | AddAccessParams<string>;
if (param.type === "plugin") {
let finalConfig;
if (typeof param.config.config === 'string') {
finalConfig = {
name: param.config.name,
config: JSON.parse(param.config.config)
};
} else {
finalConfig = {
name: param.config.name,
config: param.config.config
};
}
data = {
...param,
config: JSON.stringify(finalConfig),
} as UpdateAccessParams<string>;
} else {
data = {
...param,
config: JSON.stringify(param.config),
} as UpdateAccessParams<string>;
}
if ("id" in data) {
const { id, name, config } = data;
await updateExistingAccess({
id: id.toString(),
name,
config,
} as UpdateAccessParams<string>);
} else {
await addNewAccess(data as AddAccessParams<string>);
}
} catch (error) {
throw handleError(new Error($t("t_4_1746667590873")));
}
};
/**
* @description 渲染插件tips
* @param {PluginOption} option - 插件选项
* @returns {VNode} 渲染后的VNode
*/
const renderPluginTips = (tips: Record<string, any>): string => {
return $t("t_3_1750210706775") + JSON.stringify(tips || {});
};
// 使用表单hooks
const { component: ApiManageForm, fetch, validate } = useForm({
config,
defaultValue: param,
request: submitApiManageForm,
rules: rules as any,
});
// 关联确认按钮
confirm(async (close) => {
try {
// 先进行表单校验
await validate();
openLoad();
await fetch();
resetApiForm();
close();
} catch (error) {
return handleError(error);
} finally {
closeLoad();
}
});
// 组件初始化时获取插件列表
onMounted(loadPlugins);
return {
ApiManageForm,
};
};