e5ef6792d1
- 新增 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
431 lines
13 KiB
JavaScript
431 lines
13 KiB
JavaScript
// 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')
|
|
}
|