diff --git a/files/www/luci-static/resources/view/blue-merle.js b/files/www/luci-static/resources/view/blue-merle.js
index 516887a..4c64bf7 100644
--- a/files/www/luci-static/resources/view/blue-merle.js
+++ b/files/www/luci-static/resources/view/blue-merle.js
@@ -104,516 +104,54 @@ var currentDisplayMode = 'available', currentDisplayRows = [];
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');
-
- if ((next && (offset + 100) >= currentDisplayRows.length) ||
- (!next && (offset < 100)))
- return;
-
- 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
- ? _('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');
- else
- pagers[i].querySelector('.prev').removeAttribute('disabled');
-
- if ((offset + 100) >= currentDisplayRows.length)
- pagers[i].querySelector('.next').setAttribute('disabled', 'disabled');
- else
- pagers[i].querySelector('.next').removeAttribute('disabled');
- }
-
- var placeholder = _('No information available');
-
- if (filter.value)
- placeholder = [
- E('span', {}, _('No packages matching "%h".').format(filter.value)), ' (',
- E('a', { href: '#', click: handleReset }, _('Reset')), ')'
- ];
-
- cbi_update_table('#packages', currentDisplayRows.slice(offset, offset + 100),
- placeholder);
}
function handleMode(ev)
{
- var tab = findParent(ev.target, 'li');
- if (tab.getAttribute('data-mode') === currentDisplayMode)
- return;
-
- tab.parentNode.querySelectorAll('li').forEach(function(li) {
- li.classList.remove('cbi-tab');
- li.classList.add('cbi-tab-disabled');
- });
-
- tab.classList.remove('cbi-tab-disabled');
- tab.classList.add('cbi-tab');
-
- currentDisplayMode = tab.getAttribute('data-mode');
-
- display(document.querySelector('input[name="filter"]').value);
-
- ev.target.blur();
- ev.preventDefault();
}
function handleI18nFilter(ev)
{
- display(document.querySelector('input[name="filter"]').value);
}
function orderOf(c)
{
- if (c === '~')
- return -1;
- else if (c === '' || c >= '0' && c <= '9')
- return 0;
- else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
- return c.charCodeAt(0);
- else
- return c.charCodeAt(0) + 256;
}
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 };
-
- val = val || '';
- ref = ref || '';
-
- if (val === ref)
- return 0;
-
- while (vi < val.length || ri < ref.length) {
- var 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));
- if (vc !== rc)
- return vc - rc;
-
- vi++; ri++;
- }
-
- while (val.charAt(vi) === '0')
- vi++;
-
- while (ref.charAt(ri) === '0')
- ri++;
-
- while (isdigit[val.charAt(vi)] && isdigit[ref.charAt(ri)]) {
- first_diff = first_diff || (val.charCodeAt(vi) - ref.charCodeAt(ri));
- vi++; ri++;
- }
-
- if (isdigit[val.charAt(vi)])
- return 1;
- else if (isdigit[ref.charAt(ri)])
- return -1;
- else if (first_diff)
- return first_diff;
- }
-
- return 0;
}
function versionSatisfied(ver, ref, vop)
{
- var r = compareVersion(ver, ref);
-
- switch (vop) {
- case '<':
- case '<=':
- return r <= 0;
-
- case '>':
- case '>=':
- return r >= 0;
-
- case '<<':
- return r < 0;
-
- case '>>':
- return r > 0;
-
- case '=':
- return r == 0;
- }
-
- return false;
}
function pkgStatus(pkg, vop, ver, info)
{
- info.errors = info.errors || [];
- info.install = info.install || [];
-
- if (pkg.installed) {
- if (vop && !versionSatisfied(pkg.version, ver, vop)) {
- var repl = null;
-
- (packages.available.providers[pkg.name] || []).forEach(function(p) {
- if (!repl && versionSatisfied(p.version, ver, vop))
- repl = p;
- });
-
- if (repl) {
- info.install.push(repl);
- return E('span', {
- 'class': 'label',
- 'data-tooltip': _('Requires update to %h %h')
- .format(repl.name, repl.version)
- }, _('Needs upgrade'));
- }
-
- info.errors.push(_('The installed version of package %h is not compatible, require %s while %s is installed.').format(pkg.name, truncateVersion(ver, vop), truncateVersion(pkg.version)));
-
- return E('span', {
- 'class': 'label warning',
- 'data-tooltip': _('Require version %h %h,\ninstalled %h')
- .format(vop, ver, pkg.version)
- }, _('Version incompatible'));
- }
-
- return E('span', { 'class': 'label notice' }, _('Installed'));
- }
- else if (!pkg.missing) {
- if (!vop || versionSatisfied(pkg.version, ver, vop)) {
- info.install.push(pkg);
- return E('span', { 'class': 'label' }, _('Not installed'));
- }
-
- info.errors.push(_('The repository version of package %h is not compatible, require %s but only %s is available.')
- .format(pkg.name, truncateVersion(ver, vop), truncateVersion(pkg.version)));
-
- return E('span', {
- 'class': 'label warning',
- 'data-tooltip': _('Require version %h %h,\ninstalled %h')
- .format(vop, ver, pkg.version)
- }, _('Version incompatible'));
- }
- else {
- info.errors.push(_('Required dependency package %h is not available in any repository.').format(pkg.name));
-
- return E('span', { 'class': 'label warning' }, _('Not available'));
- }
}
function renderDependencyItem(dep, info, flat)
{
- var li = E('li'),
- vop = dep.version ? dep.version[0] : null,
- ver = dep.version ? dep.version[1] : null,
- depends = [];
-
- for (var i = 0; dep.pkgs && i < dep.pkgs.length; i++) {
- var 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;
-
- if (pkg.installsize)
- text += ' (%1024mB)'.format(pkg.installsize);
- else if (pkg.size)
- text += ' (~%1024mB)'.format(pkg.size);
-
- li.appendChild(E('span', { 'data-tooltip': pkg.description },
- [ text, ' ', pkgStatus(pkg, vop, ver, info) ]));
-
- (pkg.depends || []).forEach(function(d) {
- if (depends.indexOf(d) === -1)
- depends.push(d);
- });
- }
-
- if (!li.firstChild)
- li.appendChild(E('span', {},
- [ dep.name, ' ',
- pkgStatus({ name: dep.name, missing: true }, vop, ver, info) ]));
-
- if (!flat) {
- var subdeps = renderDependencies(depends, info);
- if (subdeps)
- li.appendChild(subdeps);
- }
-
- return li;
}
function renderDependencies(depends, info, flat)
{
- var deps = depends || [],
- items = [];
-
- info.seen = info.seen || [];
-
- for (var i = 0; i < deps.length; i++) {
- var dep, vop, ver;
-
- if (deps[i] === 'libc')
- continue;
-
- if (deps[i].match(/^(.+?)\s+\((<=|>=|<<|>>|<|>|=)(.+?)\)/)) {
- dep = RegExp.$1.trim();
- vop = RegExp.$2.trim();
- ver = RegExp.$3.trim();
- }
- else {
- dep = deps[i].trim();
- vop = ver = null;
- }
-
- if (info.seen[dep])
- continue;
-
- var pkgs = [];
-
- (packages.installed.providers[dep] || []).forEach(function(p) {
- if (pkgs.indexOf(p.name) === -1) pkgs.push(p.name);
- });
-
- (packages.available.providers[dep] || []).forEach(function(p) {
- if (pkgs.indexOf(p.name) === -1) pkgs.push(p.name);
- });
-
- info.seen[dep] = {
- name: dep,
- pkgs: pkgs,
- version: [vop, ver]
- };
-
- items.push(renderDependencyItem(info.seen[dep], info, flat));
- }
-
- if (items.length)
- return E('ul', { 'class': 'deps' }, items);
-
- return null;
}
function truncateVersion(v, op)
{
- v = v.replace(/\b(([a-f0-9]{8})[a-f0-9]{24,32})\b/,
- '$2…');
-
- if (!op || op === '=')
- return v;
-
- return '%h %h'.format(op, v);
}
function handleReset(ev)
{
- var filter = document.querySelector('input[name="filter"]');
-
- filter.value = '';
- display();
}
function handleInstall(ev)
{
- var name = ev.target.getAttribute('data-package'),
- pkg = packages.available.pkgs[name],
- depcache = {},
- size;
-
- if (pkg.installsize)
- size = _('~%1024mB installed').format(pkg.installsize);
- else if (pkg.size)
- size = _('~%1024mB compressed').format(pkg.size);
- else
- size = _('unknown');
-
- var deps = renderDependencies(pkg.depends, depcache),
- tree = null, errs = null, inst = null, desc = null;
-
- if (depcache.errors && depcache.errors.length) {
- errs = E('ul', { 'class': 'errors' });
- depcache.errors.forEach(function(err) {
- errs.appendChild(E('li', {}, err));
- });
- }
-
- var totalsize = pkg.installsize || pkg.size || 0,
- totalpkgs = 1,
- suggestsize = 0;
-
- if (depcache.install && depcache.install.length)
- depcache.install.forEach(function(ipkg) {
- totalsize += ipkg.installsize || ipkg.size || 0;
- totalpkgs++;
- });
-
- var luci_basename = pkg.name.match(/^luci-([^-]+)-(.+)$/),
- i18n_packages = [],
- i18n_tree;
-
- if (luci_basename && (luci_basename[1] != 'i18n' || luci_basename[2].indexOf('base-') === 0)) {
- var i18n_filter;
-
- if (luci_basename[1] == 'i18n') {
- var basenames = [];
-
- for (var pkgname in packages.installed.pkgs) {
- var m = pkgname.match(/^luci-([^-]+)-(.+)$/);
-
- if (m && m[1] != 'i18n')
- basenames.push(m[2]);
- }
-
- if (basenames.length)
- i18n_filter = new RegExp('^luci-i18n-(' + basenames.join('|') + ')-' + pkg.name.replace(/^luci-i18n-base-/, '') + '$');
- }
- else {
- i18n_filter = new RegExp('^luci-i18n-' + luci_basename[2] + '-(' + languages.join('|') + ')$');
- }
-
- if (i18n_filter) {
- for (var pkgname in packages.available.pkgs)
- if (pkgname != pkg.name && pkgname.match(i18n_filter))
- i18n_packages.push(pkgname);
-
- var i18ncache = {};
-
- i18n_tree = renderDependencies(i18n_packages, i18ncache, true);
-
- if (i18ncache.install && i18ncache.install.length) {
- i18ncache.install.forEach(function(ipkg) {
- suggestsize += ipkg.installsize || ipkg.size || 0;
- });
- }
- }
- }
-
- inst = E('p', [
- _('Require approx. %1024mB size for %d package(s) to install.')
- .format(totalsize, totalpkgs),
- ' ',
- suggestsize ? _('Suggested translations require approx. %1024mB additional space.').format(suggestsize) : ''
- ]);
-
- if (deps) {
- tree = E('li', '%s:'.format(_('Dependencies')));
- tree.appendChild(deps);
- }
-
- if (pkg.description) {
- desc = E('div', {}, [
- E('h5', {}, _('Description')),
- E('p', {}, pkg.description)
- ]);
- }
-
- ui.showModal(_('Details for package %h').format(pkg.name), [
- E('ul', {}, [
- E('li', '%s: %h'.format(_('Version'), pkg.version)),
- E('li', '%s: %h'.format(_('Size'), size)),
- tree || '',
- i18n_packages.length ? E('li', [
- E('strong', [_('Suggested translations'), ':']),
- i18n_tree
- ]) : ''
- ]),
- desc || '',
- errs || inst || '',
- E('div', [
- E('hr'),
- i18n_packages.length ? E('p', [
- E('label', { 'class': 'cbi-checkbox' }, [
- E('input', {
- 'id': 'i18ninstall-cb',
- 'type': 'checkbox',
- 'name': 'i18ninstall',
- 'data-packages': i18n_packages.join(' '),
- 'disabled': isReadonlyView,
- 'checked': true
- }), ' ',
- E('label', { 'for': 'i18ninstall-cb' }), ' ',
- _('Install suggested translation packages as well')
- ])
- ]) : '',
- E('p', [
- E('label', { 'class': 'cbi-checkbox' }, [
- E('input', {
- 'id': 'overwrite-cb',
- 'type': 'checkbox',
- 'name': 'overwrite',
- 'disabled': isReadonlyView
- }), ' ',
- E('label', { 'for': 'overwrite-cb' }), ' ',
- _('Allow overwriting conflicting package files')
- ])
- ])
- ]),
- E('div', { 'class': 'right' }, [
- E('div', {
- 'class': 'btn',
- 'click': ui.hideModal
- }, _('Cancel')),
- ' ',
- E('div', {
- 'data-command': 'install',
- 'data-package': name,
- 'class': 'btn cbi-button-action',
- 'click': handleOpkg,
- 'disabled': isReadonlyView
- }, _('Install'))
- ])
- ]);
}
function handleManualInstall(ev)
{
- var name_or_url = document.querySelector('input[name="install"]').value,
- install = E('div', {
- 'class': 'btn cbi-button-action',
- 'data-command': 'install',
- 'data-package': name_or_url,
- 'click': function(ev) {
- document.querySelector('input[name="install"]').value = '';
- handleOpkg(ev);
- }
- }, _('Install')), warning;
-
- if (!name_or_url.length) {
- return;
- }
- else if (name_or_url.indexOf('/') !== -1) {
- warning = E('p', {}, _('Installing packages from untrusted sources is a potential security risk! Really attempt to install %h?').format(name_or_url));
- }
- else if (!packages.available.providers[name_or_url]) {
- warning = E('p', {}, _('The package %h is not available in any configured repository.').format(name_or_url));
- install = '';
- }
- else {
- warning = E('p', {}, _('Really attempt to install %h?').format(name_or_url));
- }
-
- ui.showModal(_('Manually install package'), [
- warning,
- E('div', { 'class': 'right' }, [
- E('div', {
- 'click': ui.hideModal,
- 'class': 'btn cbi-button-neutral'
- }, _('Cancel')),
- ' ', install
- ])
- ]);
}
@@ -720,9 +258,6 @@ function handleConfig(ev)
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() {
@@ -945,48 +480,10 @@ function handleOpkg(ev)
function handleUpload(ev)
{
- var path = '/tmp/upload.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 %h?').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) {
- ui.hideModal();
- 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() {
- fs.remove(path)
- });
- }
- }, _('Install'))
- ])
- ]);
- }, this, ev.target));
}
-var inputTimeout = null;
-
function handleInput(ev) {
- if (inputTimeout !== null)
- window.clearTimeout(inputTimeout);
-
- inputTimeout = window.setTimeout(function() {
- display(ev.target.value);
- }, 250);
}
return view.extend({