luci-mod-network: Add IPSelect widget which eases selection of interface IPs

This widget is modeled after CBINetworkSelect, which is similar in nature.
It presents a dropdown box of all device IPs with an accompanying badge of the
device.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
Paul Donald
2025-10-26 01:47:57 +02:00
parent 3dced05aa2
commit 7c1696c420

View File

@@ -338,6 +338,110 @@ var CBIZoneForwards = form.DummyValue.extend({
},
});
const CBIIPSelect = form.ListValue.extend({
__name__: 'CBI.IPSelect',
load(section_id) {
return network.getDevices().then(L.bind(function(devices) {
this.devices = devices;
return this.super('load', section_id);
}, this));
},
filter(section_id, value) {
return true;
},
renderIfaceBadge(device, ip) {
return E('div', {}, [
ip,
' ',
E('span', { 'class': 'ifacebadge', }, [ device.getName(),
E('img', {
'title': device.getI18n(),
'src': L.resource('icons/%s%s.svg'.format(device.getType(), device.isUp() ? '' : '_disabled'))
})
]),
]);
},
renderWidget(section_id, option_index, cfgvalue) {
let values = L.toArray((cfgvalue != null) ? cfgvalue : this.default);
const choices = {};
const checked = {};
for (const val of values)
checked[val] = true;
values = [];
if (!this.multiple && (this.rmempty || this.optional))
choices[''] = E('em', _('unspecified'));
for (const device of (this.devices || [])) {
const name = device.getName();
if (name == this.exclude || !this.filter(section_id, name))
continue;
if (name == 'loopback' && !this.loopback)
continue;
if (this.novirtual && device.isVirtual())
continue;
for (const ip of [...device.getIPAddrs(), ...device.getIP6Addrs()]) {
const iponly = ip.split('/')?.[0]
if (checked[iponly])
values.push(iponly);
choices[iponly] = this.renderIfaceBadge(device, iponly);
}
}
const widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
id: this.cbid(section_id),
sort: true,
multiple: this.multiple,
optional: this.optional || this.rmempty,
disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
select_placeholder: E('em', _('unspecified')),
display_items: this.display_size || this.size || 2,
dropdown_items: this.dropdown_size || this.size || 5,
datatype: this.multiple ? 'list(ipaddr)' : 'ipaddr',
validate: L.bind(this.validate, this, section_id),
create: false,
});
return widget.render();
},
textvalue(section_id) {
const cfgvalue = this.cfgvalue(section_id);
const values = L.toArray((cfgvalue != null) ? cfgvalue : this.default);
const rv = E([]);
for (const device of (this.devices || [])) {
for (const ip of [...device.getIPAddrs(), ...device.getIP6Addrs()]) {
const iponly = ip.split('/')[0];
if (values.indexOf(iponly) === -1)
continue;
if (rv.childNodes.length)
rv.appendChild(document.createTextNode(' '));
rv.appendChild(this.renderIfaceBadge(device, iponly));
}
}
if (!rv.firstChild)
rv.appendChild(E('em', _('unspecified')));
return rv;
},
});
var CBINetworkSelect = form.ListValue.extend({
__name__: 'CBI.NetworkSelect',
@@ -652,6 +756,7 @@ var CBIGroupSelect = form.ListValue.extend({
return L.Class.extend({
ZoneSelect: CBIZoneSelect,
ZoneForwards: CBIZoneForwards,
IPSelect: CBIIPSelect,
NetworkSelect: CBINetworkSelect,
DeviceSelect: CBIDeviceSelect,
UserSelect: CBIUserSelect,