Commit d1c4a328 authored by jan.koester's avatar jan.koester
Browse files

test

parent 132c7c9c
Loading
Loading
Loading
Loading
+10 −2
Changes for modules/network/network.cpp: 10 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -30,8 +30,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * change those, start/restart/reload apply them.
 *
 *   list                                          -> {interfaces, netdevs}
 *   create  {interface, config?, match?, dhcp?, address?, gateway?, dns?[]}
 *   update  {interface | config, match?, dhcp?, address?, gateway?, dns?[]}
 *   create  {interface, config?, match?, dhcp?, address?, gateway?, address6?, gateway6?, ipv6AcceptRA?, dns?[]}
 *   update  {interface | config, match?, dhcp?, address?, gateway?, address6?, gateway6?, ipv6AcceptRA?, dns?[]}
 *           address6/gateway6: IPv6, next to DHCP or an IPv4 address;
 *           ipv6AcceptRA: yes|no|"" (router advertisements, "": the kernel's default)
 *   delete  {interface, config?}
 *   start | stop | restart  {interface}
 *   reload | forceReload    {interface?}          (none: all)
@@ -88,6 +90,9 @@ json_object *toJson(const sysbus::InterfaceInfo &i) {
    json_object_object_add(o, "dhcp", json_object_new_boolean(i.dhcp));
    json_object_object_add(o, "address", jstr(i.configAddress));
    json_object_object_add(o, "gateway", jstr(i.gateway));
    json_object_object_add(o, "address6", jstr(i.configAddress6));
    json_object_object_add(o, "gateway6", jstr(i.gateway6));
    json_object_object_add(o, "ipv6AcceptRA", jstr(i.ipv6AcceptRA));
    json_object *dns = json_object_new_array();
    for (auto &d : i.dns) json_object_array_add(dns, jstr(d));
    json_object_object_add(o, "dns", dns);
@@ -232,6 +237,9 @@ bool settings(json_object *a, sysbus::InterfaceSettings &s, std::string &err) {
    if (webctl::has(a, "dhcp")) s.dhcp = webctl::boolean(a, "dhcp");
    if (webctl::has(a, "address")) s.address = webctl::str(a, "address");
    if (webctl::has(a, "gateway")) s.gateway = webctl::str(a, "gateway");
    if (webctl::has(a, "address6")) s.address6 = webctl::str(a, "address6");
    if (webctl::has(a, "gateway6")) s.gateway6 = webctl::str(a, "gateway6");
    if (webctl::has(a, "ipv6AcceptRA")) s.ipv6AcceptRA = webctl::str(a, "ipv6AcceptRA");
    json_object *dns = nullptr;
    if (json_object_object_get_ex(a, "dns", &dns) && dns) {
        if (!json_object_is_type(dns, json_type_array)) {
+53 −11
Changes for www/modules/network.js: 53 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -12,6 +12,12 @@
    'Changing the interface this page runs over can cut the connection.':
      'Änderungen am Interface, über das diese Seite läuft, können die Verbindung trennen.',
    'A static configuration needs an address.': 'Für statische Konfiguration ist eine Adresse nötig.',
    'IPv6': 'IPv6', 'IPv6 address (CIDR)': 'IPv6-Adresse (CIDR)', 'IPv6 gateway': 'IPv6-Gateway',
    'Router advertisements': 'Router-Advertisements', 'kernel default': 'Kernel-Standard',
    'yes: default route and address (SLAAC) from the router': 'ja: Default-Route und Adresse (SLAAC) vom Router',
    'no': 'nein',
    'IPv6 works next to DHCP or a static IPv4 address. A link-local gateway (fe80::…) is fine.':
      'IPv6 gilt neben DHCP oder einer statischen IPv4-Adresse. Ein link-lokales Gateway (fe80::…) geht auch.',
    'Configuration for {0} saved and applied.': 'Konfiguration für {0} gespeichert und angewendet.',
    'Configuration for {0} saved.': 'Konfiguration für {0} gespeichert.',
    'Delete configuration': 'Konfiguration löschen',
@@ -181,11 +187,28 @@

  // how an interface gets its addresses: 'dhcp', 'static' or 'none' (its
  // own configuration has none: it only carries VLANs, or is layer 2 only)
  const ipModeOf = (i, own) => !own ? 'dhcp' : i.dhcp ? 'dhcp' : i.address ? 'static' : 'none';
  // the address fields of a dialog as create/update take them
  const ipModeOf = (i, own) => !own ? 'dhcp' : i.dhcp ? 'dhcp'
                             : i.address || i.address6 || i.ipv6AcceptRA === 'yes' ? 'static' : 'none';
  // the address fields of a dialog as create/update take them; IPv6 goes
  // with DHCP and static alike, 'none' clears it too
  const ipArgsOf = (d, mode) => ({dhcp: mode === 'dhcp', address: mode === 'static' ? d.address.trim() : '',
                                  gateway: mode === 'static' ? d.gateway.trim() : '',
                                  address6: mode === 'none' ? '' : (d.address6 || '').trim(),
                                  gateway6: mode === 'none' ? '' : (d.gateway6 || '').trim(),
                                  ipv6AcceptRA: mode === 'none' ? '' : (d.ipv6AcceptRA || ''),
                                  dns: mode === 'none' ? [] : d.dns.split(/[\s,]+/).filter(Boolean)});
  // a static configuration: an IPv4 or an IPv6 address (or one by SLAAC)
  const staticOk = (d, mode) => mode !== 'static' || d.address.trim() || (d.address6 || '').trim() || d.ipv6AcceptRA === 'yes';
  // a dialog's IPv6 fields, filled from i's configuration (none: empty)
  const ipv6Fields = i => h('div', {},
    h('h3', {}, t('IPv6')),
    h('div', {class: 'row'},
      h('label', {}, t('IPv6 address (CIDR)'), h('input', {name: 'address6', value: i ? i.address6 || '' : '', placeholder: '2001:db8::10/64'})),
      h('label', {}, t('IPv6 gateway'), h('input', {name: 'gateway6', value: i ? i.gateway6 || '' : '', placeholder: 'fe80::1'}))),
    h('label', {}, t('Router advertisements'), h('select', {name: 'ipv6AcceptRA'},
      [['', t('kernel default')], ['yes', t('yes: default route and address (SLAAC) from the router')], ['no', t('no')]]
        .map(([v, text]) => h('option', {value: v, selected: (i ? i.ipv6AcceptRA || '' : '') === v}, text)))),
    h('p', {class: 'hint'}, t('IPv6 works next to DHCP or a static IPv4 address. A link-local gateway (fe80::…) is fine.')));

  function netdevDialog(kind, nd) {
    const iface = nd && interfaces.find(i => i.name === nd.name);
@@ -217,7 +240,11 @@
        const staticFields = h('div', {},
          h('label', {}, t('Address (CIDR)'), h('input', {name: 'address', value: own ? iface.address : '', placeholder: '192.168.1.10/24'})),
          h('label', {}, t('Gateway'), h('input', {name: 'gateway', value: own ? iface.gateway : '', placeholder: '192.168.1.1'})));
        const toggleIp = () => { staticFields.hidden = form.elements.ipmode.value !== 'static'; };
        const v6Fields = ipv6Fields(own ? iface : null);
        const toggleIp = () => {
          staticFields.hidden = form.elements.ipmode.value !== 'static';
          v6Fields.hidden = form.elements.ipmode.value === 'none';
        };
        const current = ipModeOf(iface, own);
        const ipmode = (value, text) => h('label', {class: 'check'},
          h('input', {type: 'radio', name: 'ipmode', value, checked: value === current, onchange: toggleIp}), text);
@@ -226,10 +253,13 @@
        // host's address becoming a bridge port keeps the host reachable
        let addressesTouched = false;
        const takeAddresses = i => {
          if (nd || own || addressesTouched || !ownConfig(i) || (!i.dhcp && !i.address)) return;
          if (nd || own || addressesTouched || !ownConfig(i) || (!i.dhcp && !i.address && !i.address6 && i.ipv6AcceptRA !== 'yes')) return;
          form.querySelector(`input[name=ipmode][value=${i.dhcp ? 'dhcp' : 'static'}]`).checked = true;
          form.elements.address.value = i.dhcp ? '' : i.address;
          form.elements.gateway.value = i.dhcp ? '' : (i.gateway || '');
          form.elements.address6.value = i.address6 || '';
          form.elements.gateway6.value = i.gateway6 || '';
          form.elements.ipv6AcceptRA.value = i.ipv6AcceptRA || '';
          form.elements.dns.value = (i.dns || []).join(', ');
          toggleIp();
          addressesTouched = true;
@@ -238,7 +268,7 @@
        };
        setTimeout(() => {
          if (kind === 'bond') toggle(); toggleIp();
          for (const n of ['address', 'gateway', 'dns'])
          for (const n of ['address', 'gateway', 'address6', 'gateway6', 'ipv6AcceptRA', 'dns'])
            form.elements[n].addEventListener('input', () => { addressesTouched = true; });
          for (const r of form.querySelectorAll('input[name=ipmode]')) r.addEventListener('change', () => { addressesTouched = true; });
        });
@@ -259,6 +289,7 @@
          h('h3', {}, t('Addresses of {0}', nd ? nd.name : t(kind))),
          h('div', {class: 'row'}, ipmode('dhcp', t('DHCP')), ipmode('static', t('Static')), ipmode('none', t('No address'))),
          staticFields,
          v6Fields,
          h('label', {}, t('DNS servers (comma separated)'), h('input', {name: 'dns', value: own ? (iface.dns || []).join(', ') : ''})),
          h('label', {class: 'check'}, h('input', {type: 'checkbox', name: 'apply', checked: true}), t('Apply now')),
          h('p', {class: 'hint'}, t('Members lose their own addresses: the {0} has them. Changing the interface this page runs over can cut the connection.', t(kind))),
@@ -271,7 +302,7 @@
        if (!ifnameOk(name)) throw new Error(t('Names: letters, digits, "-", "_" and ".", at most 15 characters.'));
        const memberList = d.members || [];
        if (!memberList.length) throw new Error(t('Select at least one member.'));
        if (d.ipmode === 'static' && !d.address.trim()) throw new Error(t('A static configuration needs an address.'));
        if (!staticOk(d, d.ipmode)) throw new Error(t('A static configuration needs an address.'));
        const args = {netdev: name, members: memberList};
        if (kind === 'bond') {
          args.mode = d.mode;
@@ -400,7 +431,12 @@
        const staticFields = h('div', {},
          h('label', {}, t('Address (CIDR)'), h('input', {name: 'address', value: own ? iface.address : '', placeholder: '192.168.10.10/24'})),
          h('label', {}, t('Gateway'), h('input', {name: 'gateway', value: own ? iface.gateway : '', placeholder: '192.168.10.1'})));
        const toggleIp = () => { if (form.elements.ipmode) staticFields.hidden = form.elements.ipmode.value !== 'static'; };
        const v6Fields = ipv6Fields(own ? iface : null);
        const toggleIp = () => {
          if (!form.elements.ipmode) return;
          staticFields.hidden = form.elements.ipmode.value !== 'static';
          v6Fields.hidden = form.elements.ipmode.value === 'none';
        };
        const current = ipModeOf(iface, own);
        const ipmode = (value, text) => h('label', {class: 'check'},
          h('input', {type: 'radio', name: 'ipmode', value, checked: value === current, onchange: toggleIp}), text);
@@ -432,6 +468,7 @@
            h('h3', {}, t('Addresses of {0}', nd ? nd.name : t('vlan'))),
            h('div', {class: 'row'}, ipmode('dhcp', t('DHCP')), ipmode('static', t('Static')), ipmode('none', t('No address'))),
            staticFields,
            v6Fields,
            h('label', {}, t('DNS servers (comma separated)'), h('input', {name: 'dns', value: own ? (iface.dns || []).join(', ') : ''})),
          ],
          h('label', {class: 'check'}, h('input', {type: 'checkbox', name: 'apply', checked: true}), t('Apply now')),
@@ -447,7 +484,7 @@
        if (!d.parent) throw new Error(t('Select the interface the VLAN is on.'));
        const dup = netdevs.find(n => n.kind === 'vlan' && n !== nd && n.parent === d.parent && n.vlanId === vlanId);
        if (dup) throw new Error(t('{0} already has VLAN {1} ({2}).', d.parent, vlanId, dup.name));
        if (!bridge && d.ipmode === 'static' && !d.address.trim()) throw new Error(t('A static configuration needs an address.'));
        if (!bridge && !staticOk(d, d.ipmode)) throw new Error(t('A static configuration needs an address.'));
        const args = {netdev: name, vlanId, parent: d.parent};
        if (nd) await call('network', 'updateNetdev', args);
        else await call('network', 'createNetdev', Object.assign({kind: 'vlan'}, args));
@@ -785,10 +822,14 @@
        const staticFields = h('div', {},
          h('label', {}, t('Address (CIDR)'), h('input', {name: 'address', value: i.address, placeholder: '192.168.1.10/24'})),
          h('label', {}, t('Gateway'), h('input', {name: 'gateway', value: i.gateway, placeholder: '192.168.1.1'})));
        const toggle = () => { staticFields.hidden = form.elements.mode.value !== 'static'; };
        const v6Fields = ipv6Fields(i);
        const toggle = () => {
          staticFields.hidden = form.elements.mode.value !== 'static';
          v6Fields.hidden = form.elements.mode.value === 'none';
        };
        // without an address: only an interface carrying VLANs
        const carries = (i.vlans || []).length > 0;
        const current = i.dhcp ? 'dhcp' : own && carries && !i.address ? 'none' : 'static';
        const current = i.dhcp ? 'dhcp' : own && carries && !i.address && !i.address6 && i.ipv6AcceptRA !== 'yes' ? 'none' : 'static';
        const mode = (value, text) => h('label', {class: 'check'},
          h('input', {type: 'radio', name: 'mode', value, checked: value === current, onchange: toggle}), text);
        setTimeout(toggle);
@@ -797,6 +838,7 @@
                                                       i.config, i.match || '*', i.name)) : null,
          h('div', {class: 'row'}, mode('dhcp', t('DHCP')), mode('static', t('Static')), carries ? mode('none', t('No address')) : null),
          staticFields,
          v6Fields,
          h('label', {}, t('DNS servers (comma separated)'), h('input', {name: 'dns', value: (i.dns || []).join(', ')})),
          h('label', {class: 'check'}, h('input', {type: 'checkbox', name: 'apply', checked: true}), t('Apply now')),
          h('p', {class: 'hint'}, t('Changing the interface this page runs over can cut the connection.')),
@@ -804,7 +846,7 @@
      },
      onSubmit: async d => {
        const args = Object.assign({interface: i.name}, ipArgsOf(d, d.mode));
        if (d.mode === 'static' && !args.address) throw new Error(t('A static configuration needs an address.'));
        if (!staticOk(d, d.mode)) throw new Error(t('A static configuration needs an address.'));
        if (own) args.config = i.config;
        await call('network', own ? 'update' : 'create', args);
        if (d.apply) await call('network', 'reload', {interface: i.name});