Commit 0655cf80 authored by jan.koester's avatar jan.koester
Browse files

test

parent 1ab80f75
Loading
Loading
Loading
Loading
+56 −30
Changes for www/modules/network.js: 56 added lines, 30 removed lines.
Original line number Diff line number Diff line
@@ -127,6 +127,10 @@
    '{0} and its configuration are removed; {1} no longer carries VLAN {2}.':
      '{0} und seine Konfiguration werden entfernt; {1} führt VLAN {2} dann nicht mehr.',
    'VLANs: {0}': 'VLANs: {0}',
    'No address': 'Keine Adresse', 'no address': 'keine Adresse',
    '{0} gets no address of its own (it only carries the VLANs)': '{0} bekommt keine eigene Adresse (trägt nur die VLANs)',
    'The switch port of {0} must carry VLAN {1} tagged; untagged traffic stays with {0}. If this page runs over {0}, removing its address cuts the connection.':
      'Der Switch-Port von {0} muss VLAN {1} getaggt führen; ungetaggter Verkehr bleibt bei {0}. Läuft diese Seite über {0}, trennt das Entfernen seiner Adresse die Verbindung.',
    'The host\'s ends of the containers\' veth pairs; containerd makes and removes them with the containers (Containers page).':
      'Die Host-Enden der veth-Paare der Container; containerd legt sie mit den Containern an und entfernt sie (Seite Container).',
  }});
@@ -175,6 +179,14 @@
      !netdevs.some(n => n.kind === 'vlan' && n.parent === i.name));
  }

  // 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 ipArgsOf = (d, mode) => ({dhcp: mode === 'dhcp', address: mode === 'static' ? d.address.trim() : '',
                                  gateway: mode === 'static' ? d.gateway.trim() : '',
                                  dns: mode === 'none' ? [] : d.dns.split(/[\s,]+/).filter(Boolean)});

  function netdevDialog(kind, nd) {
    const iface = nd && interfaces.find(i => i.name === nd.name);
    const own = iface && ownConfig(iface);
@@ -205,10 +217,10 @@
        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 === 'dhcp'; };
        const dhcp = !own || iface.dhcp;
        const toggleIp = () => { staticFields.hidden = form.elements.ipmode.value !== 'static'; };
        const current = ipModeOf(iface, own);
        const ipmode = (value, text) => h('label', {class: 'check'},
          h('input', {type: 'radio', name: 'ipmode', value, checked: (value === 'dhcp') === dhcp, onchange: toggleIp}), text);
          h('input', {type: 'radio', name: 'ipmode', value, checked: value === current, onchange: toggleIp}), text);
        // a new bond/bridge takes over the addresses of its first member that
        // has its own (syslite moves them the same way): a bond with the
        // host's address becoming a bridge port keeps the host reachable
@@ -245,7 +257,7 @@
                i.name, i.addresses && i.addresses.length ? h('span', {class: 'muted'}, ' ' + i.addresses.join(', ')) : null)))
            : h('p', {class: 'empty'}, t('No free interfaces.')),
          h('h3', {}, t('Addresses of {0}', nd ? nd.name : t(kind))),
          h('div', {class: 'row'}, ipmode('dhcp', t('DHCP')), ipmode('static', t('Static'))),
          h('div', {class: 'row'}, ipmode('dhcp', t('DHCP')), ipmode('static', t('Static')), ipmode('none', t('No address'))),
          staticFields,
          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')),
