feat: 新增网卡配置文件生成器(ifcfg/NM keyfile/Netplan)

- 新增 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
This commit is contained in:
Your Name
2026-08-05 17:16:41 +08:00
parent 02d514fe7d
commit e5ef6792d1
7 changed files with 1237 additions and 40 deletions
-39
View File
@@ -644,9 +644,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -660,9 +657,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -676,9 +670,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -692,9 +683,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -708,9 +696,6 @@
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -724,9 +709,6 @@
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -740,9 +722,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -756,9 +735,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -772,9 +748,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -788,9 +761,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -804,9 +774,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -820,9 +787,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -836,9 +800,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
+1
View File
@@ -141,6 +141,7 @@ function isFieldVisible(field) {
const dep = field.dependsOn const dep = field.dependsOn
const val = formData.value[dep.key] const val = formData.value[dep.key]
if ('value' in dep) return val === dep.value 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) if ('valueNotIn' in dep) return !dep.valueNotIn.includes(val)
return true return true
} }
+1 -1
View File
@@ -60,7 +60,7 @@
</div> </div>
<div class="sidebar-footer"> <div class="sidebar-footer">
<el-text size="small" type="info">v1.0.0 · 纯前端生成 · 60 个组件</el-text> <el-text size="small" type="info">v1.0.0 · 纯前端生成 · 63 个组件</el-text>
</div> </div>
</div> </div>
</template> </template>
+18
View File
@@ -95,6 +95,11 @@ import { sersyncSchema, generateSersyncXml } from './sersync'
import { vsftpdSchema, generateVsftpdConf } from './vsftpd' import { vsftpdSchema, generateVsftpdConf } from './vsftpd'
import { subversionSchema, generateSubversionConf } from './subversion' 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' import { mycatSchema, generateMycatServerXml } from './mycat'
@@ -183,6 +188,10 @@ export const schemas = {
subversion: subversionSchema, subversion: subversionSchema,
// 数据库中间件 // 数据库中间件
mycat: mycatSchema, mycat: mycatSchema,
// 网络配置
'network-rhel-ifcfg': networkRhelIfcfgSchema,
'network-rhel-nmkeyfile': networkRhelNmKeyfileSchema,
'network-ubuntu-netplan': networkUbuntuNetplanSchema,
} }
// ============================================================ // ============================================================
@@ -254,6 +263,10 @@ export const generators = {
subversion: generateSubversionConf, subversion: generateSubversionConf,
// 数据库中间件 // 数据库中间件
mycat: generateMycatServerXml, mycat: generateMycatServerXml,
// 网络配置
'network-rhel-ifcfg': generateNetworkRhelIfcfg,
'network-rhel-nmkeyfile': generateNetworkRhelNmKeyfile,
'network-ubuntu-netplan': generateNetworkUbuntuNetplan,
} }
// ============================================================ // ============================================================
@@ -351,6 +364,11 @@ export const categories = [
icon: 'Connection', icon: 'Connection',
items: ['keepalived', 'haproxy', 'lvs'], items: ['keepalived', 'haproxy', 'lvs'],
}, },
{
name: '网络配置',
icon: 'Connection',
items: ['network-rhel-ifcfg', 'network-rhel-nmkeyfile', 'network-ubuntu-netplan'],
},
{ {
name: '系统服务', name: '系统服务',
icon: 'Setting', icon: 'Setting',
+458
View File
@@ -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-<name>
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=yesMASTER=<bondName>',
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=<bridgeName>',
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.<vlan_id>',
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 <name>`)
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')
}
+430
View File
@@ -0,0 +1,430 @@
// RHEL / CentOS 9 NetworkManager keyfile 风格网络配置
// 覆盖: CentOS Stream 9, RHEL 9, Rocky Linux 9, AlmaLinux 9
// 配置文件位于 /etc/NetworkManager/system-connections/<name>.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. 将下面每个 <name>.nmconnection 内容保存到对应文件`)
out.push(`# 2. 设置权限: chmod 600 <name>.nmconnection`)
out.push(`# 3. 执行: nmcli connection reload`)
out.push(`# nmcli connection up <name>`)
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')
}
+329
View File
@@ -0,0 +1,329 @@
// Ubuntu Netplan 网络配置
// 覆盖: Ubuntu 18.04 / 20.04 / 22.04 / 24.04
// 配置文件位于 /etc/netplan/<name>.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: networkd20.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 / enp0s320.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'
}