luci-app-attendedsysupgrade: Render on client
authorPaul Spooren <mail@aparcar.org>
Wed, 10 Feb 2021 03:45:52 +0000 (17:45 -1000)
committerPaul Spooren <mail@aparcar.org>
Mon, 8 Mar 2021 09:17:06 +0000 (23:17 -1000)
Use new LuCI JavaScript code instead of a mix of Lua and JS.

Signed-off-by: Paul Spooren <mail@aparcar.org>
applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/configuration.js [new file with mode: 0644]
applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/overview.js [new file with mode: 0644]
applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm [deleted file]
applications/luci-app-attendedsysupgrade/root/usr/share/luci/menu.d/luci-app-attendedsysupgrade.json
applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/attendedsysupgrade.json [deleted file]
applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/luci-app-attendedsysupgrade.json [new file with mode: 0644]

diff --git a/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/configuration.js b/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/configuration.js
new file mode 100644 (file)
index 0000000..a99e646
--- /dev/null
@@ -0,0 +1,34 @@
+'use strict';
+'require view';
+'require form';
+
+return view.extend({
+       render: function() {
+               var m, s, o;
+
+               m = new form.Map('attendedsysupgrade', _('Attended Sysupgrade'),
+                       _('Attendedsysupgrade Configuration.')
+               );
+
+               s = m.section(form.TypedSection, 'server', _('Server'));
+               s.anonymous = true;
+
+               s.option(form.Value, 'url', _('Address'),
+                       _('Address of the sysupgrade server'));
+
+               s = m.section(form.TypedSection, 'client', _('Client'));
+               s.anonymous = true;
+
+               o = s.option(form.Flag, 'auto_search', _('Search on opening'),
+                       _('Search for new sysupgrades on opening the tab'));
+               o.default = '1';
+               o.rmempty = false;
+
+               o = s.option(form.Flag, 'advanced_mode', _('Advances Mode'),
+                       _('Show advanced options like packge list modification'));
+               o.default = '0';
+               o.rmempty = false;
+
+               return m.render();
+       }
+});
diff --git a/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/overview.js b/applications/luci-app-attendedsysupgrade/htdocs/luci-static/resources/view/attendedsysupgrade/overview.js
new file mode 100644 (file)
index 0000000..a46bb3d
--- /dev/null
@@ -0,0 +1,366 @@
+'use strict';
+'require view';
+'require form';
+'require uci';
+'require rpc';
+'require ui';
+'require poll';
+'require request';
+'require dom';
+
+var callPackagelist = rpc.declare({
+       object: 'rpc-sys',
+       method: 'packagelist',
+});
+
+var callSystemBoard = rpc.declare({
+       object: 'system',
+       method: 'board'
+});
+
+var callUpgradeStart = rpc.declare({
+       object: 'rpc-sys',
+       method: 'upgrade_start',
+       params: ["keep"]
+});
+
+function install_sysupgrade(url, keep, sha256) {
+       displayStatus("notice spinning", E('p', _('Downloading firmware from server to browser')));
+       request.get(url, {
+                       headers: {
+                               'Content-Type': 'application/x-www-form-urlencoded'
+                       },
+                       responseType: 'blob'
+               })
+               .then(response => {
+                       var form_data = new FormData();
+                       form_data.append("sessionid", rpc.getSessionID());
+                       form_data.append("filename", "/tmp/firmware.bin");
+                       form_data.append("filemode", 600);
+                       form_data.append("filedata", response.blob());
+
+                       displayStatus("notice spinning", E('p', _('Uploading firmware from browser to device')));
+                       request.get(L.env.cgi_base + "/cgi-upload", {
+                                       method: 'PUT',
+                                       content: form_data
+                               })
+                               .then(response => response.json())
+                               .then(response => {
+                                       if (response.sha256sum != sha256) {
+                                               displayStatus("warning", [
+                                                       E('b', _('Wrong checksum')),
+                                                       E('p', _('Error during download of firmware. Please try again')),
+                                                       E('div', {
+                                                               'class': 'btn',
+                                                               'click': ui.hideModal
+                                                       }, _('Close'))
+                                               ]);
+                                       } else {
+                                               displayStatus('warning spinning', E('p', _('Installing the sysupgrade. Do not unpower device!')));
+                                                       L.resolveDefault(callUpgradeStart(keep), {}).then(response => {
+                                                               if (keep) {
+                                                                       ui.awaitReconnect(window.location.host);
+                                                               } else {
+                                                                       ui.awaitReconnect('192.168.1.1', 'openwrt.lan');
+                                                               }
+                                                       });
+                                       }
+                               });
+               });
+}
+
+function request_sysupgrade(server_url, data) {
+       var res, req;
+
+       if (data.request_hash) {
+               req = request.get(server_url + "/api/build/" + data.request_hash)
+       } else {
+               req = request.post(server_url + "/api/build", {
+                       profile: data.board_name,
+                       version: data.version,
+                       packages: data.packages,
+                       diff_packages: true,
+               })
+       }
+
+       req.then(response => {
+               switch (response.status) {
+                       case 200:
+                               var res = response.json()
+                               var image;
+                               console.log(res)
+                               for (image of res.images) {
+                                       if (image.type == "sysupgrade") {
+                                               break;
+                                       }
+                               }
+                               if (image.name != undefined) {
+                                       var sysupgrade_url = server_url + "/store/" + res.bin_dir + "/" + image.name;
+
+                                       var keep = E('input', {
+                                               type: 'checkbox'
+                                       })
+                                       keep.checked = true;
+
+                                       var fields = [
+                                               _('Version'), res.version_number + ' ' + res.version_code,
+                                               _('File'), E('a', {
+                                                       'href': sysupgrade_url
+                                               }, image.name),
+                                               _('SHA256'), image.sha256,
+                                               _('Build Date'), res.build_at,
+                                               _('Target'), res.target,
+                                       ];
+
+                                       var table = E('div', {
+                                               'class': 'table'
+                                       });
+
+                                       for (var i = 0; i < fields.length; i += 2) {
+                                               table.appendChild(E('div', {
+                                                       'class': 'tr'
+                                               }, [
+                                                       E('div', {
+                                                               'class': 'td left',
+                                                               'width': '33%'
+                                                       }, [fields[i]]),
+                                                       E('div', {
+                                                               'class': 'td left'
+                                                       }, [(fields[i + 1] != null) ? fields[i + 1] : '?'])
+                                               ]));
+                                       }
+
+                                       var modal_body = [
+                                               table,
+                                               E('p', {}, E('label', {
+                                                       'class': 'btn'
+                                               }, [
+                                                       keep, ' ', _('Keep settings and retain the current configuration')
+                                               ])),
+                                               E('div', {
+                                                       'class': 'right'
+                                               }, [
+                                                       E('div', {
+                                                               'class': 'btn',
+                                                               'click': ui.hideModal
+                                                       }, _('Cancel')),
+                                                       ' ',
+                                                       E('div', {
+                                                               'class': 'btn cbi-button-action',
+                                                               'click': function() {
+                                                                       install_sysupgrade(sysupgrade_url, keep.checked, image.sha256)
+                                                               }
+                                                       }, _('Install Sysupgrade'))
+                                               ])
+                                       ]
+
+                                       ui.showModal(_('Successfully created sysupgrade image'), modal_body);
+                               }
+
+                               break;
+                       case 202:
+                               res = response.json()
+                               data.request_hash = res.request_hash;
+                               switch (res.status) {
+                                       case "queued":
+                                               displayStatus("notice spinning", E('p', _('Request in build queue')));
+                                               break;
+                                       case "started":
+                                               displayStatus("notice spinning", E('p', _('Building the sysupgrade image')));
+                                               break;
+                               }
+                               setTimeout(function() {
+                                       request_sysupgrade(server_url, data);
+                               }, 5000);
+                               break;
+                       case 400: // bad request
+                       case 422: // bad package
+                       case 500: // build failed
+                               res = response.json()
+                               var body = [
+                                       E('p', {}, _(res.message)),
+                                       E('p', {}, _("Please report the error message and request")),
+                                       E('b', {}, _("Request to server:")),
+                                       E('pre', {}, JSON.stringify(data, null, 4)),
+
+                               ]
+
+                               if (res.stdout) {
+                                       body.push(E('b', {}, "STDOUT:"))
+                                       body.push(E('pre', {}, res.stdout))
+
+                               }
+
+                               if (res.stderr) {
+                                       body.push(E('b', {}, "STDERR:"))
+                                       body.push(E('pre', {}, res.stderr))
+
+                               }
+
+                               body = body.concat([
+                                       E('div', {
+                                               'class': 'right'
+                                       }, [
+                                               E('div', {
+                                                       'class': 'btn',
+                                                       'click': ui.hideModal
+                                               }, _('Close'))
+                                       ])
+                               ]);
+                               ui.showModal(_('Error building the sysupgrade'), body);
+                               break;
+               }
+       });
+}
+
+function check_sysupgrade(server_url, current_version, board_name, packages) {
+       displayStatus("notice spinning", E('p', _('Searching for an available sysupgrade')));
+       var current_branch = current_version.split(".").slice(0, 2).join(".");
+       var candidates = [];
+       fetch(server_url + "/api/latest")
+               .then(response => response.json())
+               .then(response => {
+                       if (current_version == "SNAPSHOT") {
+                               candidates.push("SNAPSHOT");
+                       } else {
+                               for (let version of response["latest"]) {
+                                       var branch = version.split(".").slice(0, 2).join(".");
+
+                                       // already latest version installed
+                                       if (current_version == version) {
+                                               break;
+                                       }
+
+                                       // warn user that a new major release would be installed
+                                       //if (current_branch != branch) {
+                                       //      branch["warn_branch_jump"] = true;
+                                       //}
+
+                                       candidates.unshift(version);
+
+                                       // don't offer branches older than the current
+                                       if (current_branch == branch) {
+                                               break;
+                                       }
+                               }
+                       }
+                       if (candidates) {
+                               var m, s, o;
+                               var advanced_mode = uci.get_first('attendedsysupgrade', 'client', 'advanced_mode') || 0;
+
+                               console.log(candidates);
+
+                               var mapdata = {
+                                       request: {
+                                               board_name: board_name,
+                                               version: candidates[0],
+                                               packages: Object.keys(packages).sort(),
+                                       }
+                               }
+
+                               m = new form.JSONMap(mapdata, '');
+
+                               s = m.section(form.NamedSection, 'request', 'example', '',
+                                       'Use defaults for the safest update');
+                               o = s.option(form.ListValue, 'version', 'Select firmware version');
+                               for (let candidate of candidates) {
+                                       o.value(candidate, candidate);
+                               }
+
+                               if (advanced_mode == 1) {
+                                       o = s.option(form.Value, 'board_name', 'Board Name / Profile');
+                                       o = s.option(form.DynamicList, 'packages', 'Packages');
+                               }
+
+
+                               m.render()
+                                       .then(function(form_rendered) {
+                                               ui.showModal(_('New upgrade available'), [
+                                                       form_rendered,
+                                                       E('div', {
+                                                               'class': 'right'
+                                                       }, [
+                                                               E('div', {
+                                                                       'class': 'btn',
+                                                                       'click': ui.hideModal
+                                                               }, _('Cancel')),
+                                                               ' ',
+                                                               E('div', {
+                                                                       'class': 'btn cbi-button-action',
+                                                                       'click': function() {
+                                                                               m.save().then(foo => {
+                                                                                       request_sysupgrade(
+                                                                                               server_url, mapdata.request
+                                                                                       )
+                                                                               });
+                                                                       }
+                                                               }, _('Request Sysupgrade'))
+                                                       ])
+                                               ]);
+                                       });
+                       } else {
+                               ui.showModal(_('No upgrade available'), [
+                                       E('p', {}, _("The device runs the latest firmware version")),
+                                       E('div', {
+                                               'class': 'right'
+                                       }, [
+                                               E('div', {
+                                                       'class': 'btn',
+                                                       'click': ui.hideModal
+                                               }, _('Close'))
+                                       ])
+                               ]);
+                       }
+               });
+}
+
+function displayStatus(type, content) {
+       if (type) {
+               var message = ui.showModal('', '');
+
+               message.classList.add('alert-message');
+               DOMTokenList.prototype.add.apply(message.classList, type.split(/\s+/));
+
+               if (content)
+                       dom.content(message, content);
+       } else {
+               ui.hideModal();
+       }
+}
+
+return view.extend({
+       load: function() {
+               return Promise.all([
+                       L.resolveDefault(callPackagelist(), {}),
+                       L.resolveDefault(callSystemBoard(), {}),
+                       uci.load('attendedsysupgrade')
+               ]);
+       },
+       render: function(res) {
+               var packages = res[0].packages;
+               var current_version = res[1].release.version;
+               var board_name = res[1].board_name;
+               var auto_search = uci.get_first('attendedsysupgrade', 'client', 'auto_search') || 1;
+               var server_url = uci.get_first('attendedsysupgrade', 'server', 'url');
+
+               var view = [
+                       E('h2', _("Attended Sysupgrade")),
+                       E('p', _('The attended sysupgrade service allows to easily upgrade vanilla and custom firmware images.')),
+                       E('p', _('This is done by building a new firmware on demand via an online service.'))
+               ];
+
+               if (auto_search == 1) {
+                       check_sysupgrade(server_url, current_version, board_name, packages)
+               }
+
+               view.push(E('p', {
+                       'class': 'btn cbi-button-positive',
+                       'click': function() {
+                               check_sysupgrade(server_url, current_version, board_name, packages)
+                       }
+               }, _('Search for sysupgrade')));
+
+               return view;
+       },
+
+});
diff --git a/applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm b/applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm
deleted file mode 100644 (file)
index c925955..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<%
--- all lua code provided by https://github.com/jow-/
--- thank you very much!
-
-    function apply_acls(filename, session)
-        local json = require "luci.jsonc"
-        local util = require "luci.util"
-        local fs   = require "nixio.fs"
-
-        local grants = { }
-
-        local acl = json.parse(fs.readfile(filename))
-        if type(acl) ~= "table" then
-            return
-        end
-
-        local group, perms
-        for group, perms in pairs(acl) do
-            local perm, scopes
-            for perm, scopes in pairs(perms) do
-                if type(scopes) == "table" then
-                    local scope, objects
-                    for scope, objects in pairs(scopes) do
-                        if type(objects) == "table" then
-                            if not grants[scope] then
-                                grants[scope] = { }
-                            end
-
-                            if next(objects) == 1 then
-                                local _, object
-                                for _, object in ipairs(objects) do
-                                    if not grants[scope][object] then
-                                        grants[scope][object] = { }
-                                    end
-                                    table.insert(grants[scope][object], perm)
-                                end
-                            else
-                                local object, funcs
-                                for object, funcs in pairs(objects) do
-                                    if type(funcs) == "table" then
-                                        local _, func
-                                        for _, func in ipairs(funcs) do
-                                            if not grants[scope][object] then
-                                                grants[scope][object] = { }
-                                            end
-                                            table.insert(grants[scope][object], func)
-                                        end
-                                    end
-                                end
-                            end
-                        end
-                    end
-                end
-            end
-        end
-
-        local _, scope, object, func
-        for scope, _ in pairs(grants) do
-            local objects = { }
-            for object, _ in pairs(_) do
-                for _, func in ipairs(_) do
-                    table.insert(objects, { object, func })
-                end
-            end
-
-            util.ubus("session", "grant", {
-                ubus_rpc_session = session,
-                scope = scope, objects = objects
-            })
-        end
-    end
-
-    apply_acls("/usr/share/rpcd/acl.d/attendedsysupgrade.json", luci.dispatcher.context.authsession)
-%>
-<%+header%>
-<h2 name="content"><%:Attended Sysupgrade%></h2>
-<div class="cbi-map-descr">
-       Easily search and install new releases and package upgrades. Sysupgrade firmware are created on demand based on locally installed packages.
-</div>
-<div style="display: none" id="status_box" class="alert-message info"></div>
-<div style="display: none" id="packages" class="alert-message success"></div>
-<p>
-<textarea style="display: none; width: 100%;" id="edit_packages" rows="15"></textarea>
-</p>
-<fieldset class="cbi-section">
-       <form method="post" action="">
-               <div class="cbi-selection-node">
-                       <div class="cbi-value" id="keep_container" style="display: none">
-                               <div class="cbi-section-descr">
-                                       Check "Keep settings" to retain the current configuration (requires a compatible firmware).
-                               </div>
-                               <label class="cbi-value-title" for="keep">Keep settings:</label>
-                               <div class="cbi-value-field">
-                                       <input name="keep" id="keep" checked="checked" type="checkbox">
-                               </div>
-                       </div>
-                       <div class="cbi-value" id="edit_button" style="display: none">
-                               <div class="cbi-value-field">
-                                       <input class="cbi-button" value="Edit installed packages" onclick="edit_packages()" type="button">
-                               </div>
-                       </div>
-                       <div class="cbi-value cbi-value" id="server_div" style="display:none">
-                               <label class="cbi-value-title" for="server">Server:</label>
-                               <div class="cbi-value-field">
-                                       <input onclick="edit_server()" class="cbi-button cbi-button-edit" value="" type="button" id="server" name="server">
-                               </div>
-                       </div>
-                       <div class="cbi-value cbi-value-last">
-                               <div class="cbi-value-field">
-                                       <input class="cbi-button cbi-button-apply" value="Search for upgrades" style="display: none" onclick="upgrade_check()" type="button" id="upgrade_button">
-                               </div>
-                       </div>
-               </div>
-       </form>
-</fieldset>
-<script type="text/javascript">
-       data = {};
-       data["ubus_rpc_session"] = "<%=luci.dispatcher.context.authsession%>"
-       origin = document.location.href.replace(location.pathname, "")
-       ubus_url = origin + "/ubus/"
-</script>
-<script type="text/javascript" src="<%=resource%>/attendedsysupgrade.js"></script>
-<%+footer%>
index 79d82a828f59fc974a524a305bac1ec4be0a8bdc..33d7019fa31cc13cf9d5bfdcca13ea852614d9e2 100644 (file)
@@ -1,13 +1,31 @@
 {
-       "admin/system/attended_sysupgrade": {
+       "admin/system/attendedsysupgrade": {
                "title": "Attended Sysupgrade",
-               "order": 1,
+               "order": 60,
                "action": {
-                       "type": "template",
-                       "path": "attendedsysupgrade"
+                       "type": "firstchild"
                },
                "depends": {
-                       "acl": [ "attendedsysupgrade" ]
+                       "acl": [ "luci-app-attendedsysupgrade" ],
+                       "uci": { "attendedsysupgrade": true }
+               }
+       },
+
+       "admin/system/attendedsysupgrade/overview": {
+               "title": "Overview",
+               "order": 1,
+               "action": {
+                       "type": "view",
+                       "path": "attendedsysupgrade/overview"
+               }
+       },
+
+       "admin/system/attendedsysupgrade/configuration": {
+               "title": "Configuration",
+               "order": 2,
+               "action": {
+                       "type": "view",
+                       "path": "attendedsysupgrade/configuration"
                }
        }
 }
diff --git a/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/attendedsysupgrade.json b/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/attendedsysupgrade.json
deleted file mode 100644 (file)
index 7549319..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-       "attendedsysupgrade": {
-               "description": "attended sysupgrade via rpcd and luci",
-               "read": {
-                       "ubus": {
-                               "rpc-sys": [
-                                       "upgrade_start",
-                                       "packagelist"
-                               ],
-                               "system": [
-                                       "board",
-                                       "info"
-                               ],
-                               "uci": [
-                                       "get", "set", "commit"
-                               ]
-                       },
-                       "uci": [
-                               "attendedsysupgrade"
-                       ]
-               },
-               "write": {
-                       "cgi-io": [
-                               "upload"
-                       ],
-                       "uci": [
-                               "attendedsysupgrade"
-                       ]
-               }
-       }
-}
diff --git a/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/luci-app-attendedsysupgrade.json b/applications/luci-app-attendedsysupgrade/root/usr/share/rpcd/acl.d/luci-app-attendedsysupgrade.json
new file mode 100644 (file)
index 0000000..e3ceeaa
--- /dev/null
@@ -0,0 +1,17 @@
+{
+       "luci-app-attendedsysupgrade": {
+               "description": "Grant UCI access to LuCI app attendedsysupgrade",
+               "read": {
+                       "uci": ["attendedsysupgrade"],
+                       "ubus": {
+                               "rpc-sys": ["upgrade_start", "packagelist"]
+                       }
+               },
+               "write": {
+                       "uci": ["attendedsysupgrade"],
+                       "ubus": {
+                               "rpc-sys": ["upgrade_start"]
+                       }
+               }
+       }
+}