luci-app-opkg: add ipk upload feature
authorRichard Yu <yurichard3839@gmail.com>
Wed, 23 Oct 2019 12:22:11 +0000 (20:22 +0800)
committerJo-Philipp Wich <jo@mein.io>
Fri, 1 Nov 2019 11:03:33 +0000 (12:03 +0100)
Signed-off-by: Richard Yu <yurichard3839@gmail.com>
[fix duplicate upload errors, remove temporary file, tweak style]
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
applications/luci-app-opkg/htdocs/luci-static/resources/view/opkg.js
applications/luci-app-opkg/luasrc/view/opkg.htm
modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json

index 20fbf430befe1ccbebb058341239db0b143bb225..5442e794b070abba43dfcab732cf0f70e2507cba 100644 (file)
@@ -745,42 +745,83 @@ function handleRemove(ev)
 
 function handleOpkg(ev)
 {
-       var cmd = ev.target.getAttribute('data-command'),
-           pkg = ev.target.getAttribute('data-package'),
-           rem = document.querySelector('input[name="autoremove"]'),
-           owr = document.querySelector('input[name="overwrite"]'),
-           url = 'admin/system/opkg/exec/' + encodeURIComponent(cmd);
-
-       var dlg = L.showModal(_('Executing package manager'), [
-               E('p', { 'class': 'spinning' },
-                       _('Waiting for the <em>opkg %h</em> command to complete…').format(cmd))
-       ]);
+       return new Promise(function(resolveFn, rejectFn) {
+               var cmd = ev.target.getAttribute('data-command'),
+                   pkg = ev.target.getAttribute('data-package'),
+                   rem = document.querySelector('input[name="autoremove"]'),
+                   owr = document.querySelector('input[name="overwrite"]'),
+                   url = 'admin/system/opkg/exec/' + encodeURIComponent(cmd);
+
+               var dlg = L.showModal(_('Executing package manager'), [
+                       E('p', { 'class': 'spinning' },
+                               _('Waiting for the <em>opkg %h</em> command to complete…').format(cmd))
+               ]);
 
-       L.post(url, { package: pkg, autoremove: rem ? rem.checked : false, overwrite: owr ? owr.checked : false }, function(xhr, res) {
-               dlg.removeChild(dlg.lastChild);
+               L.post(url, { package: pkg, autoremove: rem ? rem.checked : false, overwrite: owr ? owr.checked : false }, function(xhr, res) {
+                       dlg.removeChild(dlg.lastChild);
 
-               if (res.stdout)
-                       dlg.appendChild(E('pre', [ res.stdout ]));
+                       if (res.stdout)
+                               dlg.appendChild(E('pre', [ res.stdout ]));
 
-               if (res.stderr) {
-                       dlg.appendChild(E('h5', _('Errors')));
-                       dlg.appendChild(E('pre', { 'class': 'errors' }, [ res.stderr ]));
-               }
+                       if (res.stderr) {
+                               dlg.appendChild(E('h5', _('Errors')));
+                               dlg.appendChild(E('pre', { 'class': 'errors' }, [ res.stderr ]));
+                       }
 
-               if (res.code !== 0)
-                       dlg.appendChild(E('p', _('The <em>opkg %h</em> command failed with code <code>%d</code>.').format(cmd, (res.code & 0xff) || -1)));
+                       if (res.code !== 0)
+                               dlg.appendChild(E('p', _('The <em>opkg %h</em> command failed with code <code>%d</code>.').format(cmd, (res.code & 0xff) || -1)));
 
-               dlg.appendChild(E('div', { 'class': 'right' },
-                       E('div', {
-                               'class': 'btn',
-                               'click': function() {
-                                       L.hideModal();
-                                       updateLists();
-                               }
-                       }, _('Dismiss'))));
+                       dlg.appendChild(E('div', { 'class': 'right' },
+                               E('div', {
+                                       'class': 'btn',
+                                       'click': L.bind(function(res) {
+                                               L.hideModal();
+                                               updateLists();
+
+                                               if (res.code !== 0)
+                                                       rejectFn(new Error(res.stderr || 'opkg error %d'.format(res.code)));
+                                               else
+                                                       resolveFn(res);
+                                       }, this, res)
+                               }, _('Dismiss'))));
+               });
        });
 }
 
+function handleUpload(ev)
+{
+       var path = '/tmp/upload.ipk';
+       return L.ui.uploadFile(path).then(L.bind(function(btn, res) {
+               L.showModal(_('Manually install package'), [
+                       E('p', {}, _('Installing packages from untrusted sources is a potential security risk! Really attempt to install <em>%h</em>?').format(res.name)),
+                       E('ul', {}, [
+                               res.size ? E('li', {}, '%s: %1024.2mB'.format(_('Size'), res.size)) : '',
+                               res.checksum ? E('li', {}, '%s: %s'.format(_('MD5'), res.checksum)) : '',
+                               res.sha256sum ? E('li', {}, '%s: %s'.format(_('SHA256'), res.sha256sum)) : ''
+                       ]),
+                       E('div', { 'class': 'right' }, [
+                               E('div', {
+                                       'click': function(ev) {
+                                               L.hideModal();
+                                               L.fs.remove(path);
+                                       },
+                                       'class': 'btn cbi-button-neutral'
+                               }, _('Cancel')), ' ',
+                               E('div', {
+                                       'class': 'btn cbi-button-action',
+                                       'data-command': 'install',
+                                       'data-package': path,
+                                       'click': function(ev) {
+                                               handleOpkg(ev).finally(function() {
+                                                       L.fs.remove(path)
+                                               });
+                                       }
+                               }, _('Install'))
+                       ])
+               ]);
+       }, this, ev.target));
+}
+
 function updateLists()
 {
        cbi_update_table('#packages', [],
index 0d2a4e2920f8f4bacc31653145697f9a7a870fd6..297891dbd83cee1c410768511e1a11be22161e20 100644 (file)
                min-width: 250px;
        }
 
+       .controls > *:nth-child(2),
+       .controls > *:nth-child(3) {
+               flex-basis: 20%;
+       }
+
        .controls > * > .btn {
                flex-basis: 20px;
                text-align: center;
                <label><%:Actions%>:</label>
                <button class="btn cbi-button-positive" data-command="update" onclick="handleOpkg(event)"><%:Update lists…%></button>
                &#160;
+               <button class="btn cbi-button-action" onclick="handleUpload(event)"><%:Upload Package…%></button>
+               &#160;
                <button class="btn cbi-button-neutral" onclick="handleConfig(event)"><%:Configure opkg…%></button>
        </div>
 </div>
index 098af140da1d2fb054f0080ca460d423c0ce8fad..a09c6b424503dab320d5a70429d118343cb80487 100644 (file)
@@ -69,7 +69,8 @@
                                "/bin/tar": [ "exec" ],
                                "/bin/umount": [ "exec" ],
                                "/tmp/backup.tar.gz": [ "write" ],
-                               "/tmp/firmware.bin": [ "write" ]
+                               "/tmp/firmware.bin": [ "write" ],
+                               "/tmp/upload.ipk": [ "write" ]
                        },
                        "ubus": {
                                "file": [ "write", "remove", "exec" ],