'require ui';
'require rpc';
-var css = ' \
+const css = ' \
.controls { \
display: flex; \
margin: .5em 0 1em 0; \
} \
';
-var isReadonlyView = !L.hasViewPermission() || null;
+const isReadonlyView = !L.hasViewPermission() || null;
const callMountPoints = rpc.declare({
object: 'luci',
expect: { result: [] }
});
-var packages = {
+const packages = {
available: { providers: {}, pkgs: {} },
installed: { providers: {}, pkgs: {} }
};
-var languages = ['en'];
+const languages = ['en'];
-var currentDisplayMode = 'available', currentDisplayRows = [];
+let currentDisplayMode = 'available'
+const currentDisplayRows = [];
function parseList(s, dest)
{
- var re = /([^\n]*)\n/g,
- pkg = null, key = null, val = null, m;
+ const re = /([^\n]*)\n/g;
+ let pkg = null, key = null, val = null, m;
+ let list, stat, mode, installed;
while ((m = re.exec(s)) !== null) {
if (m[1].match(/^\s(.*)$/)) {
case 'depends':
case 'provides':
- var list = val.split(/\s*,\s*/);
+ list = val.split(/\s*,\s*/);
if (list.length !== 1 || list[0].length > 0)
pkg[key] = list;
break;
break;
case 'status':
- var stat = val.split(/\s+/),
- mode = stat[1],
- installed = stat[2];
+ stat = val.split(/\s+/);
+ mode = stat[1];
+ installed = stat[2];
switch (mode) {
case 'user':
else if (pkg) {
dest.pkgs[pkg.name] = pkg;
- var provides = dest.providers[pkg.name] ? [] : [ pkg.name ];
+ const provides = dest.providers[pkg.name] ? [] : [ pkg.name ];
if (pkg.provides)
provides.push.apply(provides, pkg.provides);
function display(pattern)
{
- var src = packages[currentDisplayMode === 'updates' ? 'installed' : currentDisplayMode],
- table = document.querySelector('#packages'),
- pagers = document.querySelectorAll('.controls > .pager'),
- i18n_filter = null;
+ const src = packages[currentDisplayMode === 'updates' ? 'installed' : currentDisplayMode];
+ const pagers = document.querySelectorAll('.controls > .pager');
+ let i18n_filter = null;
currentDisplayRows.length = 0;
break;
}
- for (var name in src.pkgs) {
- var pkg = src.pkgs[name],
- desc = pkg.description || '',
- altsize = null;
+ for (let name in src.pkgs) {
+ const pkg = src.pkgs[name];
+ let desc = pkg.description || '';
+ let altsize = null;
if (!pkg.size && packages.available.pkgs[name])
altsize = packages.available.pkgs[name].size;
if (name.indexOf('luci-i18n-') === 0 && (!(i18n_filter instanceof RegExp) || !name.match(i18n_filter)))
continue;
- var btn, ver;
+ let btn, ver;
if (currentDisplayMode === 'updates') {
- var avail = packages.available.pkgs[name],
- inst = packages.installed.pkgs[name];
+ const avail = packages.available.pkgs[name];
+ const inst = packages.installed.pkgs[name];
if (!inst || !inst.installed)
continue;
}, _('Remove…'));
}
else {
- var inst = packages.installed.pkgs[name];
+ const inst = packages.installed.pkgs[name];
ver = truncateVersion(pkg.version || '-');
return 0;
});
- for (var i = 0; i < pagers.length; i++) {
- pagers[i].parentNode.style.display = '';
- pagers[i].setAttribute('data-offset', 100);
+ for (let pager of pagers) {
+ pager.parentNode.style.display = '';
+ pager.setAttribute('data-offset', 100);
}
handlePage({ target: pagers[0].querySelector('.prev') });
function handlePage(ev)
{
- var filter = document.querySelector('input[name="filter"]'),
- offset = +ev.target.parentNode.getAttribute('data-offset'),
- next = ev.target.classList.contains('next'),
- pagers = document.querySelectorAll('.controls > .pager');
+ const filter = document.querySelector('input[name="filter"]');
+ let offset = +ev.target.parentNode.getAttribute('data-offset');
+ const next = ev.target.classList.contains('next');
+ const pagers = document.querySelectorAll('.controls > .pager');
if ((next && (offset + 100) >= currentDisplayRows.length) ||
(!next && (offset < 100)))
offset += next ? 100 : -100;
- for (var i = 0; i < pagers.length; i++) {
- pagers[i].setAttribute('data-offset', offset);
- pagers[i].querySelector('.text').firstChild.data = currentDisplayRows.length
+ for (let pager of pagers) {
+ pager.setAttribute('data-offset', offset);
+ pager.querySelector('.text').firstChild.data = currentDisplayRows.length
? _('Displaying %d-%d of %d').format(1 + offset, Math.min(offset + 100, currentDisplayRows.length), currentDisplayRows.length)
: _('No packages');
if (offset < 100)
- pagers[i].querySelector('.prev').setAttribute('disabled', 'disabled');
+ pager.querySelector('.prev').setAttribute('disabled', 'disabled');
else
- pagers[i].querySelector('.prev').removeAttribute('disabled');
+ pager.querySelector('.prev').removeAttribute('disabled');
if ((offset + 100) >= currentDisplayRows.length)
- pagers[i].querySelector('.next').setAttribute('disabled', 'disabled');
+ pager.querySelector('.next').setAttribute('disabled', 'disabled');
else
- pagers[i].querySelector('.next').removeAttribute('disabled');
+ pager.querySelector('.next').removeAttribute('disabled');
}
- var placeholder = _('No information available');
+ let placeholder = _('No information available');
if (filter.value)
placeholder = [
function handleMode(ev)
{
- var tab = findParent(ev.target, 'li');
+ const tab = findParent(ev.target, 'li');
if (tab.getAttribute('data-mode') === currentDisplayMode)
return;
function compareVersion(val, ref)
{
- var vi = 0, ri = 0,
- isdigit = { 0:1, 1:1, 2:1, 3:1, 4:1, 5:1, 6:1, 7:1, 8:1, 9:1 };
+ let vi = 0, ri = 0;
+ const isdigit = { 0:1, 1:1, 2:1, 3:1, 4:1, 5:1, 6:1, 7:1, 8:1, 9:1 };
val = val || '';
ref = ref || '';
return 0;
while (vi < val.length || ri < ref.length) {
- var first_diff = 0;
+ let first_diff = 0;
while ((vi < val.length && !isdigit[val.charAt(vi)]) ||
(ri < ref.length && !isdigit[ref.charAt(ri)])) {
- var vc = orderOf(val.charAt(vi)), rc = orderOf(ref.charAt(ri));
+ const vc = orderOf(val.charAt(vi)), rc = orderOf(ref.charAt(ri));
if (vc !== rc)
return vc - rc;
function versionSatisfied(ver, ref, vop)
{
- var r = compareVersion(ver, ref);
+ const r = compareVersion(ver, ref);
switch (vop) {
case '<':
if (pkg.installed) {
if (vop && !versionSatisfied(pkg.version, ver, vop)) {
- var repl = null;
+ let repl = null;
(packages.available.providers[pkg.name] || []).forEach(function(p) {
if (!repl && versionSatisfied(p.version, ver, vop))
function renderDependencyItem(dep, info, flat)
{
- var li = E('li'),
- vop = dep.version ? dep.version[0] : null,
- ver = dep.version ? dep.version[1] : null,
- depends = [];
+ const li = E('li');
+ const vop = dep.version ? dep.version[0] : null;
+ const ver = dep.version ? dep.version[1] : null;
+ const depends = [];
- for (var i = 0; dep.pkgs && i < dep.pkgs.length; i++) {
- var pkg = packages.installed.pkgs[dep.pkgs[i]] ||
+ for (let i = 0; dep.pkgs && i < dep.pkgs.length; i++) {
+ const pkg = packages.installed.pkgs[dep.pkgs[i]] ||
packages.available.pkgs[dep.pkgs[i]] ||
{ name: dep.name };
if (i > 0)
li.appendChild(document.createTextNode(' | '));
- var text = pkg.name;
+ let text = pkg.name;
if (pkg.installsize)
text += ' (%1024mB)'.format(pkg.installsize);
pkgStatus({ name: dep.name, missing: true }, vop, ver, info) ]));
if (!flat) {
- var subdeps = renderDependencies(depends, info);
+ const subdeps = renderDependencies(depends, info);
if (subdeps)
li.appendChild(subdeps);
}
function renderDependencies(depends, info, flat)
{
- var deps = depends || [],
- items = [];
+ const deps = depends || [];
+ const items = [];
info.seen = info.seen || [];
- for (var i = 0; i < deps.length; i++) {
- var dep, vop, ver;
+ for (let i = 0; i < deps.length; i++) {
+ let dep, vop, ver;
if (deps[i] === 'libc')
continue;
if (info.seen[dep])
continue;
- var pkgs = [];
+ const pkgs = [];
(packages.installed.providers[dep] || []).forEach(function(p) {
if (pkgs.indexOf(p.name) === -1) pkgs.push(p.name);
function handleReset(ev)
{
- var filter = document.querySelector('input[name="filter"]');
+ const filter = document.querySelector('input[name="filter"]');
filter.value = '';
display();
function handleInstall(ev)
{
- var name = ev.target.getAttribute('data-package'),
- installcmd = ev.target.getAttribute('data-action'),
- pkg = packages.available.pkgs[name],
- depcache = {},
- size;
+ const name = ev.target.getAttribute('data-package');
+ const installcmd = ev.target.getAttribute('data-action');
+ const pkg = packages.available.pkgs[name];
+ const depcache = {};
+ let size;
if (pkg.installsize)
size = _('~%1024mB installed').format(pkg.installsize);
else
size = _('unknown');
- var deps = renderDependencies(pkg.depends, depcache),
- tree = null, errs = null, inst = null, desc = null;
+ const deps = renderDependencies(pkg.depends, depcache);
+ let tree = null, errs = null, inst = null, desc = null;
if (depcache.errors && depcache.errors.length) {
errs = E('ul', { 'class': 'errors' });
});
}
- var totalsize = pkg.installsize || pkg.size || 0,
- totalpkgs = 1,
- suggestsize = 0;
+ let totalsize = pkg.installsize || pkg.size || 0;
+ let totalpkgs = 1;
+ let suggestsize = 0;
if (depcache.install && depcache.install.length)
depcache.install.forEach(function(ipkg) {
totalpkgs++;
});
- var luci_basename = pkg.name.match(/^luci-([^-]+)-(.+)$/),
- i18n_packages = [],
- i18n_tree;
+ const luci_basename = pkg.name.match(/^luci-([^-]+)-(.+)$/);
+ const i18n_packages = [];
+ let i18n_tree;
if (luci_basename && (luci_basename[1] != 'i18n' || luci_basename[2].indexOf('base-') === 0)) {
- var i18n_filter;
+ let i18n_filter;
if (luci_basename[1] == 'i18n') {
- var basenames = [];
+ const basenames = [];
- for (var pkgname in packages.installed.pkgs) {
- var m = pkgname.match(/^luci-([^-]+)-(.+)$/);
+ for (let pkgname in packages.installed.pkgs) {
+ const m = pkgname.match(/^luci-([^-]+)-(.+)$/);
if (m && m[1] != 'i18n')
basenames.push(m[2]);
}
if (i18n_filter) {
- for (var pkgname in packages.available.pkgs)
+ for (let pkgname in packages.available.pkgs)
if (pkgname != pkg.name && pkgname.match(i18n_filter))
i18n_packages.push(pkgname);
- var i18ncache = {};
+ const i18ncache = {};
i18n_tree = renderDependencies(i18n_packages, i18ncache, true);
function handleManualInstall(ev)
{
- var name_or_url = document.querySelector('input[name="install"]').value,
- install = E('div', {
+ const name_or_url = document.querySelector('input[name="install"]').value;
+ let install = E('div', {
'class': 'btn cbi-button-action',
'data-command': 'install',
'data-package': name_or_url,
document.querySelector('input[name="install"]').value = '';
handlePkg(ev);
}
- }, _('Install')), warning;
+ }, _('Install'));
+ let warning;
if (!name_or_url.length) {
return;
function handleConfig(ev)
{
- var conf = {};
- var base_dir = L.hasSystemFeature('apk') ? '/etc/apk' : '/etc/opkg';
+ const conf = {};
+ const base_dir = L.hasSystemFeature('apk') ? '/etc/apk' : '/etc/opkg';
ui.showModal(_('%s Configuration').format(L.hasSystemFeature('apk') ? 'APK' : 'OPKG'), [
E('p', { 'class': 'spinning' }, _('Loading configuration data…'))
]);
fs.list(base_dir).then(function(partials) {
- var files = [];
+ const files = [];
if (L.hasSystemFeature('apk')) {
files.push(base_dir + '/' + 'repositories.d/customfeeds.list',
files.push(base_dir + '.conf')
}
- for (var i = 0; i < partials.length; i++) {
+ for (let i = 0; i < partials.length; i++) {
if (partials[i].type == 'file') {
if (L.hasSystemFeature('apk')) {
if (partials[i].name == 'repositories')
});
}));
}).then(function() {
- var opkg_text = _('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>.')
- var apk_text = _('Below is a listing of the various configuration files used by <em>apk</em>. The configuration in the other files may be changed but is usually not preserved by <em>sysupgrade</em>.')
- var body = [
+ const opkg_text = _('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>.')
+ const apk_text = _('Below is a listing of the various configuration files used by <em>apk</em>. The configuration in the other files may be changed but is usually not preserved by <em>sysupgrade</em>.')
+ const body = [
E('p', {}, L.hasSystemFeature('apk') ? apk_text : opkg_text)
];
E('div', {
'class': 'btn cbi-button-positive',
'click': function(ev) {
- var data = {};
+ const data = {};
findParent(ev.target, '.modal').querySelectorAll('textarea[name]')
.forEach(function(textarea) {
data[textarea.getAttribute('name')] = textarea.value
function handleRemove(ev)
{
- var name = ev.target.getAttribute('data-package'),
- pkg = packages.installed.pkgs[name],
- avail = packages.available.pkgs[name] || {},
- size, desc;
+ const name = ev.target.getAttribute('data-package');
+ const pkg = packages.installed.pkgs[name];
+ const avail = packages.available.pkgs[name] || {};
+ let size, desc;
if (avail.installsize)
size = _('~%1024mB installed').format(avail.installsize);
function handlePkg(ev)
{
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"]'),
- i18n = document.querySelector('input[name="i18ninstall"]');
+ const cmd = ev.target.getAttribute('data-command');
+ const pkg = ev.target.getAttribute('data-package');
+ const rem = document.querySelector('input[name="autoremove"]');
+ const owr = document.querySelector('input[name="overwrite"]');
+ const i18n = document.querySelector('input[name="i18ninstall"]');
- var dlg = ui.showModal(_('Executing package manager'), [
+ const dlg = ui.showModal(_('Executing package manager'), [
E('p', { 'class': 'spinning' },
_('Waiting for the <em>%s %h</em> command to complete…').format(L.hasSystemFeature('apk') ? 'apk' : 'opkg', cmd))
]);
- var argv = [ cmd ];
+ const argv = [ cmd ];
if (cmd == 'remove')
argv.push('--force-removal-of-dependent-packages')
function handleUpload(ev)
{
- var path = '/tmp/upload.%s'.format(L.hasSystemFeature('apk') ? 'apk' : 'ipk');
+ const path = '/tmp/upload.%s'.format(L.hasSystemFeature('apk') ? 'apk' : 'ipk');
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)),
packages.installed = { providers: {}, pkgs: {} };
return (data ? Promise.resolve(data) : downloadLists()).then(function(data) {
- var pg = document.querySelector('.cbi-progressbar'),
- mount = L.toArray(data[0].filter(function(m) { return m.mount == '/' || m.mount == '/overlay' }))
+ const pg = document.querySelector('.cbi-progressbar');
+ const 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(mount.size ? (100 / mount.size) * (mount.size - mount.free) : 100) + '%';
parseList(data[1], packages.available);
parseList(data[2], packages.installed);
- for (var pkgname in packages.installed.pkgs)
+ for (let pkgname in packages.installed.pkgs)
if (pkgname.indexOf('luci-i18n-base-') === 0)
languages.push(pkgname.substring(15));
});
}
-var inputTimeout = null;
+let inputTimeout = null;
function handleInput(ev) {
if (inputTimeout !== null)
}
return view.extend({
- load: function() {
+ load() {
return downloadLists();
},
- render: function(listData) {
- var query = decodeURIComponent(L.toArray(location.search.match(/\bquery=([^=]+)\b/))[1] || '');
+ render(listData) {
+ const query = decodeURIComponent(L.toArray(location.search.match(/\bquery=([^=]+)\b/))[1] || '');
- var view = E([], [
+ const view = E([], [
E('style', { 'type': 'text/css' }, [ css ]),
E('h2', {}, _('Software')),