【新增】百度云DNS解析功能和宝塔waf部署功能

【新增】本机部署方式
【修复】已知问题
This commit is contained in:
chudong
2025-05-15 10:42:24 +08:00
parent e6947ec5c4
commit 151cec10a0
129 changed files with 28549 additions and 9082 deletions

View File

@@ -10,7 +10,17 @@ interface DnsProviderOption {
type: string
}
type DnsProviderType = 'btpanel' | 'aliyun' | 'ssh' | 'tencentcloud' | '1panel' | 'dns' | ''
type DnsProviderType =
| 'aliyun'
| 'tencentcloud'
| 'baidu'
| 'huaweicloud'
| 'cloudflare'
| 'dns'
| 'btpanel'
| '1panel'
| 'ssh'
| ''
interface DnsProviderSelectProps {
// 表单类型,用于获取不同的下拉列表

View File

@@ -1,4 +1,17 @@
import { NButton, NCard, NStep, NSteps, NText, NTooltip } from 'naive-ui'
import {
NButton,
NCard,
NStep,
NSteps,
NText,
NTooltip,
NTabs,
NTabPane,
NInput,
NDivider,
NFormItem,
NSwitch,
} from 'naive-ui'
import { useForm, useFormHooks, useModalClose, useModalOptions, useMessage } from '@baota/naive-ui/hooks'
import { useThemeCssVar } from '@baota/naive-ui/theme'
import { useError } from '@baota/hooks/error'
@@ -7,6 +20,7 @@ import { DeployNodeConfig, DeployNodeInputsConfig } from '@components/flowChart/
import { $t } from '@locales/index'
import SvgIcon from '@components/svgIcon'
import DnsProviderSelect from '@/components/dnsProviderSelect'
import SearchOutlined from '@vicons/antd/es/SearchOutlined'
import styles from './index.module.css'
import verifyRules from './verify'
@@ -30,6 +44,7 @@ export default defineComponent({
fromNodeId: '',
name: '',
},
skip: 1,
},
}),
},
@@ -51,47 +66,75 @@ export default defineComponent({
// 部署类型选项
const deployTypeOptions = [
{ label: $t('t_5_1744958839222'), value: 'ssh' },
{ label: $t('t_10_1745735765165'), value: 'btpanel' },
{ label: $t('t_11_1745735766456'), value: 'btpanel-site' },
{ label: $t('t_12_1745735765571'), value: '1panel' },
{ label: $t('t_13_1745735766084'), value: '1panel-site' },
{ label: $t('t_14_1745735766121'), value: 'tencentcloud-cdn' },
{ label: $t('t_15_1745735768976'), value: 'tencentcloud-cos' },
{ label: $t('t_16_1745735766712'), value: 'aliyun-cdn' },
{ label: $t('t_2_1746697487164'), value: 'aliyun-oss' },
{ label: $t('t_6_1747271296994'), value: 'localhost', category: 'host', icon: 'ssh' },
{ label: $t('t_5_1744958839222'), value: 'ssh', category: 'host', icon: 'ssh' },
{ label: $t('t_10_1745735765165'), value: 'btpanel', category: 'btpanel', icon: 'btpanel' },
{ label: $t('t_11_1745735766456'), value: 'btpanel-site', category: 'btpanel', icon: 'btpanel' },
{ label: $t('t_0_1747215751189'), value: 'btwaf-site', category: 'btpanel', icon: 'btpanel' },
{ label: $t('t_12_1745735765571'), value: '1panel', category: '1panel', icon: '1panel' },
{ label: $t('t_13_1745735766084'), value: '1panel-site', category: '1panel', icon: '1panel' },
{ label: $t('t_14_1745735766121'), value: 'tencentcloud-cdn', category: 'tencentcloud', icon: 'tencentcloud' },
{ label: $t('t_15_1745735768976'), value: 'tencentcloud-cos', category: 'tencentcloud', icon: 'tencentcloud' },
{ label: $t('t_16_1745735766712'), value: 'aliyun-cdn', category: 'aliyun', icon: 'aliyun' },
{ label: $t('t_2_1746697487164'), value: 'aliyun-oss', category: 'aliyun', icon: 'aliyun' },
]
const certOptions = ref<{ label: string; value: string }[]>([]) // 证书选项
const current = ref(1) // 当前步骤
const next = ref(true) // 是否是下一步
const currentStatus = ref<StepStatus>('process') // 当前步骤状态
const currentTab = ref('all') // 当前选中的tab
const searchKeyword = ref('') // 搜索关键字
const param = ref(deepClone(props.node.config)) // 表单参数
const localProvider = ref([{ label: $t('本机部署'), value: 'localhost' }]) // 本地提供商
const provider = computed(() => {
return param.value.provider
? $t('t_4_1746858917773') + '' + deployTypeOptions.find((item) => item.value === param.value.provider)?.label
: $t('t_19_1745735766810')
})
// 过滤后的部署类型选项
const filteredDeployTypes = computed(() => {
let filtered = deployTypeOptions
// 根据标签过滤
if (currentTab.value !== 'all') {
filtered = filtered.filter((item) => item.category === currentTab.value)
}
// 根据搜索关键词过滤
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
filtered = filtered.filter(
(item) => item.label.toLowerCase().includes(keyword) || item.value.toLowerCase().includes(keyword),
)
}
return filtered
})
// 表单配置
const formConfig = computed(() => {
const config = []
config.push(
...[
{
type: 'custom' as const,
render: () => {
return (
<DnsProviderSelect
type={param.value.provider}
path="provider_id"
value={param.value.provider_id}
onUpdate:value={(val: { value: number; type: string }) => {
param.value.provider_id = val.value
}}
/>
)
},
},
param.value.provider !== 'localhost'
? {
type: 'custom' as const,
render: () => {
return (
<DnsProviderSelect
type={param.value.provider}
path="provider_id"
value={param.value.provider_id}
onUpdate:value={(val: { value: number; type: string }) => {
param.value.provider_id = val.value
}}
/>
)
},
}
: useFormSelect($t('主机提供商'), 'provider', localProvider.value, { disabled: true }),
],
useFormSelect($t('t_1_1745748290291'), 'inputs.fromNodeId', certOptions.value, {
onUpdateValue: (val, option: { label: string; value: string }) => {
@@ -101,36 +144,38 @@ export default defineComponent({
}),
)
switch (param.value.provider) {
case 'localhost':
case 'ssh':
config.push(
...[
useFormInput('证书文件路径仅支持PEM格式', 'certPath', {
useFormInput($t('证书文件路径仅支持PEM格式'), 'certPath', {
placeholder: $t('t_30_1746667591892'),
onInput: (val: string) => (param.value.certPath = val.trim()),
}),
useFormInput('私钥文件路径', 'keyPath', {
useFormInput($t('私钥文件路径'), 'keyPath', {
placeholder: $t('t_31_1746667593074'),
onInput: (val: string) => (param.value.keyPath = val.trim()),
}),
useFormTextarea(
'前置命令',
$t('前置命令(可选)'),
'beforeCmd',
{ placeholder: $t('t_21_1745735769154') },
{ placeholder: $t('t_21_1745735769154'), rows: 2 },
{ showRequireMark: false },
),
useFormTextarea(
'后置命令',
$t('后置命令(可选)'),
'afterCmd',
{ placeholder: $t('t_22_1745735767366') },
{ placeholder: $t('t_22_1745735767366'), rows: 2 },
{ showRequireMark: false },
),
],
)
break
case 'btwaf-site':
case 'btpanel-site':
config.push(
...[
useFormInput('站点名称', 'siteName', {
useFormInput($t('站点名称'), 'siteName', {
placeholder: $t('t_23_1745735766455'),
onInput: (val: string) => (param.value.siteName = val.trim()),
}),
@@ -140,7 +185,7 @@ export default defineComponent({
case '1panel-site':
config.push(
...[
useFormInput('站点ID', 'site_id', {
useFormInput($t('站点ID'), 'site_id', {
placeholder: $t('t_24_1745735766826'),
onInput: (val: string) => (param.value.site_id = val.trim()),
}),
@@ -151,7 +196,7 @@ export default defineComponent({
case 'aliyun-cdn':
config.push(
...[
useFormInput('域名', 'domain', {
useFormInput($t('域名'), 'domain', {
placeholder: $t('t_0_1744958839535'),
onInput: (val: string) => (param.value.domain = val.trim()),
}),
@@ -162,7 +207,7 @@ export default defineComponent({
case 'aliyun-oss':
config.push(
...[
useFormInput('域名', 'domain', {
useFormInput($t('域名'), 'domain', {
placeholder: $t('t_0_1744958839535'),
onInput: (val: string) => (param.value.domain = val.trim()),
}),
@@ -170,7 +215,7 @@ export default defineComponent({
)
config.push(
...[
useFormInput('区域', 'region', {
useFormInput($t('区域'), 'region', {
placeholder: $t('t_25_1745735766651'),
onInput: (val: string) => (param.value.region = val.trim()),
}),
@@ -178,7 +223,7 @@ export default defineComponent({
)
config.push(
...[
useFormInput('存储桶', 'bucket', {
useFormInput($t('存储桶'), 'bucket', {
placeholder: $t('t_26_1745735767144'),
onInput: (val: string) => (param.value.bucket = val.trim()),
}),
@@ -186,6 +231,25 @@ export default defineComponent({
)
break
}
config.push({
type: 'custom' as const,
render: () => {
return (
<NFormItem label={$t('重复部署')} path="skip">
<NText>{$t('当与上次部署的证书相同且上次部署成功时')}</NText>
<NSwitch
v-model:value={param.value.skip}
checkedValue={1}
uncheckedValue={0}
class="mx-[.5rem] "
v-slots={{ checked: () => $t('跳过'), unchecked: () => $t('不跳过') }}
/>
<NText>{$t('重新部署')}</NText>
</NFormItem>
)
},
})
return config
})
@@ -195,7 +259,11 @@ export default defineComponent({
*/
const nextStep = async () => {
if (!param.value.provider) return message.error($t('t_0_1746858920894'))
if (param.value.provider === 'localhost') {
delete param.value.provider_id
} else {
param.value.provider_id = props.node.config.provider_id
}
// 加载证书来源选项
certOptions.value = findApplyUploadNodesUp(props.node.id).map((item) => {
return { label: item.name, value: item.id }
@@ -240,11 +308,12 @@ export default defineComponent({
await example.value?.validate()
const tempData = param.value
const inputs = tempData.inputs
updateNode(props.node.id, { inputs: [inputs], config: {} }, false)
delete tempData.inputs
// 将输入值直接传递给updateNodeConfig
updateNodeConfig(props.node.id, {
...tempData,
})
// 单独更新inputs
updateNode(props.node.id, { inputs: [inputs] } as any, false)
isRefreshNode.value = props.node.id
closeModal()
} catch (error) {
@@ -256,6 +325,8 @@ export default defineComponent({
onMounted(() => {
// 隐藏底部按钮
modalOptions.value.footer = false
// 设置弹窗宽度和高度
modalOptions.value.area = [850, 600]
// 如果已经选择了部署类型,则跳转到下一步
if (param.value.provider) {
if (props.node.inputs) param.value.inputs = props.node.inputs[0]
@@ -270,25 +341,61 @@ export default defineComponent({
<NStep title={$t('t_29_1745735768933')} description={$t('t_2_1745738969878')}></NStep>
</NSteps>
{current.value === 1 && (
<div class={styles.cardContainer}>
{deployTypeOptions.map((item) => (
<div
key={item.value}
class={`${styles.optionCard} ${param.value.provider === item.value ? styles.optionCardSelected : ''}`}
onClick={() => {
param.value.provider = item.value
}}
<div class={styles.configContainer}>
<div class={styles.leftPanel}>
<NTabs
type="bar"
placement="left"
value={currentTab.value}
onUpdateValue={(val) => (currentTab.value = val)}
>
<NCard contentClass={styles.cardContent} hoverable bordered={false}>
<SvgIcon
icon={`resources-${item.value.replace(/-[a-z]+$/, '')}`}
size="2rem"
class={`${styles.icon} ${param.value.provider === item.value ? styles.iconSelected : ''}`}
/>
<NText type={param.value.provider === item.value ? 'primary' : 'default'}>{item.label}</NText>
</NCard>
<NTabPane name="all" tab={$t('t_7_1747271292060')} />
<NTabPane name="host" tab={$t('t_1_1745833931535')} />
<NTabPane name="btpanel" tab={$t('t_8_1747271290414')} />
<NTabPane name="1panel" tab={$t('t_9_1747271284765')} />
<NTabPane name="tencentcloud" tab={$t('t_3_1747019616129')} />
<NTabPane name="aliyun" tab={$t('t_2_1747019616224')} />
</NTabs>
</div>
<div class={styles.rightPanel}>
<div class={styles.searchBar}>
<NInput
value={searchKeyword.value}
onUpdateValue={(val) => (searchKeyword.value = val)}
placeholder={$t('搜索部署类型')}
clearable
>
{{
suffix: () => (
<div class="flex items-center">
<SearchOutlined class="text-[var(--text-color-3)] w-[1.6rem] cursor-pointer font-bold" />
</div>
),
}}
</NInput>
</div>
))}
<NDivider class="!my-[1rem]" />
<div class={styles.cardContainer}>
{filteredDeployTypes.value.map((item) => (
<div
key={item.value}
class={`${styles.optionCard} ${param.value.provider === item.value ? styles.optionCardSelected : ''}`}
onClick={() => {
param.value.provider = item.value
}}
>
<div class={styles.cardContent}>
<SvgIcon
icon={`resources-${item.icon.replace(/-[a-z]+$/, '')}`}
size="2rem"
class={`${styles.icon} ${param.value.provider === item.value ? styles.iconSelected : ''}`}
/>
<NText type={param.value.provider === item.value ? 'primary' : 'default'}>{item.label}</NText>
</div>
</div>
))}
</div>
</div>
</div>
)}
{current.value === 2 && (

View File

@@ -1,13 +1,35 @@
/* Deploy Node Drawer Styles */
/* 整体布局容器 */
.configContainer {
@apply flex mt-[2.4rem] gap-4;
}
/* 左侧面板样式 */
.leftPanel {
@apply border-r border-solid;
border-color: var(--n-border-color);
}
/* 右侧面板样式 */
.rightPanel {
@apply flex-1 flex flex-col;
}
/* 搜索栏样式 */
.searchBar {
@apply w-full mb-[1rem] px-[0.5rem];
}
/* Card container styles */
.cardContainer {
@apply grid grid-cols-3 gap-4 mt-[2.4rem];
@apply grid grid-cols-3 gap-4 mt-[0.5rem] overflow-y-auto px-[0.5rem] h-[28rem];
grid-auto-rows: min-content;
}
/* Option card styles */
.optionCard {
@apply flex items-center justify-center rounded-[0.4rem] transition-all border-[1px] border-transparent;
@apply flex items-center justify-center rounded-[0.4rem] transition-all border-[1px] border-transparent h-[6.2rem];
border-color: var(--n-border-color);
}
@@ -38,7 +60,7 @@
/* Card content styles */
.cardContent {
@apply flex flex-col items-center justify-center p-[4px] cursor-pointer;
@apply flex flex-col items-center justify-center p-[4px] cursor-pointer pt-[1rem] text-[1.3rem];
}
/* Icon styles */

View File

@@ -37,7 +37,7 @@ export default defineComponent({
// 提示内容
const verificationPrompt = computed(() => {
console.log(props.node.config.provider, 'validationResult')
console.log(validationResult.value, 'validationResult', props.node.config)
if (validationResult.value.valid) return <TypeIcon icon={props.node.config.provider} type="success" />
return $t('t_9_1745735765287')
})

View File

@@ -147,6 +147,7 @@ nodeOptions[DEPLOY] = () =>
config: {
provider: '',
provider_id: '',
skip: 1,
inputs: {
fromNodeId: '',
name: '',

View File

@@ -29,6 +29,7 @@ export default {
config: {
provider: '',
provider_id: '',
skip: 1,
inputs: {
fromNodeId: '',
name: '',

View File

@@ -231,10 +231,11 @@ export interface DeployConfig<
> {
provider: T
provider_id: string
skip: 1 | 0
[key: string]: Z
}
export interface DeployPanelConfig {}
// export interface DeployPanelConfig {}
// 部署节点配置ssh
export interface DeploySSHConfig {
@@ -244,6 +245,9 @@ export interface DeploySSHConfig {
afterCmd?: string // 后置命令
}
// 部署本地节点配置
export interface DeployLocalConfig extends DeploySSHConfig {}
// 部署节点配置(宝塔面板)
export interface DeployBTPanelConfig {
siteName: string
@@ -268,7 +272,7 @@ export interface DeployStorageConfig {
// 部署节点配置
export type DeployNodeConfig = DeployConfig<
DeploySSHConfig | DeployBTPanelConfig | Deploy1PanelConfig | DeployCDNConfig | DeployStorageConfig
DeploySSHConfig | DeployLocalConfig | DeployBTPanelConfig | Deploy1PanelConfig | DeployCDNConfig | DeployStorageConfig
>
// 部署节点输入配置

View File

@@ -4,12 +4,15 @@ import SvgIcon from '../svgIcon/index' // 注意修改引入路径以匹配实
// 定义支持的访问类型
const types = {
localhost: '本机部署',
ssh: 'SSH',
aliyun: '阿里云',
tencentcloud: '腾讯云',
btpanel: '宝塔面板',
'1panel': '1Panel',
huaweicloud: '华为云',
baidu: '百度云',
btpanel: '宝塔面板',
btwaf: '宝塔WAF',
'1panel': '1Panel',
cloudflare: 'Cloudflare',
mail: '邮件',
dingtalk: '钉钉',
@@ -56,10 +59,12 @@ export const AuthApiTypeIcon = defineComponent({
// 所有支持的类型直接映射到对应的资源名称
const iconMap: Record<string, string> = {
localhost: 'ssh',
ssh: 'ssh',
aliyun: 'aliyun',
tencentcloud: 'tencentcloud',
btpanel: 'btpanel',
btwaf: 'btpanel',
'1panel': '1panel',
huaweicloud: 'huaweicloud',
cloudflare: 'cloudflare',
@@ -74,6 +79,7 @@ export const AuthApiTypeIcon = defineComponent({
'aliyun-oss': 'aliyun',
'1panel-site': '1panel',
'btpanel-site': 'btpanel',
baidu: 'baidu',
}
// 返回匹配的图标路径或默认图标