Merge pull request #5540 from jow-/wireguard-peer-gridsection
authorJo-Philipp Wich <jo@mein.io>
Thu, 2 Dec 2021 09:54:36 +0000 (10:54 +0100)
committerGitHub <noreply@github.com>
Thu, 2 Dec 2021 09:54:36 +0000 (10:54 +0100)
Implement nested grid section support and use it for wireguard peer config

modules/luci-base/htdocs/luci-static/resources/form.js
protocols/luci-proto-wireguard/htdocs/luci-static/resources/protocol/wireguard.js

index cf09a1da905d2f0628752a26b4ae8e11bdf1ed4f..2be5bbce86c0679ab5417da8b066021764c49a6b 100644 (file)
@@ -2972,15 +2972,44 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
 
        /** @private */
        handleModalCancel: function(modalMap, ev) {
-               return Promise.resolve(ui.hideModal());
+               var prevNode = this.getPreviousModalMap();
+
+               if (prevNode) {
+                       var heading = prevNode.parentNode.querySelector('h4');
+
+                       prevNode.classList.add('flash');
+                       prevNode.classList.remove('hidden');
+                       prevNode.parentNode.removeChild(prevNode.nextElementSibling);
+
+                       heading.removeChild(heading.lastElementChild);
+
+                       if (!this.getPreviousModalMap())
+                               prevNode.parentNode
+                                       .querySelector('div.right > button')
+                                       .firstChild.data = _('Dismiss');
+               }
+               else {
+                       ui.hideModal();
+               }
+
+               return Promise.resolve();
        },
 
        /** @private */
        handleModalSave: function(modalMap, ev) {
-               return modalMap.save(null, true)
-                       .then(L.bind(this.map.load, this.map))
-                       .then(L.bind(this.map.reset, this.map))
-                       .then(ui.hideModal)
+               var mapNode = this.getActiveModalMap(),
+                   activeMap = dom.findClassInstance(mapNode),
+                   saveTasks = activeMap.save(null, true);
+
+               while (activeMap.parent) {
+                       activeMap = activeMap.parent;
+                       saveTasks = saveTasks
+                               .then(L.bind(activeMap.load, activeMap))
+                               .then(L.bind(activeMap.reset, activeMap));
+               }
+
+               return saveTasks
+                       .then(L.bind(this.handleModalCancel, this, modalMap, ev))
                        .catch(function() {});
        },
 
@@ -3013,6 +3042,19 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
 
        },
 
+       /** @private */
+       getActiveModalMap: function() {
+               return document.querySelector('body.modal-overlay-active > #modal_overlay > .modal.cbi-modal > .cbi-map:not(.hidden)');
+       },
+
+       /** @private */
+       getPreviousModalMap: function() {
+               var mapNode = this.getActiveModalMap(),
+                   prevNode = mapNode ? mapNode.previousElementSibling : null;
+
+               return (prevNode && prevNode.matches('.cbi-map.hidden')) ? prevNode : null;
+       },
+
        /** @private */
        renderMoreOptionsModal: function(section_id, ev) {
                var parent = this.map,
@@ -3061,20 +3103,37 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
                }
 
                return Promise.resolve(this.addModalOptions(s, section_id, ev)).then(L.bind(m.render, m)).then(L.bind(function(nodes) {
-                       ui.showModal(title, [
-                               nodes,
-                               E('div', { 'class': 'right' }, [
-                                       E('button', {
-                                               'class': 'cbi-button',
-                                               'click': ui.createHandlerFn(this, 'handleModalCancel', m)
-                                       }, [ _('Dismiss') ]), ' ',
-                                       E('button', {
-                                               'class': 'cbi-button cbi-button-positive important',
-                                               'click': ui.createHandlerFn(this, 'handleModalSave', m),
-                                               'disabled': m.readonly || null
-                                       }, [ _('Save') ])
-                               ])
-                       ], 'cbi-modal');
+                       var modalMap = this.getActiveModalMap();
+
+                       if (modalMap) {
+                               modalMap.parentNode
+                                       .querySelector('h4')
+                                       .appendChild(E('span', title ? ' ยป ' + title : ''));
+
+                               modalMap.parentNode
+                                       .querySelector('div.right > button')
+                                       .firstChild.data = _('Back');
+
+                               modalMap.classList.add('hidden');
+                               modalMap.parentNode.insertBefore(nodes, modalMap.nextElementSibling);
+                               nodes.classList.add('flash');
+                       }
+                       else {
+                               ui.showModal(title, [
+                                       nodes,
+                                       E('div', { 'class': 'right' }, [
+                                               E('button', {
+                                                       'class': 'cbi-button',
+                                                       'click': ui.createHandlerFn(this, 'handleModalCancel', m)
+                                               }, [ _('Dismiss') ]), ' ',
+                                               E('button', {
+                                                       'class': 'cbi-button cbi-button-positive important',
+                                                       'click': ui.createHandlerFn(this, 'handleModalSave', m),
+                                                       'disabled': m.readonly || null
+                                               }, [ _('Save') ])
+                                       ])
+                               ], 'cbi-modal');
+                       }
                }, this)).catch(L.error);
        }
 });
