From e5ef6792d12ba7fe757a1f8d72be5225fa0c7b94 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 5 Aug 2026 17:16:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E7=BD=91=E5=8D=A1?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E7=94=9F=E6=88=90=E5=99=A8?= =?UTF-8?q?(ifcfg/NM=20keyfile/Netplan)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 3 个网络配置生成器,覆盖 CentOS/RHEL 7-9 + Ubuntu 18.04-24.04: - network-rhel-ifcfg: RHEL/CentOS 7-8 ifcfg 风格(network-scripts) - network-rhel-nmkeyfile: RHEL/CentOS 9 NetworkManager keyfile - network-ubuntu-netplan: Ubuntu Netplan YAML - 每个生成器支持 5 种配置类型: 静态IP / DHCP / Bond / Bridge / VLAN - Bond 支持 7 种模式(0-6),含 miimon/lacp_rate/成员网卡配置 - 支持路由 Metric 配置(ifcfg METRIC= / NM route-metric= / Netplan metric:) - ConfigForm 增加 valueIn 依赖支持(修复 keepalived 已有但失效的 valueIn bug) - 新增「网络配置」侧边栏分类,组件计数 60 -> 63 --- package-lock.json | 39 --- src/components/ConfigForm.vue | 1 + src/components/Sidebar.vue | 2 +- src/schemas/index.js | 18 + src/schemas/network-rhel-ifcfg.js | 458 ++++++++++++++++++++++++++ src/schemas/network-rhel-nmkeyfile.js | 430 ++++++++++++++++++++++++ src/schemas/network-ubuntu-netplan.js | 329 ++++++++++++++++++ 7 files changed, 1237 insertions(+), 40 deletions(-) create mode 100644 src/schemas/network-rhel-ifcfg.js create mode 100644 src/schemas/network-rhel-nmkeyfile.js create mode 100644 src/schemas/network-ubuntu-netplan.js diff --git a/package-lock.json b/package-lock.json index 5ad1e1b..c47a320 100644 --- a/package-lock.json +++ b/package-lock.json @@ -644,9 +644,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -660,9 +657,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -676,9 +670,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -692,9 +683,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -708,9 +696,6 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -724,9 +709,6 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -740,9 +722,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -756,9 +735,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -772,9 +748,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -788,9 +761,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -804,9 +774,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -820,9 +787,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -836,9 +800,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/src/components/ConfigForm.vue b/src/components/ConfigForm.vue index b9177b9..8d0407f 100644 --- a/src/components/ConfigForm.vue +++ b/src/components/ConfigForm.vue @@ -141,6 +141,7 @@ function isFieldVisible(field) { const dep = field.dependsOn const val = formData.value[dep.key] if ('value' in dep) return val === dep.value + if ('valueIn' in dep) return dep.valueIn.includes(val) if ('valueNotIn' in dep) return !dep.valueNotIn.includes(val) return true } diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 9df45f9..2bd549c 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -60,7 +60,7 @@ diff --git a/src/schemas/index.js b/src/schemas/index.js index 709091c..8d7543c 100644 --- a/src/schemas/index.js +++ b/src/schemas/index.js @@ -95,6 +95,11 @@ import { sersyncSchema, generateSersyncXml } from './sersync' import { vsftpdSchema, generateVsftpdConf } from './vsftpd' import { subversionSchema, generateSubversionConf } from './subversion' +// ============ 网络配置 ============ +import { networkRhelIfcfgSchema, generateNetworkRhelIfcfg } from './network-rhel-ifcfg' +import { networkRhelNmKeyfileSchema, generateNetworkRhelNmKeyfile } from './network-rhel-nmkeyfile' +import { networkUbuntuNetplanSchema, generateNetworkUbuntuNetplan } from './network-ubuntu-netplan' + // ============ 数据库中间件 ============ import { mycatSchema, generateMycatServerXml } from './mycat' @@ -183,6 +188,10 @@ export const schemas = { subversion: subversionSchema, // 数据库中间件 mycat: mycatSchema, + // 网络配置 + 'network-rhel-ifcfg': networkRhelIfcfgSchema, + 'network-rhel-nmkeyfile': networkRhelNmKeyfileSchema, + 'network-ubuntu-netplan': networkUbuntuNetplanSchema, } // ============================================================ @@ -254,6 +263,10 @@ export const generators = { subversion: generateSubversionConf, // 数据库中间件 mycat: generateMycatServerXml, + // 网络配置 + 'network-rhel-ifcfg': generateNetworkRhelIfcfg, + 'network-rhel-nmkeyfile': generateNetworkRhelNmKeyfile, + 'network-ubuntu-netplan': generateNetworkUbuntuNetplan, } // ============================================================ @@ -351,6 +364,11 @@ export const categories = [ icon: 'Connection', items: ['keepalived', 'haproxy', 'lvs'], }, + { + name: '网络配置', + icon: 'Connection', + items: ['network-rhel-ifcfg', 'network-rhel-nmkeyfile', 'network-ubuntu-netplan'], + }, { name: '系统服务', icon: 'Setting', diff --git a/src/schemas/network-rhel-ifcfg.js b/src/schemas/network-rhel-ifcfg.js new file mode 100644 index 0000000..a0e9ad4 --- /dev/null +++ b/src/schemas/network-rhel-ifcfg.js @@ -0,0 +1,458 @@ +// RHEL / CentOS 7-8 ifcfg 风格网络配置 +// 覆盖: CentOS 7, RHEL 7, CentOS 8/Stream 8, RHEL 8, Rocky 8, AlmaLinux 8 +// 配置文件位于 /etc/sysconfig/network-scripts/ifcfg- + +export const networkRhelIfcfgSchema = { + id: 'network-rhel-ifcfg', + name: 'RHEL/CentOS ifcfg', + icon: 'Connection', + category: '网络配置', + description: 'RHEL/CentOS 7-8 ifcfg 网络脚本(含 Bond/Bridge/VLAN)', + format: 'conf', + fileName: 'ifcfg-eth0', + groups: [ + { + title: '系统版本', + fields: [ + { + key: 'osVersion', + label: '操作系统版本', + type: 'select', + options: [ + { label: 'CentOS 7', value: 'centos7' }, + { label: 'RHEL 7', value: 'rhel7' }, + { label: 'CentOS 8 / Stream 8', value: 'centos8' }, + { label: 'RHEL 8', value: 'rhel8' }, + { label: 'Rocky Linux 8 / AlmaLinux 8', value: 'el8-clone' }, + ], + default: 'centos7', + tip: '影响配置文件注释与建议说明,ifcfg 语法本身通用', + }, + ], + }, + { + title: '配置类型', + fields: [ + { + key: 'configType', + label: '配置类型', + type: 'select', + options: [ + { label: '单网卡 - 静态 IP', value: 'static' }, + { label: '单网卡 - DHCP', value: 'dhcp' }, + { label: 'Bond - 链路聚合', value: 'bond' }, + { label: 'Bridge - 桥接', value: 'bridge' }, + { label: 'VLAN - 子接口', value: 'vlan' }, + ], + default: 'static', + }, + ], + }, + { + title: '主接口配置', + fields: [ + { + key: 'interface', + label: '主接口名', + type: 'text', + default: 'eth0', + tip: 'ifcfg-<此名称>,例如 eth0 / ens33 / enp0s3', + }, + { + key: 'bootproto', + label: '协议', + type: 'select', + options: [ + { label: 'static / none - 静态', value: 'none' }, + { label: 'dhcp - 自动获取', value: 'dhcp' }, + ], + default: 'none', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'ipaddr', + label: 'IP 地址', + type: 'text', + default: '192.168.1.10', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'prefix', + label: '子网前缀 (CIDR)', + type: 'number', + min: 1, + max: 32, + default: 24, + tip: '例如 24 = 255.255.255.0', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'gateway', + label: '默认网关', + type: 'text', + default: '192.168.1.1', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'dns1', + label: '主 DNS', + type: 'text', + default: '8.8.8.8', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'dns2', + label: '备用 DNS', + type: 'text', + default: '8.8.4.4', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'routeMetric', + label: '路由 Metric', + type: 'number', + min: 0, + max: 9999, + default: 0, + tip: '默认路由优先级,值越小优先级越高;0 表示不输出', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'onboot', + label: '开机自动启用', + type: 'switch', + default: true, + }, + { + key: 'ipv6', + label: '禁用 IPv6', + type: 'switch', + default: true, + tip: '在 ifcfg 中写入 IPV6INIT=no', + }, + ], + }, + { + title: 'Bond 配置', + fields: [ + { + key: 'bondName', + label: 'Bond 接口名', + type: 'text', + default: 'bond0', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'bondMode', + label: 'Bond 模式', + type: 'select', + options: [ + { label: '0 - balance-rr (轮询)', value: '0' }, + { label: '1 - active-backup (主备)', value: '1' }, + { label: '2 - balance-xor (XOR)', value: '2' }, + { label: '3 - broadcast (广播)', value: '3' }, + { label: '4 - 802.3ad (LACP 动态聚合)', value: '4' }, + { label: '5 - balance-tlb (自适应发送负载)', value: '5' }, + { label: '6 - balance-alb (自适应负载均衡)', value: '6' }, + ], + default: '4', + tip: '模式 4 需要交换机端开启 LACP;模式 1 兼容性最好', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'miimon', + label: 'MII 监控间隔 (ms)', + type: 'number', + min: 0, + max: 10000, + default: 100, + tip: '链路状态检测间隔,0 表示关闭', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'lacpRate', + label: 'LACP 速率', + type: 'select', + options: [ + { label: 'slow (默认 30s)', value: 'slow' }, + { label: 'fast (1s)', value: 'fast' }, + ], + default: 'fast', + tip: '仅 mode=4 (802.3ad) 生效', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'bondSlaves', + label: '成员网卡 (逗号分隔)', + type: 'text', + default: 'eth0,eth1', + tip: '会被设为 SLAVE=yes,MASTER=', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'primarySlave', + label: '主网卡 (可选)', + type: 'text', + default: '', + placeholder: '留空使用默认', + tip: '仅 mode=1 active-backup 时作为活动网卡,可留空', + dependsOn: { key: 'configType', value: 'bond' }, + }, + ], + }, + { + title: 'Bridge 配置', + fields: [ + { + key: 'bridgeName', + label: 'Bridge 接口名', + type: 'text', + default: 'br0', + dependsOn: { key: 'configType', value: 'bridge' }, + }, + { + key: 'bridgePorts', + label: '桥接成员 (逗号分隔)', + type: 'text', + default: 'eth0', + tip: '成员网卡会写成 BRIDGE=', + dependsOn: { key: 'configType', value: 'bridge' }, + }, + { + key: 'bridgeStp', + label: '启用 STP 生成树', + type: 'switch', + default: false, + dependsOn: { key: 'configType', value: 'bridge' }, + }, + ], + }, + { + title: 'VLAN 配置', + fields: [ + { + key: 'vlanParent', + label: '父接口', + type: 'text', + default: 'eth0', + tip: '例如 eth0,生成的子接口为 eth0.', + dependsOn: { key: 'configType', value: 'vlan' }, + }, + { + key: 'vlanId', + label: 'VLAN ID', + type: 'number', + min: 1, + max: 4094, + default: 100, + dependsOn: { key: 'configType', value: 'vlan' }, + }, + ], + }, + ], +} + +function genIfcfgFile(fileName, fields, header) { + const lines = [] + if (header) lines.push(header) + for (const [k, v] of Object.entries(fields)) { + if (v === undefined || v === null) continue + lines.push(`${k}=${v}`) + } + return lines.join('\n') +} + +export function generateNetworkRhelIfcfg(config) { + const out = [] + const ts = new Date().toLocaleString('zh-CN') + out.push(`# =========================================================`) + out.push(`# RHEL/CentOS ifcfg 网络配置 - 由 ConfTemplate 生成`) + out.push(`# 生成时间: ${ts}`) + out.push(`# 目标目录: /etc/sysconfig/network-scripts/`) + out.push(`# 配置类型: ${config.configType}`) + out.push(`# =========================================================`) + out.push(``) + out.push(`# 应用步骤:`) + out.push(`# 1. 将下面每个 ifcfg-xxx 内容保存到对应文件`) + out.push(`# 2. 执行: systemctl restart network # CentOS 7`) + out.push(`# nmcli connection reload # CentOS 8+ (NetworkManager)`) + out.push(`# nmcli connection up `) + out.push(``) + out.push(`# -----------------------------------------------------------`) + + const ipv6Line = config.ipv6 ? 'IPV6INIT=no' : null + + if (config.configType === 'static' || config.configType === 'dhcp') { + const bootproto = config.configType === 'dhcp' ? 'dhcp' : (config.bootproto || 'none') + const base = { + TYPE: 'Ethernet', + PROXY_METHOD: 'none', + BROWSER_ONLY: 'no', + BOOTPROTO: bootproto, + DEFROUTE: 'yes', + IPV4_FAILURE_FATAL: 'no', + NAME: config.interface, + DEVICE: config.interface, + ONBOOT: config.onboot ? 'yes' : 'no', + } + if (config.ipv6) base.IPV6INIT = 'no' + if (config.configType === 'static') { + Object.assign(base, { + IPADDR: config.ipaddr, + PREFIX: String(config.prefix), + GATEWAY: config.gateway, + DNS1: config.dns1, + DNS2: config.dns2, + }) + if (config.routeMetric) base.METRIC = String(config.routeMetric) + } + out.push(``) + out.push(`# >>> 文件: ifcfg-${config.interface}`) + out.push(`# >>> 接口 ${config.interface} (${config.configType === 'dhcp' ? 'DHCP' : '静态 IP'})`) + out.push(genIfcfgFile(`ifcfg-${config.interface}`, base)) + out.push(``) + } + + if (config.configType === 'bond') { + // bond 主接口 + const bondOpts = [`mode=${config.bondMode}`, `miimon=${config.miimon}`] + if (config.bondMode === '4') { + bondOpts.push(`lacp_rate=${config.lacpRate}`) + } + if (config.primarySlave) { + bondOpts.push(`primary=${config.primarySlave}`) + } + const master = { + TYPE: 'Bond', + BONDING_MASTER: 'yes', + PROXY_METHOD: 'none', + BROWSER_ONLY: 'no', + BOOTPROTO: config.bootproto || 'none', + DEFROUTE: 'yes', + IPV4_FAILURE_FATAL: 'no', + NAME: config.bondName, + DEVICE: config.bondName, + ONBOOT: config.onboot ? 'yes' : 'no', + BONDING_OPTS: `"${bondOpts.join(' ')}"`, + IPADDR: config.ipaddr, + PREFIX: String(config.prefix), + GATEWAY: config.gateway, + DNS1: config.dns1, + DNS2: config.dns2, + } + if (config.ipv6) master.IPV6INIT = 'no' + if (config.routeMetric) master.METRIC = String(config.routeMetric) + out.push(``) + out.push(`# >>> 文件: ifcfg-${config.bondName}`) + out.push(`# >>> Bond 主接口 ${config.bondName} (mode=${config.bondMode})`) + out.push(genIfcfgFile(`ifcfg-${config.bondName}`, master)) + out.push(``) + + // 成员接口 + const slaves = String(config.bondSlaves || '').split(',').map((s) => s.trim()).filter(Boolean) + for (const slv of slaves) { + const slave = { + TYPE: 'Ethernet', + PROXY_METHOD: 'none', + BROWSER_ONLY: 'no', + BOOTPROTO: 'none', + DEFROUTE: 'no', + IPV4_FAILURE_FATAL: 'no', + NAME: slv, + DEVICE: slv, + ONBOOT: config.onboot ? 'yes' : 'no', + MASTER: config.bondName, + SLAVE: 'yes', + } + if (config.ipv6) slave.IPV6INIT = 'no' + out.push(`# >>> 文件: ifcfg-${slv}`) + out.push(`# >>> Bond 成员 ${slv} → ${config.bondName}`) + out.push(genIfcfgFile(`ifcfg-${slv}`, slave)) + out.push(``) + } + out.push(`# 提示: mode=4 (LACP) 需要在交换机端开启 LACP 动态聚合`) + } + + if (config.configType === 'bridge') { + // bridge 主接口 + const br = { + TYPE: 'Bridge', + PROXY_METHOD: 'none', + BROWSER_ONLY: 'no', + BOOTPROTO: config.bootproto || 'none', + DEFROUTE: 'yes', + IPV4_FAILURE_FATAL: 'no', + NAME: config.bridgeName, + DEVICE: config.bridgeName, + ONBOOT: config.onboot ? 'yes' : 'no', + STP: config.bridgeStp ? 'yes' : 'no', + IPADDR: config.ipaddr, + PREFIX: String(config.prefix), + GATEWAY: config.gateway, + DNS1: config.dns1, + DNS2: config.dns2, + } + if (config.ipv6) br.IPV6INIT = 'no' + if (config.routeMetric) br.METRIC = String(config.routeMetric) + out.push(``) + out.push(`# >>> 文件: ifcfg-${config.bridgeName}`) + out.push(`# >>> Bridge ${config.bridgeName} (STP=${config.bridgeStp ? 'on' : 'off'})`) + out.push(genIfcfgFile(`ifcfg-${config.bridgeName}`, br)) + out.push(``) + + // 成员接口 + const ports = String(config.bridgePorts || '').split(',').map((s) => s.trim()).filter(Boolean) + for (const p of ports) { + const port = { + TYPE: 'Ethernet', + PROXY_METHOD: 'none', + BROWSER_ONLY: 'no', + BOOTPROTO: 'none', + DEFROUTE: 'no', + IPV4_FAILURE_FATAL: 'no', + NAME: p, + DEVICE: p, + ONBOOT: config.onboot ? 'yes' : 'no', + BRIDGE: config.bridgeName, + } + if (config.ipv6) port.IPV6INIT = 'no' + out.push(`# >>> 文件: ifcfg-${p}`) + out.push(`# >>> Bridge 成员 ${p} → ${config.bridgeName}`) + out.push(genIfcfgFile(`ifcfg-${p}`, port)) + out.push(``) + } + } + + if (config.configType === 'vlan') { + const subName = `${config.vlanParent}.${config.vlanId}` + const vlan = { + TYPE: 'Ethernet', + PROXY_METHOD: 'none', + BROWSER_ONLY: 'no', + BOOTPROTO: config.bootproto || 'none', + DEFROUTE: 'yes', + IPV4_FAILURE_FATAL: 'no', + NAME: subName, + DEVICE: subName, + ONBOOT: config.onboot ? 'yes' : 'no', + VLAN: 'yes', + IPADDR: config.ipaddr, + PREFIX: String(config.prefix), + GATEWAY: config.gateway, + DNS1: config.dns1, + DNS2: config.dns2, + } + if (config.ipv6) vlan.IPV6INIT = 'no' + if (config.routeMetric) vlan.METRIC = String(config.routeMetric) + out.push(``) + out.push(`# >>> 文件: ifcfg-${subName}`) + out.push(`# >>> VLAN 子接口 ${subName} (VLAN ID=${config.vlanId})`) + out.push(genIfcfgFile(`ifcfg-${subName}`, vlan)) + out.push(``) + out.push(`# 提示: 父接口 ${config.vlanParent} 也需要 ONBOOT=yes 并处于 up 状态`) + out.push(`# 若使用基于接口名的隐式 VLAN,VLAN=yes 即可;`) + out.push(`# 若用显式 VLAN_ID,可增加: PHYSDEV=${config.vlanParent}, VLAN_ID=${config.vlanId}`) + } + + return out.join('\n') +} diff --git a/src/schemas/network-rhel-nmkeyfile.js b/src/schemas/network-rhel-nmkeyfile.js new file mode 100644 index 0000000..191c92d --- /dev/null +++ b/src/schemas/network-rhel-nmkeyfile.js @@ -0,0 +1,430 @@ +// RHEL / CentOS 9 NetworkManager keyfile 风格网络配置 +// 覆盖: CentOS Stream 9, RHEL 9, Rocky Linux 9, AlmaLinux 9 +// 配置文件位于 /etc/NetworkManager/system-connections/.nmconnection +// 说明: RHEL 9 / CentOS Stream 9 已弃用 network-scripts (ifcfg-*),必须使用 NM keyfile + +export const networkRhelNmKeyfileSchema = { + id: 'network-rhel-nmkeyfile', + name: 'RHEL/CentOS 9 NM', + icon: 'Connection', + category: '网络配置', + description: 'RHEL/CentOS 9 NetworkManager keyfile(含 Bond/Bridge/VLAN)', + format: 'ini', + fileName: 'connection.nmconnection', + groups: [ + { + title: '系统版本', + fields: [ + { + key: 'osVersion', + label: '操作系统版本', + type: 'select', + options: [ + { label: 'CentOS Stream 9', value: 'centos-stream9' }, + { label: 'RHEL 9', value: 'rhel9' }, + { label: 'Rocky Linux 9', value: 'rocky9' }, + { label: 'AlmaLinux 9', value: 'alma9' }, + ], + default: 'centos-stream9', + tip: '语法相同,仅影响注释与说明', + }, + ], + }, + { + title: '配置类型', + fields: [ + { + key: 'configType', + label: '配置类型', + type: 'select', + options: [ + { label: '单网卡 - 静态 IP', value: 'static' }, + { label: '单网卡 - DHCP', value: 'dhcp' }, + { label: 'Bond - 链路聚合', value: 'bond' }, + { label: 'Bridge - 桥接', value: 'bridge' }, + { label: 'VLAN - 子接口', value: 'vlan' }, + ], + default: 'static', + }, + ], + }, + { + title: '主接口配置', + fields: [ + { + key: 'interface', + label: '主接口名', + type: 'text', + default: 'eth0', + tip: 'nmconnection 文件名建议与接口名一致', + }, + { + key: 'ipaddr', + label: 'IP 地址', + type: 'text', + default: '192.168.1.10', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'prefix', + label: '子网前缀 (CIDR)', + type: 'number', + min: 1, + max: 32, + default: 24, + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'gateway', + label: '默认网关', + type: 'text', + default: '192.168.1.1', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'dns', + label: 'DNS (逗号分隔)', + type: 'text', + default: '8.8.8.8,8.8.4.4', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'routeMetric', + label: '路由 Metric', + type: 'number', + min: 0, + max: 9999, + default: 0, + tip: '默认路由优先级,值越小优先级越高;0 表示不输出', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'autoconnect', + label: '开机自动启用', + type: 'switch', + default: true, + }, + { + key: 'ipv6Method', + label: 'IPv6 方法', + type: 'select', + options: [ + { label: 'disabled - 禁用', value: 'disabled' }, + { label: 'auto - 自动', value: 'auto' }, + { label: 'ignore - 忽略', value: 'ignore' }, + ], + default: 'disabled', + }, + ], + }, + { + title: 'Bond 配置', + fields: [ + { + key: 'bondName', + label: 'Bond 接口名', + type: 'text', + default: 'bond0', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'bondMode', + label: 'Bond 模式', + type: 'select', + options: [ + { label: 'balance-rr (0)', value: 'balance-rr' }, + { label: 'active-backup (1)', value: 'active-backup' }, + { label: 'balance-xor (2)', value: 'balance-xor' }, + { label: 'broadcast (3)', value: 'broadcast' }, + { label: '802.3ad (4 LACP)', value: '802.3ad' }, + { label: 'balance-tlb (5)', value: 'balance-tlb' }, + { label: 'balance-alb (6)', value: 'balance-alb' }, + ], + default: '802.3ad', + tip: 'mode 4 需要交换机端开启 LACP', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'miimon', + label: 'MII 监控间隔 (ms)', + type: 'number', + min: 0, + max: 10000, + default: 100, + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'lacpRate', + label: 'LACP 速率', + type: 'select', + options: [ + { label: 'slow (30s)', value: 'slow' }, + { label: 'fast (1s)', value: 'fast' }, + ], + default: 'fast', + tip: '仅 802.3ad 模式生效', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'bondSlaves', + label: '成员网卡 (逗号分隔)', + type: 'text', + default: 'eth0,eth1', + dependsOn: { key: 'configType', value: 'bond' }, + }, + ], + }, + { + title: 'Bridge 配置', + fields: [ + { + key: 'bridgeName', + label: 'Bridge 接口名', + type: 'text', + default: 'br0', + dependsOn: { key: 'configType', value: 'bridge' }, + }, + { + key: 'bridgePorts', + label: '桥接成员 (逗号分隔)', + type: 'text', + default: 'eth0', + dependsOn: { key: 'configType', value: 'bridge' }, + }, + { + key: 'bridgeStp', + label: '启用 STP', + type: 'switch', + default: false, + dependsOn: { key: 'configType', value: 'bridge' }, + }, + { + key: 'bridgeHelloTime', + label: 'STP Hello 时间 (秒)', + type: 'number', + min: 1, + max: 10, + default: 2, + dependsOn: { key: 'configType', value: 'bridge' }, + }, + ], + }, + { + title: 'VLAN 配置', + fields: [ + { + key: 'vlanParent', + label: '父接口', + type: 'text', + default: 'eth0', + dependsOn: { key: 'configType', value: 'vlan' }, + }, + { + key: 'vlanId', + label: 'VLAN ID', + type: 'number', + min: 1, + max: 4094, + default: 100, + dependsOn: { key: 'configType', value: 'vlan' }, + }, + ], + }, + ], +} + +function genKeyfile(sections, headerComment) { + const lines = [] + if (headerComment) { + lines.push(headerComment) + } + for (const [section, kv] of Object.entries(sections)) { + lines.push(`[${section}]`) + for (const [k, v] of Object.entries(kv)) { + if (v === undefined || v === null || v === '') continue + lines.push(`${k}=${v}`) + } + lines.push('') + } + return lines.join('\n') +} + +export function generateNetworkRhelNmKeyfile(config) { + const out = [] + const ts = new Date().toLocaleString('zh-CN') + out.push(`# =========================================================`) + out.push(`# RHEL/CentOS 9 NetworkManager keyfile 配置 - 由 ConfTemplate 生成`) + out.push(`# 生成时间: ${ts}`) + out.push(`# 目标目录: /etc/NetworkManager/system-connections/`) + out.push(`# 配置类型: ${config.configType}`) + out.push(`# =========================================================`) + out.push(``) + out.push(`# 应用步骤:`) + out.push(`# 1. 将下面每个 .nmconnection 内容保存到对应文件`) + out.push(`# 2. 设置权限: chmod 600 .nmconnection`) + out.push(`# 3. 执行: nmcli connection reload`) + out.push(`# nmcli connection up `) + out.push(``) + out.push(`# keyfile 必须有 600 权限,否则 NetworkManager 会忽略它`) + out.push(`# -----------------------------------------------------------`) + + const autoStr = config.autoconnect ? 'true' : 'false' + const dnsList = String(config.dns || '').split(',').map((s) => s.trim()).filter(Boolean).join(';') + const dnsSuffix = dnsList ? `${dnsList};` : '' + + if (config.configType === 'static' || config.configType === 'dhcp') { + const sections = { + connection: { + id: config.interface, + type: 'ethernet', + 'interface-name': config.interface, + autoconnect: autoStr, + }, + ipv4: {}, + ipv6: { method: config.ipv6Method }, + } + if (config.configType === 'dhcp') { + sections.ipv4.method = 'auto' + } else { + sections.ipv4.method = 'manual' + sections.ipv4.addresses = `${config.ipaddr}/${config.prefix}` + sections.ipv4.gateway = config.gateway + if (dnsSuffix) sections.ipv4.dns = dnsSuffix + if (config.routeMetric) sections.ipv4['route-metric'] = String(config.routeMetric) + } + out.push(``) + out.push(`# >>> 文件: ${config.interface}.nmconnection`) + out.push(`# >>> 接口 ${config.interface} (${config.configType === 'dhcp' ? 'DHCP' : '静态'})`) + out.push(genKeyfile(sections)) + } + + if (config.configType === 'bond') { + // bond 主接口 + const bondSections = { + connection: { + id: config.bondName, + type: 'bond', + 'interface-name': config.bondName, + autoconnect: autoStr, + }, + bond: { + miimon: String(config.miimon), + mode: config.bondMode, + }, + ipv4: { + method: 'manual', + addresses: `${config.ipaddr}/${config.prefix}`, + gateway: config.gateway, + dns: dnsSuffix, + }, + ipv6: { method: config.ipv6Method }, + } + if (config.routeMetric) bondSections.ipv4['route-metric'] = String(config.routeMetric) + if (config.bondMode === '802.3ad') { + bondSections.bond['lacp_rate'] = config.lacpRate + } + out.push(``) + out.push(`# >>> 文件: ${config.bondName}.nmconnection`) + out.push(`# >>> Bond 主接口 ${config.bondName} (mode=${config.bondMode})`) + out.push(genKeyfile(bondSections)) + + // 成员接口 + const slaves = String(config.bondSlaves || '').split(',').map((s) => s.trim()).filter(Boolean) + for (const slv of slaves) { + const slaveSections = { + connection: { + id: slv, + type: 'ethernet', + 'interface-name': slv, + master: config.bondName, + 'slave-type': 'bond', + autoconnect: autoStr, + }, + ipv4: { method: 'disabled' }, + ipv6: { method: config.ipv6Method }, + } + out.push(`# >>> 文件: ${slv}.nmconnection`) + out.push(`# >>> Bond 成员 ${slv} → ${config.bondName}`) + out.push(genKeyfile(slaveSections)) + } + out.push(`# 提示: mode=802.3ad 需要在交换机端开启 LACP`) + } + + if (config.configType === 'bridge') { + // bridge 主接口 + const brSections = { + connection: { + id: config.bridgeName, + type: 'bridge', + 'interface-name': config.bridgeName, + autoconnect: autoStr, + }, + bridge: { + stp: config.bridgeStp ? 'yes' : 'no', + 'hello-time': String(config.bridgeHelloTime), + }, + ipv4: { + method: 'manual', + addresses: `${config.ipaddr}/${config.prefix}`, + gateway: config.gateway, + dns: dnsSuffix, + }, + ipv6: { method: config.ipv6Method }, + } + if (config.routeMetric) brSections.ipv4['route-metric'] = String(config.routeMetric) + out.push(``) + out.push(`# >>> 文件: ${config.bridgeName}.nmconnection`) + out.push(`# >>> Bridge ${config.bridgeName} (STP=${config.bridgeStp ? 'on' : 'off'})`) + out.push(genKeyfile(brSections)) + + // 成员接口 + const ports = String(config.bridgePorts || '').split(',').map((s) => s.trim()).filter(Boolean) + for (const p of ports) { + const portSections = { + connection: { + id: p, + type: 'ethernet', + 'interface-name': p, + master: config.bridgeName, + 'slave-type': 'bridge', + autoconnect: autoStr, + }, + ipv4: { method: 'disabled' }, + ipv6: { method: config.ipv6Method }, + } + out.push(`# >>> 文件: ${p}.nmconnection`) + out.push(`# >>> Bridge 成员 ${p} → ${config.bridgeName}`) + out.push(genKeyfile(portSections)) + } + } + + if (config.configType === 'vlan') { + const subName = `${config.vlanParent}.${config.vlanId}` + const vlanSections = { + connection: { + id: subName, + type: 'vlan', + 'interface-name': subName, + autoconnect: autoStr, + }, + vlan: { + parent: config.vlanParent, + id: String(config.vlanId), + }, + ipv4: { + method: 'manual', + addresses: `${config.ipaddr}/${config.prefix}`, + gateway: config.gateway, + dns: dnsSuffix, + }, + ipv6: { method: config.ipv6Method }, + } + if (config.routeMetric) vlanSections.ipv4['route-metric'] = String(config.routeMetric) + out.push(``) + out.push(`# >>> 文件: ${subName}.nmconnection`) + out.push(`# >>> VLAN 子接口 ${subName} (VLAN ID=${config.vlanId})`) + out.push(genKeyfile(vlanSections)) + } + + return out.join('\n') +} diff --git a/src/schemas/network-ubuntu-netplan.js b/src/schemas/network-ubuntu-netplan.js new file mode 100644 index 0000000..a938f5f --- /dev/null +++ b/src/schemas/network-ubuntu-netplan.js @@ -0,0 +1,329 @@ +// Ubuntu Netplan 网络配置 +// 覆盖: Ubuntu 18.04 / 20.04 / 22.04 / 24.04 +// 配置文件位于 /etc/netplan/.yaml + +export const networkUbuntuNetplanSchema = { + id: 'network-ubuntu-netplan', + name: 'Ubuntu Netplan', + icon: 'Connection', + category: '网络配置', + description: 'Ubuntu Netplan 配置(含 Bond/Bridge/VLAN)', + format: 'yaml', + fileName: '01-netcfg.yaml', + groups: [ + { + title: '系统版本', + fields: [ + { + key: 'osVersion', + label: '操作系统版本', + type: 'select', + options: [ + { label: 'Ubuntu 18.04 LTS', value: '18.04' }, + { label: 'Ubuntu 20.04 LTS', value: '20.04' }, + { label: 'Ubuntu 22.04 LTS', value: '22.04' }, + { label: 'Ubuntu 24.04 LTS', value: '24.04' }, + ], + default: '22.04', + tip: '18.04 推荐 renderer: networkd;20.04+ 同样支持', + }, + { + key: 'renderer', + label: '渲染器', + type: 'select', + options: [ + { label: 'networkd - systemd-networkd', value: 'networkd' }, + { label: 'NetworkManager', value: 'NetworkManager' }, + ], + default: 'networkd', + tip: '服务器默认 networkd;桌面版可改 NetworkManager', + }, + ], + }, + { + title: '配置类型', + fields: [ + { + key: 'configType', + label: '配置类型', + type: 'select', + options: [ + { label: '单网卡 - 静态 IP', value: 'static' }, + { label: '单网卡 - DHCP', value: 'dhcp' }, + { label: 'Bond - 链路聚合', value: 'bond' }, + { label: 'Bridge - 桥接', value: 'bridge' }, + { label: 'VLAN - 子接口', value: 'vlan' }, + ], + default: 'static', + }, + ], + }, + { + title: '主接口配置', + fields: [ + { + key: 'interface', + label: '主接口名', + type: 'text', + default: 'eth0', + tip: 'Ubuntu 18.04 常见 ens33 / enp0s3;20.04+ 已恢复 eth0', + }, + { + key: 'ipaddr', + label: 'IP 地址', + type: 'text', + default: '192.168.1.10', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'prefix', + label: '子网前缀 (CIDR)', + type: 'number', + min: 1, + max: 32, + default: 24, + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'gateway', + label: '默认网关', + type: 'text', + default: '192.168.1.1', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'dns', + label: 'DNS (逗号分隔)', + type: 'text', + default: '8.8.8.8,8.8.4.4', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + { + key: 'routeMetric', + label: '路由 Metric', + type: 'number', + min: 0, + max: 9999, + default: 0, + tip: '默认路由优先级,值越小优先级越高;0 表示不输出', + dependsOn: { key: 'configType', valueIn: ['static', 'bond', 'bridge', 'vlan'] }, + }, + ], + }, + { + title: 'Bond 配置', + fields: [ + { + key: 'bondName', + label: 'Bond 接口名', + type: 'text', + default: 'bond0', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'bondMode', + label: 'Bond 模式', + type: 'select', + options: [ + { label: 'balance-rr (0)', value: 'balance-rr' }, + { label: 'active-backup (1)', value: 'active-backup' }, + { label: 'balance-xor (2)', value: 'balance-xor' }, + { label: 'broadcast (3)', value: 'broadcast' }, + { label: '802.3ad (4 LACP)', value: '802.3ad' }, + { label: 'balance-tlb (5)', value: 'balance-tlb' }, + { label: 'balance-alb (6)', value: 'balance-alb' }, + ], + default: '802.3ad', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'miimon', + label: 'MII 监控间隔 (ms)', + type: 'number', + min: 0, + max: 10000, + default: 100, + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'lacpRate', + label: 'LACP 速率', + type: 'select', + options: [ + { label: 'slow (30s)', value: 'slow' }, + { label: 'fast (1s)', value: 'fast' }, + ], + default: 'fast', + dependsOn: { key: 'configType', value: 'bond' }, + }, + { + key: 'bondSlaves', + label: '成员网卡 (逗号分隔)', + type: 'text', + default: 'eth0,eth1', + dependsOn: { key: 'configType', value: 'bond' }, + }, + ], + }, + { + title: 'Bridge 配置', + fields: [ + { + key: 'bridgeName', + label: 'Bridge 接口名', + type: 'text', + default: 'br0', + dependsOn: { key: 'configType', value: 'bridge' }, + }, + { + key: 'bridgePorts', + label: '桥接成员 (逗号分隔)', + type: 'text', + default: 'eth0', + dependsOn: { key: 'configType', value: 'bridge' }, + }, + { + key: 'bridgeStp', + label: '启用 STP', + type: 'switch', + default: false, + dependsOn: { key: 'configType', value: 'bridge' }, + }, + ], + }, + { + title: 'VLAN 配置', + fields: [ + { + key: 'vlanParent', + label: '父接口', + type: 'text', + default: 'eth0', + dependsOn: { key: 'configType', value: 'vlan' }, + }, + { + key: 'vlanId', + label: 'VLAN ID', + type: 'number', + min: 1, + max: 4094, + default: 100, + dependsOn: { key: 'configType', value: 'vlan' }, + }, + ], + }, + ], +} + +// 生成接口下的 IPv4 静态配置块(addresses / routes / nameservers) +// base = 接口下属性的缩进空格数(如接口在 2 空格则 base=4) +function ipBlock(base, extraLines) { + const pad = (n) => ' '.repeat(base + n) + const lines = [] + lines.push(`${pad(0)}addresses:`) + lines.push(`${pad(2)}- ${this.cidr}`) + if (this.gateway) { + lines.push(`${pad(0)}routes:`) + lines.push(`${pad(2)}- to: default`) + lines.push(`${pad(4)}via: ${this.gateway}`) + if (this.routeMetric) { + lines.push(`${pad(4)}metric: ${this.routeMetric}`) + } + } + if (this.dnsList.length > 0) { + lines.push(`${pad(0)}nameservers:`) + lines.push(`${pad(2)}addresses:`) + for (const d of this.dnsList) lines.push(`${pad(4)}- ${d}`) + } + return lines.concat(extraLines || []) +} + +export function generateNetworkUbuntuNetplan(config) { + const dnsList = String(config.dns || '').split(',').map((s) => s.trim()).filter(Boolean) + const cidr = `${config.ipaddr}/${config.prefix}` + const ts = new Date().toLocaleString('zh-CN') + + // 绑定 ipBlock 的 this 上下文 + const ctx = { cidr, gateway: config.gateway, dnsList, routeMetric: config.routeMetric } + const genIp = (base, extra) => ipBlock.call(ctx, base, extra) + + const body = [] // 每个元素是一行,已包含完整缩进 + + if (config.configType === 'static') { + body.push(` ${config.interface}:`) + body.push(...genIp(4)) + } else if (config.configType === 'dhcp') { + body.push(` ${config.interface}:`) + body.push(` dhcp4: true`) + if (dnsList.length > 0) { + body.push(` nameservers:`) + body.push(` addresses:`) + for (const d of dnsList) body.push(` - ${d}`) + } + } else if (config.configType === 'bond') { + const slaves = String(config.bondSlaves || '').split(',').map((s) => s.trim()).filter(Boolean) + body.push(` ethernets:`) + for (const slv of slaves) { + body.push(` ${slv}:`) + body.push(` dhcp4: false`) + body.push(` optional: true`) + } + body.push(` bonds:`) + body.push(` ${config.bondName}:`) + body.push(` interfaces:`) + for (const slv of slaves) body.push(` - ${slv}`) + body.push(...genIp(6, [ + ` parameters:`, + ` mode: ${config.bondMode}`, + ` miimon: ${config.miimon}`, + ...(config.bondMode === '802.3ad' ? [` lacp-rate: ${config.lacpRate}`] : []), + ])) + } else if (config.configType === 'bridge') { + const ports = String(config.bridgePorts || '').split(',').map((s) => s.trim()).filter(Boolean) + body.push(` ethernets:`) + for (const p of ports) { + body.push(` ${p}:`) + body.push(` dhcp4: false`) + body.push(` optional: true`) + } + body.push(` bridges:`) + body.push(` ${config.bridgeName}:`) + body.push(` interfaces:`) + for (const p of ports) body.push(` - ${p}`) + body.push(...genIp(6, [ + ` parameters:`, + ` stp: ${config.bridgeStp ? 'true' : 'false'}`, + ])) + } else if (config.configType === 'vlan') { + const subName = `${config.vlanParent}.${config.vlanId}` + body.push(` ethernets:`) + body.push(` ${config.vlanParent}:`) + body.push(` dhcp4: false`) + body.push(` optional: true`) + body.push(` vlans:`) + body.push(` ${subName}:`) + body.push(...genIp(6, [ + ` id: ${config.vlanId}`, + ` link: ${config.vlanParent}`, + ])) + } + + const header = [] + header.push(`# =========================================================`) + header.push(`# Ubuntu Netplan 配置 - 由 ConfTemplate 生成`) + header.push(`# 生成时间: ${ts}`) + header.push(`# 目标文件: /etc/netplan/01-netcfg.yaml`) + header.push(`# 配置类型: ${config.configType}`) + header.push(`# =========================================================`) + header.push(`#`) + header.push(`# 应用步骤:`) + header.push(`# 1. 将本文件保存到 /etc/netplan/01-netcfg.yaml`) + header.push(`# 2. 权限: sudo chmod 600 /etc/netplan/01-netcfg.yaml`) + header.push(`# 3. 应用: sudo netplan apply`) + header.push(`# 4. 验证: ip a / networkctl status`) + header.push(`#`) + header.push(`# 注意: netplan 文件需 600 权限,否则 apply 会拒绝执行`) + header.push(``) + + return header.join('\n') + `network:\n version: 2\n renderer: ${config.renderer}\n` + body.join('\n') + '\n' +}