diff options
6 files changed, 295 insertions, 341 deletions
diff --git a/applications/luci-app-opkg/htdocs/luci-static/resources/view/opkg.js b/applications/luci-app-opkg/htdocs/luci-static/resources/view/opkg.js index c7cb55fc4..59ca2cd95 100644 --- a/applications/luci-app-opkg/htdocs/luci-static/resources/view/opkg.js +++ b/applications/luci-app-opkg/htdocs/luci-static/resources/view/opkg.js @@ -1,3 +1,95 @@ +'use strict'; +'require fs'; +'require ui'; +'require rpc'; + +var css = ' \ + .controls { \ + display: flex; \ + margin: .5em 0 1em 0; \ + flex-wrap: wrap; \ + justify-content: space-around; \ + } \ + \ + .controls > * { \ + padding: .25em; \ + white-space: nowrap; \ + flex: 1 1 33%; \ + box-sizing: border-box; \ + display: flex; \ + flex-wrap: wrap; \ + } \ + \ + .controls > *:first-child, \ + .controls > * > label { \ + flex-basis: 100%; \ + min-width: 250px; \ + } \ + \ + .controls > *:nth-child(2), \ + .controls > *:nth-child(3) { \ + flex-basis: 20%; \ + } \ + \ + .controls > * > .btn { \ + flex-basis: 20px; \ + text-align: center; \ + } \ + \ + .controls > * > * { \ + flex-grow: 1; \ + align-self: center; \ + } \ + \ + .controls > div > input { \ + width: auto; \ + } \ + \ + .td.version, \ + .td.size { \ + white-space: nowrap; \ + } \ + \ + ul.deps, ul.deps ul, ul.errors { \ + margin-left: 1em; \ + } \ + \ + ul.deps li, ul.errors li { \ + list-style: none; \ + } \ + \ + ul.deps li:before { \ + content: "↳"; \ + display: inline-block; \ + width: 1em; \ + margin-left: -1em; \ + } \ + \ + ul.deps li > span { \ + white-space: nowrap; \ + } \ + \ + ul.errors li { \ + color: #c44; \ + font-size: 90%; \ + font-weight: bold; \ + padding-left: 1.5em; \ + } \ + \ + ul.errors li:before { \ + content: "⚠"; \ + display: inline-block; \ + width: 1.5em; \ + margin-left: -1.5em; \ + } \ +'; + +var callMountPoints = rpc.declare({ + object: 'luci', + method: 'getMountPoints', + expect: { result: [] } +}); + var packages = { available: { providers: {}, pkgs: {} }, installed: { providers: {}, pkgs: {} } @@ -471,6 +563,8 @@ function renderDependencies(depends, info) info.seen = info.seen || []; for (var i = 0; i < deps.length; i++) { + var dep, vop, ver; + if (deps[i] === 'libc') continue; @@ -580,7 +674,7 @@ function handleInstall(ev) ]); } - L.showModal(_('Details for package <em>%h</em>').format(pkg.name), [ + ui.showModal(_('Details for package <em>%h</em>').format(pkg.name), [ E('ul', {}, [ E('li', '<strong>%s:</strong> %h'.format(_('Version'), pkg.version)), E('li', '<strong>%s:</strong> %h'.format(_('Size'), size)), @@ -595,7 +689,7 @@ function handleInstall(ev) ]), E('div', { 'class': 'btn', - 'click': L.hideModal + 'click': ui.hideModal }, _('Cancel')), ' ', E('div', { @@ -635,11 +729,11 @@ function handleManualInstall(ev) warning = E('p', {}, _('Really attempt to install <em>%h</em>?').format(name_or_url)); } - L.showModal(_('Manually install package'), [ + ui.showModal(_('Manually install package'), [ warning, E('div', { 'class': 'right' }, [ E('div', { - 'click': L.hideModal, + 'click': ui.hideModal, 'class': 'btn cbi-button-neutral' }, _('Cancel')), ' ', install @@ -649,11 +743,29 @@ function handleManualInstall(ev) function handleConfig(ev) { - L.showModal(_('OPKG Configuration'), [ + var conf = {}; + + ui.showModal(_('OPKG Configuration'), [ E('p', { 'class': 'spinning' }, _('Loading configuration data…')) ]); - L.get('admin/system/opkg/config', null, function(xhr, conf) { + fs.list('/etc/opkg').then(function(partials) { + var files = [ '/etc/opkg.conf' ]; + + for (var i = 0; i < partials.length; i++) + if (partials[i].type == 'file' && partials[i].name.match(/\.conf$/)) + files.push('/etc/opkg/' + partials[i].name); + + return Promise.all(files.map(function(file) { + return fs.read(file) + .then(L.bind(function(conf, file, res) { conf[file] = res }, this, conf, file)) + .catch(function(err) { + ui.addNotification(null, E('p', {}, [ _('Unable to read %s: %s').format(file, err) ])); + ui.hideModal(); + throw err; + }); + })); + }).then(function() { var body = [ E('p', {}, _('Below is a listing of the various configuration files used by <em>opkg</em>. Use <em>opkg.conf</em> for global settings and <em>customfeeds.conf</em> for custom repository entries. The configuration in the other files may be changed but is usually not preserved by <em>sysupgrade</em>.')) ]; @@ -669,7 +781,7 @@ function handleConfig(ev) body.push(E('div', { 'class': 'right' }, [ E('div', { 'class': 'btn cbi-button-neutral', - 'click': L.hideModal + 'click': ui.hideModal }, _('Cancel')), ' ', E('div', { @@ -681,16 +793,20 @@ function handleConfig(ev) data[textarea.getAttribute('name')] = textarea.value }); - L.showModal(_('OPKG Configuration'), [ + ui.showModal(_('OPKG Configuration'), [ E('p', { 'class': 'spinning' }, _('Saving configuration data…')) ]); - L.post('admin/system/opkg/config', { data: JSON.stringify(data) }, L.hideModal); + Promise.all(Object.keys(data).map(function(file) { + return fs.write(file, data[file]).catch(function(err) { + ui.addNotification(null, E('p', {}, [ _('Unable to save %s: %s').format(file, err) ])); + }); + })).then(ui.hideModal); } }, _('Save')), ])); - L.showModal(_('OPKG Configuration'), body); + ui.showModal(_('OPKG Configuration'), body); }); } @@ -715,7 +831,7 @@ function handleRemove(ev) ]); } - L.showModal(_('Remove package <em>%h</em>').format(pkg.name), [ + ui.showModal(_('Remove package <em>%h</em>').format(pkg.name), [ E('ul', {}, [ E('li', '<strong>%s:</strong> %h'.format(_('Version'), pkg.version)), E('li', '<strong>%s:</strong> %h'.format(_('Size'), size)) @@ -729,7 +845,7 @@ function handleRemove(ev) E('div', { 'style': 'flex-grow:1', 'class': 'right' }, [ E('div', { 'class': 'btn', - 'click': L.hideModal + 'click': ui.hideModal }, _('Cancel')), ' ', E('div', { @@ -749,15 +865,27 @@ 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); + owr = document.querySelector('input[name="overwrite"]'); - var dlg = L.showModal(_('Executing package manager'), [ + var dlg = ui.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) { + var argv = [ '--force-removal-of-dependent-packages' ]; + + if (rem && rem.checked) + argv.push('--autoremove'); + + if (owr && owr.checked) + argv.push('--force-overwrite'); + + argv.push(cmd); + + if (pkg != null) + argv.push(pkg); + + fs.exec('/bin/opkg', argv).then(function(res) { dlg.removeChild(dlg.lastChild); if (res.stdout) @@ -775,7 +903,7 @@ function handleOpkg(ev) E('div', { 'class': 'btn', 'click': L.bind(function(res) { - L.hideModal(); + ui.hideModal(); updateLists(); if (res.code !== 0) @@ -784,6 +912,9 @@ function handleOpkg(ev) resolveFn(res); }, this, res) }, _('Dismiss')))); + }).catch(function(err) { + ui.addNotification(null, E('p', _('Unable to execute <em>opkg %s</em> command: %s').format(cmd, err))); + ui.hideModal(); }); }); } @@ -791,8 +922,8 @@ function handleOpkg(ev) function handleUpload(ev) { var path = '/tmp/upload.ipk'; - return L.ui.uploadFile(path).then(L.bind(function(btn, res) { - L.showModal(_('Manually install package'), [ + return ui.uploadFile(path).then(L.bind(function(btn, res) { + ui.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)) : '', @@ -802,8 +933,8 @@ function handleUpload(ev) E('div', { 'class': 'right' }, [ E('div', { 'click': function(ev) { - L.hideModal(); - L.fs.remove(path); + ui.hideModal(); + fs.remove(path); }, 'class': 'btn cbi-button-neutral' }, _('Cancel')), ' ', @@ -813,7 +944,7 @@ function handleUpload(ev) 'data-package': path, 'click': function(ev) { handleOpkg(ev).finally(function() { - L.fs.remove(path) + fs.remove(path) }); } }, _('Install')) @@ -822,7 +953,16 @@ function handleUpload(ev) }, this, ev.target)); } -function updateLists() +function downloadLists() +{ + return Promise.all([ + callMountPoints(), + fs.exec_direct('/usr/libexec/opkg-list', [ 'available' ]), + fs.exec_direct('/usr/libexec/opkg-list', [ 'installed' ]) + ]); +} + +function updateLists(data) { cbi_update_table('#packages', [], E('div', { 'class': 'spinning' }, _('Loading package information…'))); @@ -830,42 +970,104 @@ function updateLists() packages.available = { providers: {}, pkgs: {} }; packages.installed = { providers: {}, pkgs: {} }; - L.get('admin/system/opkg/statvfs', null, function(xhr, stat) { + return (data ? Promise.resolve(data) : downloadLists()).then(function(data) { var pg = document.querySelector('.cbi-progressbar'), - total = stat.blocks || 0, - free = stat.bfree || 0; + mount = L.toArray(data[0].filter(function(m) { return m.mount == '/' || m.mount == '/overlay' })) + .sort(function(a, b) { return a.mount > b.mount })[0] || { size: 0, free: 0 }; - pg.firstElementChild.style.width = Math.floor(total ? ((100 / total) * free) : 100) + '%'; - pg.setAttribute('title', '%s (%.1024mB)'.format(pg.firstElementChild.style.width, free * (stat.frsize || 0))); + pg.firstElementChild.style.width = Math.floor(mount.size ? ((100 / mount.size) * mount.free) : 100) + '%'; + pg.setAttribute('title', '%s (%.1024mB)'.format(pg.firstElementChild.style.width, mount.free)); - L.get('admin/system/opkg/list/available', null, function(xhr) { - parseList(xhr.responseText, packages.available); - L.get('admin/system/opkg/list/installed', null, function(xhr) { - parseList(xhr.responseText, packages.installed); - display(document.querySelector('input[name="filter"]').value); - }); - }); + parseList(data[1], packages.available); + parseList(data[2], packages.installed); + + display(document.querySelector('input[name="filter"]').value); }); } -window.requestAnimationFrame(function() { - var filter = document.querySelector('input[name="filter"]'), - keyTimeout = null; +var keyTimeout = null; + +function handleKeyUp(ev) { + if (keyTimeout !== null) + window.clearTimeout(keyTimeout); + + keyTimeout = window.setTimeout(function() { + display(ev.target.value); + }, 250); +} + +return L.view.extend({ + load: function() { + return downloadLists(); + }, + + render: function(listData) { + var query = decodeURIComponent(L.toArray(location.search.match(/\bquery=([^=]+)\b/))[1] || ''); + + var view = E([], [ + E('style', { 'type': 'text/css' }, [ css ]), + + E('h2', {}, _('Software')), + + E('div', { 'class': 'controls' }, [ + E('div', {}, [ + E('label', {}, _('Free space') + ':'), + E('div', { 'class': 'cbi-progressbar', 'title': _('unknown') }, E('div', {}, [ '\u00a0' ])) + ]), + + E('div', {}, [ + E('label', {}, _('Filter') + ':'), + E('input', { 'type': 'text', 'name': 'filter', 'placeholder': _('Type to filter…'), 'value': query, 'keyup': handleKeyUp }), + E('button', { 'class': 'btn cbi-button', 'click': handleReset }, [ _('Clear') ]) + ]), + + E('div', {}, [ + E('label', {}, _('Download and install package') + ':'), + E('input', { 'type': 'text', 'name': 'install', 'placeholder': _('Package name or URL…'), 'keydown': function(ev) { if (ev.keyCode === 13) handleManualInstall(ev) } }), + E('button', { 'class': 'btn cbi-button cbi-button-action', 'click': handleManualInstall }, [ _('OK') ]) + ]), + + E('div', {}, [ + E('label', {}, _('Actions') + ':'), ' ', + E('button', { 'class': 'btn cbi-button-positive', 'data-command': 'update', 'click': handleOpkg }, [ _('Update lists…') ]), '\u00a0', + E('button', { 'class': 'btn cbi-button-action', 'click': handleUpload }, [ _('Upload Package…') ]), '\u00a0', + E('button', { 'class': 'btn cbi-button-neutral', 'click': handleConfig }, [ _('Configure opkg…') ]) + ]) + ]), + + E('ul', { 'class': 'cbi-tabmenu mode' }, [ + E('li', { 'data-mode': 'available', 'class': 'available cbi-tab', 'click': handleMode }, E('a', { 'href': '#' }, [ _('Available') ])), + E('li', { 'data-mode': 'installed', 'class': 'installed cbi-tab-disabled', 'click': handleMode }, E('a', { 'href': '#' }, [ _('Installed') ])), + E('li', { 'data-mode': 'updates', 'class': 'installed cbi-tab-disabled', 'click': handleMode }, E('a', { 'href': '#' }, [ _('Updates') ])) + ]), - filter.value = filter.getAttribute('value'); - filter.addEventListener('keyup', - function(ev) { - if (keyTimeout !== null) - window.clearTimeout(keyTimeout); + E('div', { 'class': 'controls', 'style': 'display:none' }, [ + E('div', { 'id': 'pager', 'class': 'center' }, [ + E('button', { 'class': 'btn cbi-button-neutral prev', 'aria-label': _('Previous page'), 'click': handlePage }, [ '«' ]), + E('div', { 'class': 'text' }, [ 'dummy' ]), + E('button', { 'class': 'btn cbi-button-neutral next', 'aria-label': _('Next page'), 'click': handlePage }, [ '»' ]) + ]) + ]), + + E('div', { 'id': 'packages', 'class': 'table' }, [ + E('div', { 'class': 'tr cbi-section-table-titles' }, [ + E('div', { 'class': 'th col-2 left' }, [ _('Package name') ]), + E('div', { 'class': 'th col-2 left version' }, [ _('Version') ]), + E('div', { 'class': 'th col-1 center size'}, [ _('Size (.ipk)') ]), + E('div', { 'class': 'th col-10 left' }, [ _('Description') ]), + E('div', { 'class': 'th right' }, [ '\u00a0' ]) + ]) + ]) + ]); - keyTimeout = window.setTimeout(function() { - display(ev.target.value); - }, 250); + requestAnimationFrame(function() { + updateLists(listData) }); - document.querySelector('#pager > .prev').addEventListener('click', handlePage); - document.querySelector('#pager > .next').addEventListener('click', handlePage); - document.querySelector('.cbi-tabmenu.mode').addEventListener('click', handleMode); + return view; + }, - updateLists(); + handleSave: null, + handleSaveApply: null, + handleReset: null }); diff --git a/applications/luci-app-opkg/luasrc/controller/opkg.lua b/applications/luci-app-opkg/luasrc/controller/opkg.lua deleted file mode 100644 index ebdcf1b09..000000000 --- a/applications/luci-app-opkg/luasrc/controller/opkg.lua +++ /dev/null @@ -1,109 +0,0 @@ --- Copyright 2018 Jo-Philipp Wich <jo@mein.io> --- Licensed to the public under the Apache License 2.0. - -module("luci.controller.opkg", package.seeall) - -function action_list(mode) - local util = require "luci.util" - local cmd - - if mode == "installed" then - cmd = { "/bin/cat", "/usr/lib/opkg/status" } - else - local lists_dir = nil - - local fd = io.popen([[sed -rne 's#^lists_dir \S+ (\S+)#\1#p' /etc/opkg.conf /etc/opkg/*.conf 2>/dev/null]], "r") - if fd then - lists_dir = fd:read("*l") - fd:close() - end - - if not lists_dir or #lists_dir == 0 then - lists_dir = "/tmp/opkg-lists" - end - - cmd = { "/bin/sh", "-c", [[find %s -type f '!' -name '*.sig' | xargs -r gzip -cd]] % util.shellquote(lists_dir) } - end - - luci.http.prepare_content("text/plain; charset=utf-8") - luci.sys.process.exec(cmd, luci.http.write) -end - -function action_exec(command, package) - local sys = require "luci.sys" - local cmd = { "/bin/opkg", "--force-removal-of-dependent-packages" } - local pkg = luci.http.formvalue("package") - - if luci.http.formvalue("autoremove") == "true" then - cmd[#cmd + 1] = "--autoremove" - end - - if luci.http.formvalue("overwrite") == "true" then - cmd[#cmd + 1] = "--force-overwrite" - end - - cmd[#cmd + 1] = command - - if pkg then - cmd[#cmd + 1] = pkg - end - - luci.http.prepare_content("application/json") - luci.http.write_json(sys.process.exec(cmd, true, true)) -end - -function action_statvfs() - local fs = require "nixio.fs" - - luci.http.prepare_content("application/json") - luci.http.write_json(fs.statvfs("/") or {}) -end - -function action_config() - local fs = require "nixio.fs" - local js = require "luci.jsonc" - local data = luci.http.formvalue("data") - - if data then - data = js.parse(data) - - if not data then - luci.http.status(400, "Bad Request") - return - end - - local file, content - for file, content in pairs(data) do - if type(content) ~= "string" or - (file ~= "opkg.conf" and not file:match("^opkg/[^/]+%.conf$")) - then - luci.http.status(400, "Bad Request") - return - end - - local path = "/etc/%s" % file - if not fs.access(path, "w") then - luci.http.status(403, "Permission denied") - return - end - - fs.writefile(path, content:gsub("\r\n", "\n")) - end - - luci.http.status(204, "Saved") - else - local rv = { ["opkg.conf"] = fs.readfile("/etc/opkg.conf") } - local entries = fs.dir("/etc/opkg") - if entries then - local entry - for entry in entries do - if entry:match("%.conf$") then - rv["opkg/%s" % entry] = fs.readfile("/etc/opkg/%s" % entry) - end - end - end - - luci.http.prepare_content("application/json") - luci.http.write_json(rv) - end -end diff --git a/applications/luci-app-opkg/luasrc/view/opkg.htm b/applications/luci-app-opkg/luasrc/view/opkg.htm deleted file mode 100644 index 297891dbd..000000000 --- a/applications/luci-app-opkg/luasrc/view/opkg.htm +++ /dev/null @@ -1,147 +0,0 @@ -<%# - Copyright 2018 Jo-Philipp Wich <jo@mein.io> - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -<style type="text/css"> - .controls { - display: flex; - margin: .5em 0 1em 0; - flex-wrap: wrap; - justify-content: space-around; - } - - .controls > * { - padding: .25em; - white-space: nowrap; - flex: 1 1 33%; - box-sizing: border-box; - display: flex; - flex-wrap: wrap; - } - - .controls > *:first-child, - .controls > * > label { - flex-basis: 100%; - min-width: 250px; - } - - .controls > *:nth-child(2), - .controls > *:nth-child(3) { - flex-basis: 20%; - } - - .controls > * > .btn { - flex-basis: 20px; - text-align: center; - } - - .controls > * > * { - flex-grow: 1; - align-self: center; - } - - .controls > div > input { - width: auto; - } - - .td.version, - .td.size { - white-space: nowrap; - } - - ul.deps, ul.deps ul, ul.errors { - margin-left: 1em; - } - - ul.deps li, ul.errors li { - list-style: none; - } - - ul.deps li:before { - content: "↳"; - display: inline-block; - width: 1em; - margin-left: -1em; - } - - ul.deps li > span { - white-space: nowrap; - } - - ul.errors li { - color: #c44; - font-size: 90%; - font-weight: bold; - padding-left: 1.5em; - } - - ul.errors li:before { - content: "⚠"; - display: inline-block; - width: 1.5em; - margin-left: -1.5em; - } -</style> - -<h2><%:Software%></h2> - -<div class="controls"> - <div> - <label><%:Free space%>:</label> - <div class="cbi-progressbar" title="<%:unknown%>"> - <div> </div> - </div> - </div> - - <div> - <label><%:Filter%>:</label> - <input type="text" name="filter" placeholder="<%:Type to filter…%>"<%=attr("value", luci.http.formvalue("query") or "")%> /><!-- - --><button class="btn cbi-button" onclick="handleReset(event)"><%:Clear%></button> - </div> - - <div> - <label><%:Download and install package%>:</label> - <input type="text" name="install" placeholder="<%:Package name or URL…%>" onkeydown="if (event.keyCode === 13) handleManualInstall(event)" /><!-- - --><button class="btn cbi-button cbi-button-action" onclick="handleManualInstall(event)"><%:OK%></button> - </div> - - <div> - <label><%:Actions%>:</label> - <button class="btn cbi-button-positive" data-command="update" onclick="handleOpkg(event)"><%:Update lists…%></button> -   - <button class="btn cbi-button-action" onclick="handleUpload(event)"><%:Upload Package…%></button> -   - <button class="btn cbi-button-neutral" onclick="handleConfig(event)"><%:Configure opkg…%></button> - </div> -</div> - -<ul class="cbi-tabmenu mode"> - <li data-mode="available" class="available cbi-tab"><a href="#"><%:Available%></a></li> - <li data-mode="installed" class="installed cbi-tab-disabled"><a href="#"><%:Installed%></a></li> - <li data-mode="updates" class="installed cbi-tab-disabled"><a href="#"><%:Updates%></a></li> -</ul> - -<div class="controls" style="display:none"> - <div id="pager" class="center"> - <button class="btn cbi-button-neutral prev" aria-label="<%:Previous page%>">«</button> - <div class="text">dummy</div> - <button class="btn cbi-button-neutral next" aria-label="<%:Next page%>">»</button> - </div> -</div> - -<div class="table" id="packages"> - <div class="tr cbi-section-table-titles"> - <div class="th col-2 left"><%:Package name%></div> - <div class="th col-2 left version"><%:Version%></div> - <div class="th col-1 center size"><%:Size (.ipk)%></div> - <div class="th col-10 left"><%:Description%></div> - <div class="th right"> </div> - </div> -</div> - -<script type="text/javascript" src="<%=resource%>/view/opkg.js"></script> - -<%+footer%> diff --git a/applications/luci-app-opkg/root/usr/libexec/opkg-list b/applications/luci-app-opkg/root/usr/libexec/opkg-list new file mode 100755 index 000000000..088bc6339 --- /dev/null +++ b/applications/luci-app-opkg/root/usr/libexec/opkg-list @@ -0,0 +1,15 @@ +#!/bin/sh + +case "$1" in + installed) + cat /usr/lib/opkg/status + ;; + available) + lists_dir=$(sed -rne 's#^lists_dir \S+ (\S+)#\1#p' /etc/opkg.conf /etc/opkg/*.conf 2>/dev/null | tail -n 1) + find "${lists_dir:-/tmp/opkg-lists}" -type f '!' -name '*.sig' | xargs -r gzip -cd + ;; + *) + echo "Usage: $0 {installed|available}" >&2 + exit 1 + ;; +esac diff --git a/applications/luci-app-opkg/root/usr/share/luci/menu.d/luci-app-opkg.json b/applications/luci-app-opkg/root/usr/share/luci/menu.d/luci-app-opkg.json index 9356b586d..8632a41b3 100644 --- a/applications/luci-app-opkg/root/usr/share/luci/menu.d/luci-app-opkg.json +++ b/applications/luci-app-opkg/root/usr/share/luci/menu.d/luci-app-opkg.json @@ -3,42 +3,8 @@ "title": "Software", "order": 30, "action": { - "type": "template", + "type": "view", "path": "opkg" } - }, - - "admin/system/opkg/list/*": { - "action": { - "type": "call", - "module": "luci.controller.opkg", - "function": "action_list" - } - }, - - "admin/system/opkg/exec/*": { - "action": { - "type": "call", - "post": true, - "module": "luci.controller.opkg", - "function": "action_exec" - } - }, - - "admin/system/opkg/statvfs/*": { - "action": { - "type": "call", - "module": "luci.controller.opkg", - "function": "action_statvfs" - } - }, - - "admin/system/opkg/config/*": { - "action": { - "type": "call", - "post": { "data": true }, - "module": "luci.controller.opkg", - "function": "action_config" - } } } diff --git a/applications/luci-app-opkg/root/usr/share/rpcd/acl.d/luci-app-opkg.json b/applications/luci-app-opkg/root/usr/share/rpcd/acl.d/luci-app-opkg.json new file mode 100644 index 000000000..66ef81f10 --- /dev/null +++ b/applications/luci-app-opkg/root/usr/share/rpcd/acl.d/luci-app-opkg.json @@ -0,0 +1,27 @@ +{ + "luci-app-opkg": { + "description": "Grant access to opkg management", + "read": { + "cgi-io": [ "exec" ], + "file": { + "/usr/libexec/opkg-list installed": [ "exec" ], + "/usr/libexec/opkg-list available": [ "exec" ], + "/etc/opkg.conf": [ "read" ], + "/etc/opkg/*.conf": [ "read" ] + }, + "ubus": { + "luci": [ "getMountPoints" ] + } + }, + "write": { + "file": { + "/bin/opkg * install *": [ "exec" ], + "/bin/opkg * remove *": [ "exec" ], + "/bin/opkg * update": [ "exec" ], + "/etc/opkg.conf": [ "write" ], + "/etc/opkg/*.conf": [ "write" ], + "/tmp/upload.ipk": [ "write" ] + } + } + } +} |