@@ -259,8 +271,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.'));
        const dhcp = d.ipmode === 'dhcp';
        if (!dhcp && !d.address.trim()) throw new Error(t('A static configuration needs an address.'));
        if (d.ipmode === 'static' && !d.address.trim()) throw new Error(t('A static configuration needs an address.'));
        const args = {netdev: name, members: memberList};
        if (kind === 'bond') {
          args.mode = d.mode;
@@ -274,8 +285,7 @@
        else await call('network', 'createNetdev', Object.assign({kind}, args));
        try {
          // the device's own addresses: its configuration 10-<name>
          const ipArgs = {interface: name, dhcp, address: dhcp ? '' : d.address.trim(), gateway: dhcp ? '' : d.gateway.trim(),
                          dns: d.dns.split(/[\s,]+/).filter(Boolean)};
          const ipArgs = Object.assign({interface: name}, ipArgsOf(d, d.ipmode));
          if (own) await call('network', 'update', Object.assign({config: iface.config}, ipArgs));
          else await call('network', 'create', ipArgs).catch(() => call('network', 'update', Object.assign({config: '10-' + name}, ipArgs)));
        } catch (e) {
@@ -390,30 +400,42 @@
        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 === 'dhcp'; };
        const dhcp = !own || iface.dhcp;
        const toggleIp = () => { if (form.elements.ipmode) staticFields.hidden = form.elements.ipmode.value !== 'static'; };
        const current = ipModeOf(iface, own);
        const ipmode = (value, text) => h('label', {class: 'check'},
          h('input', {type: 'radio', name: 'ipmode', value, checked: (value === 'dhcp') === dhcp, onchange: toggleIp}), text);
          h('input', {type: 'radio', name: 'ipmode', value, checked: value === current, onchange: toggleIp}), text);
        // the parent without addresses of its own: ticked when it has none now
        const parentNone = h('input', {type: 'checkbox', name: 'parentNone'});
        const parentNoneText = h('span', {});
        const parentHint = h('p', {class: 'hint'});
        const parentChanged = () => {
          const p = form.elements.parent.value;
          const pi = interfaces.find(x => x.name === p);
          parentNone.checked = !!pi && ipModeOf(pi, ownConfig(pi)) === 'none';
          parentNoneText.textContent = ' ' + t('{0} gets no address of its own (it only carries the VLANs)', p);
          parentHint.textContent = t('The switch port of {0} must carry VLAN {1} tagged; untagged traffic stays with {0}. If this page runs over {0}, removing its address cuts the connection.',
                                     p, form.elements.vlanId.value || 'n');
        };
        setTimeout(() => {
          suggest(); toggleIp();
          suggest(); toggleIp(); parentChanged();
          if (form.elements.name) form.elements.name.addEventListener('input', () => { nameTouched = true; });
        });
        const parent = nd ? nd.parent : parents[0];
        return [
          h('label', {}, t('Parent interface'), h('select', {name: 'parent', onchange: suggest},
          h('label', {}, t('Parent interface'), h('select', {name: 'parent', onchange: () => { suggest(); parentChanged(); }},
            parents.map(p => h('option', {value: p, selected: p === parent}, p)))),
          h('label', {class: 'check'}, parentNone, parentNoneText),
          h('label', {}, t('VLAN ID (1-4094)'), h('input', {name: 'vlanId', type: 'number', min: 1, max: 4094, required: true,
            value: nd ? nd.vlanId : 10, oninput: suggest})),
            value: nd ? nd.vlanId : 10, oninput: () => { suggest(); parentChanged(); }})),
          nd ? null : h('label', {}, t('Name (e.g. eth0.10)'), h('input', {name: 'name', required: true})),
          bridge ? h('p', {class: 'hint'}, t('{0} is a port of {1}: it has no addresses of its own.', nd.name, bridge.name)) : [
            h('h3', {}, t('Addresses of {0}', nd ? nd.name : t('vlan'))),
            h('div', {class: 'row'}, ipmode('dhcp', t('DHCP')), ipmode('static', t('Static'))),
            h('div', {class: 'row'}, ipmode('dhcp', t('DHCP')), ipmode('static', t('Static')), ipmode('none', t('No address'))),
            staticFields,
            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('The switch port of {0} must carry VLAN {1} tagged. {0} keeps its own addresses and untagged traffic.',
                                    parent, nd ? nd.vlanId : 'n')),
          parentHint,
        ];
      },
      onSubmit: async d => {
@@ -425,16 +447,20 @@
        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));
        const dhcp = d.ipmode === 'dhcp';
        if (!bridge && !dhcp && !d.address.trim()) throw new Error(t('A static configuration needs an address.'));
        if (!bridge && d.ipmode === 'static' && !d.address.trim()) 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));
        if (d.parentNone) {
          // syslite gave the parent a configuration of its own (VLAN= in it)
          const pi = interfaces.find(x => x.name === d.parent);
          const config = pi && ownConfig(pi) ? pi.config : '10-' + d.parent;
          await call('network', 'update', Object.assign({config, interface: d.parent}, ipArgsOf(d, 'none')));
        }
        if (!bridge) {
          try {
            // the VLAN's own addresses: its configuration 10-<name>
            const ipArgs = {interface: name, dhcp, address: dhcp ? '' : d.address.trim(), gateway: dhcp ? '' : d.gateway.trim(),
                            dns: d.dns.split(/[\s,]+/).filter(Boolean)};
            const ipArgs = Object.assign({interface: name}, ipArgsOf(d, d.ipmode));
            if (own) await call('network', 'update', Object.assign({config: iface.config}, ipArgs));
            else await call('network', 'create', ipArgs).catch(() => call('network', 'update', Object.assign({config: '10-' + name}, ipArgs)));
          } catch (e) {
@@ -759,14 +785,17 @@
        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 === 'dhcp'; };
        const toggle = () => { staticFields.hidden = form.elements.mode.value !== 'static'; };
        // 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 mode = (value, text) => h('label', {class: 'check'},
          h('input', {type: 'radio', name: 'mode', value, checked: (value === 'dhcp') === !!i.dhcp, onchange: toggle}), text);
          h('input', {type: 'radio', name: 'mode', value, checked: value === current, onchange: toggle}), text);
        setTimeout(toggle);
        return [
          i.config && !own ? h('p', {class: 'hint'}, t('"{0}" applies now (match {1}). Saving creates a configuration of its own for {2}.',
                                                       i.config, i.match || '*', i.name)) : null,
          h('div', {class: 'row'}, mode('dhcp', t('DHCP')), mode('static', t('Static'))),
          h('div', {class: 'row'}, mode('dhcp', t('DHCP')), mode('static', t('Static')), carries ? mode('none', t('No address')) : null),
          staticFields,
          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')),
@@ -774,11 +803,8 @@
        ];
      },
      onSubmit: async d => {
        const dhcp = d.mode === 'dhcp';
        const args = {interface: i.name, dhcp,
                      address: dhcp ? '' : d.address.trim(), gateway: dhcp ? '' : d.gateway.trim(),
                      dns: d.dns.split(/[\s,]+/).filter(Boolean)};
        if (!dhcp && !args.address) throw new Error(t('A static configuration needs an address.'));
        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 (own) args.config = i.config;
        await call('network', own ? 'update' : 'create', args);
        if (d.apply) await call('network', 'reload', {interface: i.name});
@@ -827,7 +853,7 @@
    if (i.name === 'lo') return h('span', {class: 'muted'}, t('loopback (set up by syslite)'));
    if (i.memberOf || i.master) return h('span', {class: 'badge'}, t('member of {0}', i.memberOf || i.master));
    if (!i.config) return h('span', {class: 'muted'}, t('none'));
    const what = i.dhcp ? 'DHCP' : i.address || !(i.vlans || []).length ? t('static {0}', i.address || '') : '';
    const what = i.dhcp ? 'DHCP' : i.address ? t('static {0}', i.address) : t('no address');
    return [what ? h('div', {}, what, i.gateway ? ' · GW ' + i.gateway : '') : null,
            (i.vlans || []).length ? h('div', {}, t('VLANs: {0}', i.vlans.join(', '))) : null,
            h('div', {class: 'muted'}, i.config + (ownConfig(i) ? '' : t(' (shared)')))];