mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-06 03:14:12 +08:00
305 lines
9.0 KiB
JavaScript
305 lines
9.0 KiB
JavaScript
'use strict';
|
|
|
|
'require baseclass';
|
|
'require form';
|
|
'require ui';
|
|
'require uci';
|
|
'require network';
|
|
'require firewall';
|
|
|
|
'require podman.utils as utils';
|
|
'require podman.rpc as podmanRPC';
|
|
'require podman.view as podmanView';
|
|
'require podman.model.Network as Network';
|
|
|
|
/**
|
|
* Create podman network
|
|
*/
|
|
const PodmanFormNetwork = podmanView.form.extend({
|
|
__name__: 'Podman.Form.Network',
|
|
|
|
existingZones: null,
|
|
parentDevices: null,
|
|
|
|
makeData() {
|
|
return {
|
|
network: {
|
|
name: null,
|
|
driver: 'bridge',
|
|
subnet: null,
|
|
gateway: null,
|
|
ip_range: null,
|
|
ipv6: '0',
|
|
internal: '0',
|
|
labels: null,
|
|
setup_openwrt: '1',
|
|
firewall_zone: '_create_new_',
|
|
}
|
|
};
|
|
},
|
|
|
|
async render() {
|
|
const [zones, parentDevices] = await Promise.all([
|
|
firewall.getZones().then((zones) =>
|
|
zones
|
|
.filter(zone => zone.data.name?.startsWith('podman'))
|
|
.map(zone => zone.data.name)
|
|
),
|
|
network.getDevices().then((devices) =>
|
|
devices.filter(this.isValidParentDevice)
|
|
),
|
|
]);
|
|
|
|
this.existingZones = zones;
|
|
this.parentDevices = parentDevices;
|
|
|
|
return this.super('render', []);
|
|
},
|
|
|
|
createForm: function () {
|
|
let field;
|
|
|
|
field = this.section.option(form.Value, 'name', _('Network Name'));
|
|
field.placeholder = 'my-network';
|
|
field.datatype = 'and(uciname,maxlength(15))';
|
|
field.description = _('Name for the network');
|
|
field.rmempty = false;
|
|
|
|
field = this.section.option(form.ListValue, 'driver', _('Driver'));
|
|
field.value('bridge', 'bridge');
|
|
field.value('macvlan', 'macvlan');
|
|
field.value('ipvlan', 'ipvlan');
|
|
field.description = _('Network driver');
|
|
|
|
field = this.section.option(form.ListValue, 'parent', _('Parent Interface'));
|
|
field.depends('driver', 'macvlan');
|
|
field.depends('driver', 'ipvlan');
|
|
field.description = _(
|
|
'Existing physical interface to use as parent. Required for macvlan/ipvlan networks.'
|
|
);
|
|
if (this.parentDevices.length > 0) {
|
|
this.parentDevices.forEach((device) => {
|
|
const name = device.getName();
|
|
field.value(name, name + ' (' + device.getType() + ')');
|
|
});
|
|
} else {
|
|
field.value('', _('No suitable interfaces found'));
|
|
}
|
|
|
|
field = this.section.option(form.Value, 'subnet', _('IPv4 Subnet (CIDR)'));
|
|
field.placeholder = '10.89.0.0/24';
|
|
field.datatype = 'cidr4';
|
|
field.description = _('IPv4 subnet in CIDR notation');
|
|
// field.rmempty = false;
|
|
|
|
field = this.section.option(form.Value, 'gateway', _('IPv4 Gateway'));
|
|
field.placeholder = '10.89.0.1';
|
|
field.optional = true;
|
|
field.datatype = 'ip4addr';
|
|
field.description = _('IPv4 gateway address');
|
|
|
|
field = this.section.option(form.Value, 'ip_range', _('IP Range (CIDR)'));
|
|
field.placeholder = '10.89.0.0/28';
|
|
field.optional = true;
|
|
field.datatype = 'cidr4';
|
|
field.description = _('Allocate container IP from this range');
|
|
|
|
field = this.section.option(form.Flag, 'ipv6', _('Enable IPv6'));
|
|
field.description = _('Enable IPv6 networking');
|
|
|
|
field = this.section.option(form.Flag, 'internal', _('Internal Network'));
|
|
field.description = _('Restrict external access to the network');
|
|
|
|
field = this.section.option(form.Flag, 'setup_openwrt', _('Setup OpenWrt Integration'));
|
|
field.description = _(
|
|
'Automatically configure OpenWrt network interface, bridge, and firewall zone. <strong>Highly recommended</strong> for proper container networking on OpenWrt.'
|
|
);
|
|
|
|
field = this.section.option(form.Value, 'bridge_name', _('Bridge Interface Name'));
|
|
field.placeholder = _('Leave empty to auto-generate');
|
|
field.optional = true;
|
|
field.datatype = 'netdevname';
|
|
field.depends({
|
|
'setup_openwrt': '1',
|
|
'driver': 'bridge'
|
|
});
|
|
field.description = _(
|
|
'Name of the bridge interface (e.g., podman0, mynet0). Leave empty to use: <network-name>0. Note: If the generated name conflicts with an existing interface, OpenWrt will auto-increment it.'
|
|
);
|
|
|
|
field = this.section.option(form.ListValue, 'firewall_zone', _('Firewall Zone'));
|
|
field.value('_create_new_', _('Create new zone (will be named: podman_<network-name>)'));
|
|
if (this.existingZones.length > 0) {
|
|
this.existingZones.forEach((zoneName) => {
|
|
field.value(zoneName, zoneName);
|
|
});
|
|
}
|
|
field.depends('setup_openwrt', '1');
|
|
field.description = _(
|
|
'Choose firewall zone for this network.'
|
|
);
|
|
|
|
field = this.section.option(form.TextValue, 'labels', _('Labels'));
|
|
field.placeholder = 'key1=value1\nkey2=value2';
|
|
field.rows = 3;
|
|
field.optional = true;
|
|
field.description = _('Labels in key=value format, one per line');
|
|
},
|
|
|
|
async handleCreate() {
|
|
if (!this.isValid()) {
|
|
return this.scrollToInvalid();
|
|
}
|
|
|
|
await this.save();
|
|
await uci.load(['network', 'firewall', 'dhcp']);
|
|
const ulaPrefix = uci.get('network', 'globals', 'ula_prefix');
|
|
|
|
const podnetwork = this.getFieldValues();
|
|
|
|
const setupOpenwrt = podnetwork.setup_openwrt === '1';
|
|
const driver = podnetwork.driver || 'bridge';
|
|
const requestedZone = podnetwork.firewall_zone || '_create_new_';
|
|
const zoneName = requestedZone === '_create_new_' ? 'podman_' + podnetwork.name :
|
|
requestedZone;
|
|
const baseBridgeName = podnetwork.bridge_name || podnetwork.name;
|
|
|
|
let bridgeName = baseBridgeName;
|
|
let i = 1;
|
|
while (await network.getDevice(bridgeName)) {
|
|
bridgeName = `${baseBridgeName}${i}`;
|
|
i++;
|
|
}
|
|
|
|
if ((driver === 'macvlan' || driver === 'ipvlan') && !podnetwork.parent) {
|
|
this.error(_('Parent interface is required for macvlan/ipvlan networks'));
|
|
return;
|
|
}
|
|
|
|
// if (setupOpenwrt && !podnetwork.subnet) {
|
|
// this.error(_('OpenWrt integration requires subnet to be specified'));
|
|
// return;
|
|
// }
|
|
|
|
if (!podnetwork.gateway && podnetwork.subnet) {
|
|
podnetwork.gateway = utils.ipv4.firstHost(podnetwork.subnet);
|
|
}
|
|
|
|
const payload = {
|
|
name: podnetwork.name,
|
|
driver: driver
|
|
};
|
|
|
|
if (driver === 'bridge') {
|
|
payload.network_interface = bridgeName;
|
|
} else if (driver === 'macvlan' || driver === 'ipvlan') {
|
|
payload.network_interface = podnetwork.parent;
|
|
}
|
|
|
|
if (podnetwork.subnet) {
|
|
payload.subnets = [{
|
|
subnet: podnetwork.subnet
|
|
}];
|
|
if (podnetwork.gateway) payload.subnets[0].gateway = podnetwork.gateway;
|
|
|
|
if (podnetwork.ip_range) {
|
|
const range = utils.ipv4.cidrToRange(podnetwork.ip_range);
|
|
if (range) payload.subnets[0].lease_range = range;
|
|
}
|
|
}
|
|
|
|
payload.ipv6_enabled = false;
|
|
if (podnetwork.ipv6 === '1') {
|
|
const ipv6obj = utils.ipv6.deriveUlaFromIpv4(podnetwork.subnet, ulaPrefix);
|
|
payload.ipv6_enabled = true;
|
|
|
|
if (podnetwork.subnet) {
|
|
payload.subnets.push({
|
|
subnet: ipv6obj.ipv6subnet
|
|
});
|
|
|
|
if (podnetwork.gateway)
|
|
payload.subnets[1].gateway = ipv6obj.ipv6gateway;
|
|
}
|
|
}
|
|
|
|
payload.internal = podnetwork.internal === '1';
|
|
if (podnetwork.labels) {
|
|
payload.labels = {};
|
|
podnetwork.labels.split('\n').forEach((line) => {
|
|
const parts = line.split('=');
|
|
if (parts.length >= 2) {
|
|
const key = parts[0].trim();
|
|
const value = parts.slice(1).join('=').trim();
|
|
if (key) payload.labels[key] = value;
|
|
}
|
|
});
|
|
}
|
|
|
|
const createFn = async () => {
|
|
const newNetwork = await podmanRPC.networks.create(payload);
|
|
|
|
if (!setupOpenwrt) return;
|
|
|
|
const inspectData = await Network.getSingleton({
|
|
name: podnetwork.name
|
|
}).inspect();
|
|
const NetworkModel = Network.getSingleton(inspectData);
|
|
const deviceName = await NetworkModel.integrationCreateDevice();
|
|
await NetworkModel.integrationCreateNetwork(deviceName);
|
|
|
|
if (await NetworkModel.hasDnsmasq()) {
|
|
await NetworkModel.integrationAddDnsmasqExclusion();
|
|
}
|
|
|
|
if (await firewall.getZone(zoneName)) {
|
|
const currentNetworks = uci.get('firewall', zoneName, 'network');
|
|
const networkList = Array.isArray(currentNetworks) ? currentNetworks :
|
|
currentNetworks ? [currentNetworks] : [];
|
|
if (!networkList.includes(podnetwork.name)) {
|
|
networkList.push(podnetwork.name);
|
|
uci.set('firewall', zoneName, 'network', networkList);
|
|
}
|
|
} else {
|
|
const zoneId = uci.add('firewall', 'zone');
|
|
uci.set('firewall', zoneId, 'name', zoneName);
|
|
uci.set('firewall', zoneId, 'input', 'DROP');
|
|
uci.set('firewall', zoneId, 'output', 'ACCEPT');
|
|
uci.set('firewall', zoneId, 'forward', 'REJECT');
|
|
uci.set('firewall', zoneId, 'network', [podnetwork.name]);
|
|
}
|
|
|
|
const dnsRuleName = 'Allow-' + zoneName + '-DNS';
|
|
const existingDnsRule = uci.sections('firewall', 'rule').find((s) =>
|
|
uci.get('firewall', s['.name'], 'name') === dnsRuleName
|
|
);
|
|
|
|
if (!existingDnsRule) {
|
|
const ruleId = uci.add('firewall', 'rule');
|
|
uci.set('firewall', ruleId, 'name', dnsRuleName);
|
|
uci.set('firewall', ruleId, 'src', zoneName);
|
|
uci.set('firewall', ruleId, 'dest_port', '53');
|
|
uci.set('firewall', ruleId, 'target', 'ACCEPT');
|
|
}
|
|
|
|
await uci.save();
|
|
ui.changes.apply();
|
|
await network.flushCache();
|
|
};
|
|
|
|
return this.super('handleCreate', [createFn, _('Network')]);
|
|
},
|
|
|
|
isValidParentDevice(device) {
|
|
const type = device.getType();
|
|
const name = device.getName();
|
|
return ['ethernet', 'bridge', 'vlan'].includes(type) &&
|
|
name !== 'lo' &&
|
|
!name.match(/^(podman|cni-|docker|veth)/);
|
|
}
|
|
});
|
|
|
|
return baseclass.extend({
|
|
init: PodmanFormNetwork
|
|
});
|