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
+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')
}