@@ -3155,25 +3214,33 @@ var CBIGridSection = CBITableSection.extend(/** @lends LuCI.form.GridSection.pro
        /** @private */
        handleAdd: function(ev, name) {
                var config_name = this.uciconfig || this.map.config,
-                   section_id = this.map.data.add(config_name, this.sectiontype, name);
+                   section_id = this.map.data.add(config_name, this.sectiontype, name),
+                   mapNode = this.getPreviousModalMap(),
+                   prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
+
+               prevMap.addedSection = section_id;
 
-               this.addedSection = section_id;
                return this.renderMoreOptionsModal(section_id);
        },
 
        /** @private */
        handleModalSave: function(/* ... */) {
+               var mapNode = this.getPreviousModalMap(),
+                   prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
+
                return this.super('handleModalSave', arguments)
-                       .then(L.bind(function() { this.addedSection = null }, this));
+                       .then(function() { delete prevMap.addedSection });
        },
 
        /** @private */
        handleModalCancel: function(/* ... */) {
-               var config_name = this.uciconfig || this.map.config;
+               var config_name = this.uciconfig || this.map.config,
+                   mapNode = this.getPreviousModalMap(),
+                   prevMap = mapNode ? dom.findClassInstance(mapNode) : this.map;
 
-               if (this.addedSection != null) {
-                       this.map.data.remove(config_name, this.addedSection);
-                       this.addedSection = null;
+               if (prevMap.addedSection != null) {
+                       this.map.data.remove(config_name, prevMap.addedSection);
+                       delete prevMap.addedSection;
                }
 
                return this.super('handleModalCancel', arguments);
index 74327f3001d73229b584ad975f22a1cae69810f4..50080ca9b65da5d0f9757b312fe4e38fdefe3c14 100644 (file)
@@ -163,13 +163,15 @@ return network.registerProtocol('wireguard', {
                }
                catch(e) {}
 
-               o = s.taboption('peers', form.SectionValue, '_peers', form.TypedSection, 'wireguard_%s'.format(s.section));
+               o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'wireguard_%s'.format(s.section));
                o.depends('proto', 'wireguard');
 
                ss = o.subsection;
                ss.anonymous = true;
                ss.addremove = true;
                ss.addbtntitle = _('Add peer');
+               ss.nodescriptions = true;
+               ss.modaltitle = _('Edit peer');
 
                ss.renderSectionPlaceholder = function() {
                        return E([], [
@@ -180,6 +182,7 @@ return network.registerProtocol('wireguard', {
 
                o = ss.option(form.Flag, 'disabled', _('Peer disabled'), _('Enable / Disable peer. Restart wireguard interface to apply changes.'));
                o.optional = true;
+               o.editable = true;
 
                o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.'));
                o.placeholder = 'My Peer';
@@ -187,6 +190,7 @@ return network.registerProtocol('wireguard', {
                o.optional = true;
 
                o = ss.option(form.Value, 'description', _('QR-Code'));
+               o.modalonly = true;
                o.render = L.bind(function (view, section_id) {
                        var sections = uci.sections('network');
                        var client = findSection(sections, section_id);
@@ -259,10 +263,12 @@ return network.registerProtocol('wireguard', {
                }, this);
 
                o = ss.option(form.Value, 'public_key', _('Public Key'), _('Required. Base64-encoded public key of peer.'));
+               o.modalonly = true;
                o.validate = validateBase64;
                o.rmempty = false;
 
                o = ss.option(form.Value, 'preshared_key', _('Preshared Key'), _('Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.'));
+               o.modalonly = true;
                o.password = true;
                o.validate = validateBase64;
                o.optional = true;
@@ -272,6 +278,7 @@ return network.registerProtocol('wireguard', {
                o.optional = true;
 
                o = ss.option(form.Flag, 'route_allowed_ips', _('Route Allowed IPs'), _('Optional. Create routes for Allowed IPs for this peer.'));
+               o.modalonly = true;
 
                o = ss.option(form.Value, 'endpoint_host', _('Endpoint Host'), _('Optional. Host of peer. Names are resolved prior to bringing up the interface.'));
                o.placeholder = 'vpn.example.com';
@@ -282,6 +289,7 @@ return network.registerProtocol('wireguard', {
                o.datatype = 'port';
 
                o = ss.option(form.Value, 'persistent_keepalive', _('Persistent Keep Alive'), _('Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25.'));
+               o.modalonly = true;
                o.datatype = 'range(0,65535)';
                o.placeholder = '0';
        },