ucentral: development update

* update luci theme

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2021-07-28 08:07:15 +02:00
parent 05e156e07f
commit 71ae5a9a08
14 changed files with 419 additions and 1041 deletions

View File

@@ -1,15 +0,0 @@
#
# Copyright (C) 2020 Jo-Philipp Wich <jo@mein.io>
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI Simple UI
LUCI_DEPENDS:=+luci-base
PKG_LICENSE:=Apache-2.0
include ../luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@@ -1,477 +0,0 @@
'use strict';
'require view';
'require form';
'require rpc';
'require uci';
'require ui';
'require fs';
var callUciCommit = rpc.declare({
object: 'uci',
method: 'commit',
params: [ 'ucentral', 'network' ]
});
var callLuciSetPassword = rpc.declare({
object: 'luci',
method: 'setPassword',
params: [ 'username', 'password' ],
reject: true
});
var callSystemValidateFirmwareImage = rpc.declare({
object: 'system',
method: 'validate_firmware_image',
params: [ 'path' ],
reject: true
});
var callSystemBoard = rpc.declare({
object: 'system',
method: 'board'
});
function parseAddressAndNetmask(ipaddr, netmask) {
var m = (ipaddr || '').match(/^(.+)\/(\d+)$/);
if (m) {
var a = validation.parseIPv4(m[1]),
s = network.prefixToMask(m[2]);
if (a && s)
return [ m[1], s ];
}
else {
m = (ipaddr || '').match(/^(.+)\/(.+)$/);
if (m) {
var a = validation.parseIPv4(m[1]),
s = network.maskToPrefix(m[2]);
if (a && s)
return [ m[1], network.prefixToMask(s) ];
}
else {
return [ ipaddr, netmask ];
}
}
return null;
}
var cbiRichListValue = form.ListValue.extend({
renderWidget: function(section_id, option_index, cfgvalue) {
var choices = this.transformChoices();
var widget = new ui.Dropdown((cfgvalue != null) ? cfgvalue : this.default, choices, {
id: this.cbid(section_id),
sort: this.keylist,
optional: this.optional,
select_placeholder: this.select_placeholder || this.placeholder,
custom_placeholder: this.custom_placeholder || this.placeholder,
validate: L.bind(this.validate, this, section_id),
disabled: (this.readonly != null) ? this.readonly : this.map.readonly
});
return widget.render();
}
});
var cbiPasswordStrengthIndicator = form.DummyValue.extend({
setStrength: function(section_id, password) {
var node = this.map.findElement('id', this.cbid(section_id)),
segments = node ? node.firstElementChild.childNodes : [],
colors = [ '#d44', '#d84', '#ee4', '#4e4' ],
labels = [ _('too short', 'Password strength'), _('weak', 'Password strength'), _('medium', 'Password strength'), _('strong', 'Password strength') ],
strongRegex = new RegExp('^(?=.{8,})(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*\\W).*$', 'g'),
mediumRegex = new RegExp('^(?=.{7,})(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[0-9]))).*$', 'g'),
enoughRegex = new RegExp('(?=.{6,}).*', 'g'),
strength;
if (strongRegex.test(password))
strength = 3;
else if (mediumRegex.test(password))
strength = 2;
else if (enoughRegex.test(password))
strength = 1;
else
strength = 0;
for (var i = 0; i < segments.length; i++)
segments[i].style.background = (i <= strength) ? colors[strength] : '';
if (node)
node.lastElementChild.firstChild.data = labels[strength];
},
renderWidget: function(section_id, option_index, cfgvalue) {
return E('div', { 'id': this.cbid(section_id), 'style': 'display:flex' }, [
E('div', { 'style': 'align-self:center; display:flex; border:1px solid #aaa; height:.4rem; width:200px; margin:.2rem' }, [
E('div', { 'style': 'flex:1 1 25%; border-right:1px solid #aaa' }),
E('div', { 'style': 'flex:1 1 25%; border-right:1px solid #aaa' }),
E('div', { 'style': 'flex:1 1 25%; border-right:1px solid #aaa' }),
E('div', { 'style': 'flex:1 1 25%' })
]),
E('span', { 'style': 'margin-left:.5rem' }, [ '' ])
]);
}
});
function showProgress(text, ongoing) {
var dlg = ui.showModal(null, [
E('p', ongoing ? { 'class': 'spinning' } : {}, [ text ])
]);
dlg.removeChild(dlg.firstElementChild);
if (!ongoing) {
window.setTimeout(function() {
ui.hideIndicator('uci-changes');
ui.hideModal();
}, 750);
}
}
var formSystemBoard;
return view.extend({
load: function() {
return Promise.all([
uci.load('network'),
callSystemBoard().then(function(reply) {
formSystemBoard = reply;
})
]);
},
handleChangePassword: function() {
var formdata = { password: {} };
var m, s, o;
m = new form.JSONMap(formdata);
s = m.section(form.NamedSection, 'password', 'password');
o = s.option(form.Value, 'pw1', _('Enter new password'));
o.password = true;
o.validate = function(section_id, value) {
this.section.children.filter(function(oo) { return oo.option == 'strength' })[0].setStrength(section_id, value);
return true;
};
o = s.option(cbiPasswordStrengthIndicator, 'strength', ' ');
o = s.option(form.Value, 'pw2', _('Confirm new password'));
o.password = true;
o.validate = function(section_id, value) {
var other = this.section.children.filter(function(oo) { return oo.option == 'pw1' })[0].formvalue(section_id);
if (other != value)
return _('The given passwords do not match!');
return true;
};
return m.render().then(L.bind(function(nodes) {
ui.showModal(_('Change Login Password'), [
nodes,
E('div', { 'class': 'right' }, [
E('button', {
'click': ui.hideModal
}, [ _('Cancel') ]),
E('button', {
'class': 'important',
'click': ui.createHandlerFn(this, function(m) {
return m.save(null, true).then(function() {
showProgress(_('Setting login password…'), true);
return callLuciSetPassword('root', formdata.password.pw1).then(function() {
showProgress(_('Password has been changed.'), false);
}).catch(function(err) {
ui.hideModal();
ui.addNotification(null, _('Unable to change the login password: %s').format(err))
});
}).catch(function() {
var inval = nodes.querySelector('input.cbi-input-invalid');
if (inval)
inval.focus();
});
}, m)
}, [ 'Change password' ])
])
]);
}, this));
},
handleFirmwareFlash: function(ev) {
return ui.uploadFile('/tmp/firmware.bin').then(function(res) {
showProgress(_('Validating image…'), true);
return callSystemValidateFirmwareImage('/tmp/firmware.bin');
}).then(function(res) {
if (!res.valid) {
showProgress(_('The uploaded firmware image is invalid!'), false);
return L.resolveDefault(fs.remove('/tmp/firmware.bin'));
}
var m, s, o;
var formdata = { settings: { keep: res.allow_backup ? '1' : null } };
m = new form.JSONMap(formdata);
s = m.section(form.NamedSection, 'settings', 'settings');
if (res.allow_backup) {
o = s.option(form.Flag, 'keep', _('Keep current system settings over reflash'));
}
else {
o = s.option(form.DummyValue, 'keep');
o.default = '<em>%h</em>'.format(_('System settings will be reset to factory defaults.'));
o.rawhtml = true;
}
return m.render().then(function(nodes) {
ui.showModal('Confirm Firmware Flash', [
E('p', [ _('The uploaded file contains a valid firmware image. Press "Continue" below to start the flash process.') ]),
nodes,
E('div', { 'class': 'right' }, [
E('button', {
'click': ui.createHandlerFn({}, function() {
return L.resolveDefault(fs.remove('/tmp/firmware.bin')).then(function() {
showProgress(_('Upgrade process aborted.'), false);
});
})
}, [ _('Cancel') ]),
E('button', {
'class': 'cbi-button-negative',
'click': ui.createHandlerFn({}, function() {
return m.save(null, true).then(function() {
var keep = (formdata.settings.keep == '1'),
args = (keep ? [] : [ '-n' ]).concat([ '/tmp/firmware.bin' ]);
fs.exec('/sbin/sysupgrade', args); /* does not return */
showProgress(E([], [
_('The firmware image is flashing now.'),
E('br'),
E('em', [ _('Do NOT power off the device until the process is complete!') ])
]), true);
window.setTimeout(function() {
/* FIXME: clarify default IP / domainname */
ui.awaitReconnect.apply(ui, keep ? [ window.location.host ] : [ '192.168.1.1', 'openwrt.lan', 'openap.lan' ]);
}, 3000);
});
})
}, [ _('Continue') ])
])
]);
});
}).catch(function(err) {
showProgress(_('Firmware upload failed.'), false);
ui.addNotification(null, _('Unable to upload firmware image: %s').format(err));
});
},
handleSettingsReset: function(ev) {
ui.showModal(_('Confirm Reset'), [
E('p', [ _('Do you really want to reset all system settings?') ]),
E('p', [
E('em', [ _('Any changes made, including wireless passwords, DHCP reservations, block rules etc. will be erased!') ])
]),
E('div', { 'class': 'right' }, [
E('button', { 'click': ui.hideModal }, [ _('Cancel') ]),
E('button', {
'class': 'cbi-button-negative',
'click': function() {
showProgress(_('Resetting system configuration…'), true);
fs.exec('/sbin/firstboot', [ '-r', '-y' ]).then(function() {
ui.awaitReconnect();
}).catch(function(err) {
showProgress(_('Reset command failed.'), false);
ui.addNotification(null, _('Unable to execute reset command: %s').format(err));
});
}
}, [ _('Reset') ])
])
]);
},
handleReboot: function(ev) {
ui.showModal(_('Confirm Reboot'), [
E('p', [ _('Do you really want to reboot the device?') ]),
E('div', { 'class': 'right' }, [
E('button', { 'click': ui.hideModal }, [ _('Cancel') ]),
E('button', {
'class': 'important',
'click': function() {
showProgress(_('Rebooting device…'), true);
fs.exec('/sbin/reboot').then(function() {
ui.awaitReconnect();
}).catch(function(err) {
showProgress(_('Reboot command failed.'), false);
ui.addNotification(null, _('Unable to execute reboot command: %s').format(err));
});
}
}, [ _('Reboot') ])
])
]);
},
handleCertificateUpload: function(formdata, ev) {
var m = L.dom.findClassInstance(document.querySelector('.cbi-map'));
return m.save().then(L.bind(function() {
return this.handleApply().then(function() {
return ui.uploadFile('/tmp/certs.tar').then(function(res) {
showProgress(_('Uploading certificate…'), false);
fs.exec('/sbin/certupdate').then(function(res) {
if (res.code)
ui.addNotification(null, _('Certificate validation failed.'));
else
showProgress(_('Certificate uploaded successfully.'), false);
}).catch(function(err) {
ui.addNotification(null, _('Unable to upload certificates.'));
});
return 0;
});
});
}, this));
},
handleSettingsSave: function(formdata, ev) {
var m = L.dom.findClassInstance(document.querySelector('.cbi-map'));
return m.save().then(L.bind(function() {
var wan = formdata.data.data.wan
uci.set('network', 'wan', 'proto', wan.proto);
uci.set('network', 'wan', 'ipaddr', wan.addr);
uci.set('network', 'wan', 'netmask', wan.mask);
uci.set('network', 'wan', 'gateway', wan.gateway);
uci.set('network', 'wan', 'dns', wan.dns);
return this.handleApply();
}, this));
},
handleApply: function() {
var dlg = ui.showModal(null, [ E('em', { 'class': 'spinning' }, [ _('Saving configuration…') ]) ]);
dlg.removeChild(dlg.firstElementChild);
return uci.save().then(function() {
return Promise.all([
callUciCommit('network'),
]);
}).catch(function(err) {
ui.addNotification(null, [ E('p', [ _('Failed to save configuration: %s').format(err) ]) ])
}).finally(function() {
ui.hideIndicator('uci-changes');
ui.hideModal();
});
},
render: function(cert_key) {
var m, s, o;
var addr_wan = parseAddressAndNetmask(
uci.get('network', 'wan', 'ipaddr'),
uci.get('network', 'wan', 'netmask'),
uci.get('network', 'wan', 'gateway'),
uci.get('network', 'wan', 'dns'));
var formdata = {
information: {
serial: formSystemBoard.hostname,
model: formSystemBoard.model,
version: formSystemBoard.release["tip-version"],
revision: formSystemBoard.release["tip-revision"]
},
wan: {
proto: uci.get('network', 'wan', 'proto'),
addr: addr_wan ? addr_wan[0] : null,
mask: addr_wan ? addr_wan[1] : null,
gateway: addr_wan ? addr_wan[2] : null,
dns: addr_wan ? addr_wan[3] : null
},
maintenance: {},
certificates: {redirector: null}
};
m = new form.JSONMap(formdata, _('Setup'));
m.tabbed = true;
s = m.section(form.NamedSection, "information", 'information', _('Information'));
o = s.option(form.Value, 'serial', _('Serial'));
o.readonly = true;
o = s.option(form.Value, 'model', _('Model'));
o.readonly = true;
o = s.option(form.Value, 'version', _('Release'));
o.readonly = true;
o = s.option(form.Value, 'revision', _('Revision'));
o.readonly = true;
s = m.section(form.NamedSection, 'wan', 'wan', _('Connectivity'));
o = s.option(cbiRichListValue, 'proto', "Protocol");
o.value('dhcp', E('div', { 'style': 'white-space:normal' }, [
E('strong', [ _('Automatic address configuration (DHCP)') ]), E('br'),
E('span', { 'class': 'hide-open' })
]));
o.value('static', E('div', { 'style': 'white-space:normal' }, [
E('strong', [ _('Static address configuration') ]), E('br'),
E('span', { 'class': 'hide-open' })
]));
o = s.option(form.Value, 'addr', _('IP Address'));
o.rmempty = false;
o.datatype = 'ip4addr("nomask")';
o.depends('proto', 'static');
o = s.option(form.Value, 'mask', _('Netmask'));
o.rmempty = false;
o.datatype = 'ip4addr("nomask")';
o.depends('proto', 'static');
o = s.option(form.Value, 'gateway', _('Gateway'));
o.rmempty = false;
o.datatype = 'ip4addr("nomask")';
o.depends('proto', 'static');
o = s.option(form.Value, 'dns', _('Nameserver'));
o.rmempty = false;
o.datatype = 'ip4addr("nomask")';
o.depends('proto', 'static');
o = s.option(form.Button, 'save', _(''));
o.inputtitle = _('Save Settings');
o.onclick = ui.createHandlerFn(this, 'handleSettingsSave', m);
s = m.section(form.NamedSection, 'maintenance', 'maintenance', _('System Maintenance'));
o = s.option(form.Button, 'upgrade', _('Flash device firmware'));
o.inputtitle = _('Upload firmware image…');
o.onclick = ui.createHandlerFn(this, 'handleFirmwareFlash');
o = s.option(form.Button, 'reset', _('Reset system settings'));
o.inputtitle = _('Restore system defaults…');
o.onclick = ui.createHandlerFn(this, 'handleSettingsReset');
o = s.option(form.Button, 'reboot', _('Restart device'));
o.inputtitle = _('Reboot…');
o.onclick = ui.createHandlerFn(this, 'handleReboot');
s = m.section(form.NamedSection, 'certificates', 'certificates', _('Upgrade Certificates'));
o = s.option(form.Button, 'upgrade', _('Certificate upload'));
o.inputtitle = _('Upload certificate…');
o.onclick = ui.createHandlerFn(this, 'handleCertificateUpload', m);
return m.render();
},
handleSave: null,
handleSaveApply: null,
handleReset: null
});

View File

@@ -1,10 +0,0 @@
#!/bin/sh
rm /etc/ucentral/redirector.json
cd /etc/ucentral/
tar xf /tmp/certs.tar
/etc/init.d/firstcontact enable
/etc/init.d/firstcontact restart
return 0

View File

@@ -1,10 +0,0 @@
{
"admin/simple-setup": {
"title": "Setup",
"order": 10,
"action": {
"type": "view",
"path": "setup"
}
}
}

View File

@@ -1,28 +0,0 @@
{
"luci-mod-simple": {
"description": "LuCI simple ui access",
"read": {
"cgi-io": [ "exec" ],
"uci": [ "network", "ucentral" ]
},
"write": {
"cgi-io": [ "upload" ],
"file": {
"/sbin/firstboot -r -y": [ "exec" ],
"/sbin/reboot": [ "exec" ],
"/sbin/sysupgrade": [ "exec" ],
"/sbin/certupdate": [ "exec" ],
"/tmp/certs.tar": [ "write" ],
"/tmp/firmware.bin": [ "write" ]
},
"ubus": {
"file": [ "exec", "remove" ],
"luci": [ "setPassword" ],
"system": [ "validate_firmware_image" ],
"uci": [ "commit" ],
"system": [ "board" ]
},
"uci": [ "network", "ucentral" ]
}
}
}

View File

@@ -1,12 +1,12 @@
#
# Copyright (C) 2020 Jo-Philipp Wich <jo@mein.io>
# Copyright (C) 2021 Jo-Philipp Wich <jo@mein.io>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI uCentral theme
LUCI_TITLE:=LuCI theme for uCentral
LUCI_DEPENDS:=
include ../luci.mk

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,87 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!-- Generator: Adobe Illustrator 24.2.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 450 70" style="enable-background:new 0 0 450 70;" xml:space="preserve">
viewBox="0 0 251.2 114.2" style="enable-background:new 0 0 251.2 114.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st0{fill:#FED206;}
.st1{fill:#EB6F53;}
.st2{fill:#3BA9B6;}
.st3{fill:#414141;}
</style>
<g>
<path class="st0" d="M219.6,43.3C219.5,43.3,219.5,43.3,219.6,43.3c-1.3,0-2.2-1-2.2-2.2c0-0.2,0-0.4,0-0.6
c0-11.9-9.7-21.6-21.6-21.6c-0.2,0-0.4,0-0.6,0c-1.2,0-2.2-0.9-2.2-2.1c0-1.2,0.9-2.2,2.1-2.2c0.2,0,0.5,0,0.7,0
c14.3,0,25.9,11.6,25.9,25.9c0,0.2,0,0.5,0,0.7C221.7,42.4,220.7,43.3,219.6,43.3z"/>
<path class="st1" d="M212.1,43.3C212,43.3,212,43.3,212.1,43.3c-1.3-0.1-2.2-1.1-2.2-2.3c0-0.2,0-0.4,0-0.6
c0-7.7-6.3-14.1-14.1-14.1c-0.2,0-0.4,0-0.6,0c-1.2,0.1-2.2-0.9-2.3-2.1c0-1.2,0.9-2.2,2.1-2.3c0.3,0,0.5,0,0.8,0
c10.2,0,18.4,8.3,18.4,18.4c0,0.2,0,0.5,0,0.8C214.2,42.4,213.2,43.3,212.1,43.3z"/>
<path class="st2" d="M204.3,43.3c-0.1,0-0.1,0-0.2,0c-1.2-0.1-2.1-1.1-2-2.3c0-0.2,0-0.4,0-0.5c0-3.5-2.8-6.3-6.3-6.3
c-0.1,0-0.3,0-0.5,0c-1.2,0.1-2.3-0.8-2.3-2c-0.1-1.2,0.8-2.3,2-2.3c0.3,0,0.6,0,0.9,0c5.9,0,10.7,4.8,10.7,10.7c0,0.3,0,0.5,0,0.9
C206.4,42.4,205.4,43.3,204.3,43.3z"/>
<g>
<g>
<path class="st0" d="M104,44.1V28.7h-5.5v-2.8h14.2v2.8h-5.5v15.3H104z"/>
<g>
<path class="st3" d="M61.9,89.9v-4.7h-1.7v-0.9h4.4v0.9h-1.7v4.7H61.9z"/>
</g>
<g>
<path class="st3" d="M65.6,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8V89h2.9v0.9H65.6z"/>
</g>
<g>
<path class="st3" d="M70.7,89.9v-5.6h1V89h2.5v0.9H70.7z"/>
</g>
<g>
<path class="st3" d="M74.9,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8V89h2.9v0.9H74.9z"/>
</g>
<g>
<path class="st3" d="M79.8,87.1c0-1.7,1.3-2.9,2.9-2.9c1.1,0,1.8,0.6,2.2,1.3l-0.8,0.4c-0.3-0.5-0.8-0.8-1.4-0.8
c-1.1,0-1.9,0.8-1.9,2c0,1.2,0.8,2,1.9,2c0.6,0,1.1-0.4,1.4-0.8l0.8,0.4c-0.4,0.7-1.1,1.3-2.2,1.3C81.1,90,79.8,88.8,79.8,87.1z
"/>
</g>
<g>
<path class="st3" d="M85.5,87.1c0-1.7,1.2-2.9,2.9-2.9c1.7,0,2.9,1.2,2.9,2.9S90,90,88.3,90C86.7,90,85.5,88.8,85.5,87.1z
M90.2,87.1c0-1.2-0.7-2-1.9-2c-1.1,0-1.9,0.9-1.9,2c0,1.1,0.7,2,1.9,2C89.5,89.1,90.2,88.3,90.2,87.1z"/>
</g>
<g>
<path class="st3" d="M96.9,89.9v-4.3l-1.7,4.3h-0.4l-1.7-4.3v4.3h-1v-5.6h1.4l1.5,3.8l1.5-3.8h1.4v5.6H96.9z"/>
</g>
<g>
<path class="st3" d="M103,89.9v-5.6h1v5.6H103z"/>
</g>
<g>
<path class="st3" d="M109.7,89.9l-2.9-4v4h-1v-5.6h1l2.9,3.9v-3.9h1v5.6H109.7z"/>
</g>
<g>
<path class="st3" d="M112.4,89.9v-5.6h3.8v0.9h-2.9v1.4h2.8v0.9h-2.8v2.4H112.4z"/>
</g>
<g>
<path class="st3" d="M120.3,89.9l-1.2-2.1h-1v2.1h-1v-5.6h2.5c1.1,0,1.8,0.7,1.8,1.8c0,1-0.7,1.5-1.3,1.6l1.4,2.2H120.3z
M120.4,86.1c0-0.5-0.4-0.9-1-0.9h-1.4V87h1.4C120,87,120.4,86.6,120.4,86.1z"/>
</g>
<g>
<path class="st3" d="M126.6,89.9l-0.4-1.1h-2.6l-0.4,1.1h-1.1l2.2-5.6h1.2l2.2,5.6H126.6z M124.9,85.3l-1,2.7h2L124.9,85.3z"/>
</g>
<g>
<path class="st3" d="M131.4,89.9v-5.6h2.1c1.1,0,1.7,0.8,1.7,1.6c0,0.9-0.6,1.6-1.7,1.6h-1.6v2.3H131.4z M134.7,86
c0-0.7-0.5-1.2-1.2-1.2h-1.6v2.4h1.6C134.2,87.2,134.7,86.6,134.7,86z"/>
</g>
<g>
<path class="st3" d="M139.4,89.9l-1.6-2.3h-1.2v2.3h-0.5v-5.6h2.1c1,0,1.7,0.6,1.7,1.6c0,1-0.7,1.6-1.6,1.6l1.6,2.3H139.4z
M139.4,86c0-0.7-0.5-1.2-1.2-1.2h-1.6v2.4h1.6C138.9,87.2,139.4,86.7,139.4,86z"/>
</g>
<g>
<path class="st3" d="M141.2,87.1c0-1.6,1.1-2.9,2.7-2.9c1.6,0,2.7,1.3,2.7,2.9c0,1.6-1.1,2.9-2.7,2.9
C142.3,90,141.2,88.8,141.2,87.1z M146.1,87.1c0-1.4-0.9-2.5-2.2-2.5c-1.4,0-2.2,1-2.2,2.5c0,1.4,0.9,2.5,2.2,2.5
C145.2,89.6,146.1,88.5,146.1,87.1z"/>
</g>
<g>
<path class="st3" d="M147,89.3l0.3-0.4c0.3,0.3,0.6,0.6,1.1,0.6c0.8,0,1.2-0.5,1.2-1.3v-4h0.5v4c0,1.2-0.8,1.7-1.7,1.7
C147.9,90,147.4,89.8,147,89.3z"/>
</g>
<g>
<path class="st3" d="M151.8,89.9v-5.6h3.5v0.4h-3.1v2.1h3v0.4h-3v2.2h3.1v0.4H151.8z"/>
</g>
<g>
<path class="st3" d="M156.3,87.1c0-1.7,1.3-2.9,2.8-2.9c0.9,0,1.6,0.4,2,1l-0.4,0.3c-0.4-0.5-1-0.8-1.6-0.8
c-1.3,0-2.3,1-2.3,2.5c0,1.4,1,2.5,2.3,2.5c0.7,0,1.3-0.3,1.6-0.8l0.4,0.3c-0.5,0.6-1.2,1-2,1C157.5,90,156.3,88.8,156.3,87.1z"
/>
</g>
<g>
<path class="st3" d="M163.5,89.9v-5.2h-1.8v-0.4h4.1v0.4H164v5.2H163.5z"/>
</g>
</g>
<g>
<path class="st0" d="M116.2,44.1V25.9h12.4v2.8h-9.2v4.7h9.1v2.8h-9.1v5.1h9.2v2.8H116.2z"/>
<polygon class="st3" points="33.7,86.5 41.2,79 48.6,86.5 49.8,86.5 41.2,77.9 32.6,86.5 "/>
<polygon class="st3" points="48.6,87.8 41.2,95.2 33.7,87.8 32.6,87.8 41.2,96.4 49.8,87.8 "/>
<polygon class="st3" points="40.3,86.5 47.8,79 55.3,86.5 56.4,86.5 47.8,77.9 39.2,86.5 "/>
<polygon class="st3" points="55.3,87.8 47.8,95.2 40.3,87.8 39.2,87.8 47.8,96.4 56.4,87.8 "/>
</g>
<g>
<path class="st0" d="M132.5,44.1V25.9h3.2v15.3h8v2.8H132.5z"/>
</g>
<g>
<path class="st0" d="M146.4,44.1V25.9h12.4v2.8h-9.2v4.7h9.1v2.8h-9.1v5.1h9.2v2.8H146.4z"/>
</g>
<g>
<path class="st0" d="M162.1,35c0-5.6,4.2-9.4,9.5-9.4c3.6,0,5.9,1.9,7.3,4.1l-2.7,1.4c-0.9-1.5-2.6-2.6-4.6-2.6
c-3.6,0-6.3,2.7-6.3,6.6c0,3.8,2.7,6.6,6.3,6.6c1.9,0,3.7-1.1,4.6-2.6l2.7,1.4c-1.4,2.2-3.6,4.1-7.3,4.1
C166.3,44.4,162.1,40.6,162.1,35z"/>
</g>
<g>
<path class="st0" d="M180.6,35c0-5.4,3.8-9.4,9.3-9.4c5.4,0,9.3,4,9.3,9.4s-3.9,9.4-9.3,9.4C184.4,44.4,180.6,40.4,180.6,35z
M195.8,35c0-3.8-2.4-6.6-6-6.6c-3.7,0-6,2.8-6,6.6c0,3.7,2.3,6.6,6,6.6C193.5,41.6,195.8,38.7,195.8,35z"/>
</g>
<g>
<path class="st0" d="M217.7,44.1v-14l-5.5,14h-1.4l-5.6-14v14H202V25.9h4.5l5,12.5l4.9-12.5h4.5v18.1H217.7z"/>
</g>
<g>
<path class="st0" d="M237.5,44.1V25.9h3.2v18.1H237.5z"/>
</g>
<g>
<path class="st0" d="M259,44.1l-9.5-13v13h-3.2V25.9h3.3l9.3,12.6V25.9h3.2v18.1H259z"/>
</g>
<g>
<path class="st0" d="M267.8,44.1V25.9h12.4v2.8H271v4.7h9.1v2.8H271v7.9H267.8z"/>
</g>
<g>
<path class="st0" d="M293.4,44.1l-4-6.8h-3.2v6.8H283V25.9h8c3.6,0,6,2.3,6,5.7c0,3.2-2.1,5-4.3,5.3l4.5,7.1H293.4z M293.7,31.6
c0-1.7-1.3-2.9-3.1-2.9h-4.4v5.8h4.4C292.4,34.5,293.7,33.3,293.7,31.6z"/>
</g>
<g>
<path class="st0" d="M313.7,44.1l-1.3-3.5h-8.3l-1.3,3.5h-3.6l7.1-18.1h4l7.1,18.1H313.7z M308.2,29.1l-3.3,8.7h6.5L308.2,29.1z"
/>
</g>
<g>
<path class="st0" d="M329.4,44.1V25.9h6.8c3.5,0,5.5,2.4,5.5,5.3c0,2.9-2,5.3-5.5,5.3h-5.2v7.5H329.4z M340,31.2
c0-2.3-1.6-3.9-4-3.9h-5.1v7.8h5.1C338.5,35.1,340,33.5,340,31.2z"/>
</g>
<g>
<path class="st0" d="M355.4,44.1l-5.1-7.5h-4v7.5h-1.6V25.9h6.8c3.1,0,5.5,2,5.5,5.3c0,3.3-2.3,5.1-5,5.2l5.2,7.6H355.4z
M355.4,31.2c0-2.3-1.6-3.9-4-3.9h-5.1v7.8h5.1C353.8,35.1,355.4,33.5,355.4,31.2z"/>
</g>
<g>
<path class="st0" d="M361.1,35c0-5.3,3.5-9.4,8.8-9.4c5.3,0,8.8,4.1,8.8,9.4c0,5.3-3.5,9.4-8.8,9.4
C364.6,44.4,361.1,40.3,361.1,35z M377.1,35c0-4.6-2.8-8-7.2-8c-4.4,0-7.2,3.4-7.2,8c0,4.6,2.8,8,7.2,8
C374.3,43,377.1,39.6,377.1,35z"/>
</g>
<g>
<path class="st0" d="M380,42.2l1-1.2c0.8,1.1,1.9,2,3.6,2c2.5,0,4-1.7,4-4.2V25.9h1.6v12.9c0,3.8-2.5,5.6-5.5,5.6
C382.7,44.4,381.2,43.7,380,42.2z"/>
</g>
<g>
<path class="st0" d="M395.6,44.1V25.9h11.5v1.4h-10V34h9.8v1.4h-9.8v7.2h10v1.4H395.6z"/>
</g>
<g>
<path class="st0" d="M410.1,35c0-5.6,4.1-9.4,9.2-9.4c2.9,0,5.1,1.3,6.6,3.2l-1.3,0.8c-1.1-1.6-3.1-2.6-5.3-2.6
c-4.2,0-7.6,3.2-7.6,8c0,4.7,3.3,8,7.6,8c2.2,0,4.2-1.1,5.3-2.6l1.3,0.8c-1.6,2-3.8,3.2-6.6,3.2C414.1,44.4,410.1,40.6,410.1,35z
"/>
</g>
<g>
<path class="st0" d="M433.5,44.1V27.3h-5.9v-1.4H441v1.4h-5.9v16.7H433.5z"/>
</g>
</g>
<g>
<polygon class="st0" points="12.7,32.9 36.9,8.7 61.1,32.9 64.7,32.9 36.9,5 9,32.9 "/>
<polygon class="st0" points="61.1,37.1 36.9,61.3 12.7,37.1 9,37.1 36.9,65 64.7,37.1 "/>
<polygon class="st0" points="34.2,32.9 58.4,8.7 82.6,32.9 86.3,32.9 58.4,5 30.5,32.9 "/>
<polygon class="st0" points="82.6,37.1 58.4,61.3 34.2,37.1 30.5,37.1 58.4,65 86.3,37.1 "/>
</g>
</g>
<g>
<path class="st3" d="M51.2,41.3c2,1.1,3.6,2.6,4.7,4.5c1.1,1.9,1.7,4,1.7,6.4c0,2.3-0.6,4.5-1.7,6.4c-1.1,1.9-2.7,3.4-4.7,4.6
c-2,1.1-4.2,1.7-6.6,1.7c-2.4,0-4.6-0.6-6.6-1.7c-2-1.1-3.6-2.6-4.7-4.6c-1.1-1.9-1.7-4.1-1.7-6.4c0-2.3,0.6-4.5,1.7-6.4
c1.1-1.9,2.7-3.4,4.7-4.5c2-1.1,4.2-1.6,6.6-1.6C47,39.6,49.2,40.2,51.2,41.3z M40.5,44.9c-1.3,0.7-2.3,1.7-3,3
c-0.7,1.3-1.1,2.7-1.1,4.2s0.4,3,1.1,4.2c0.8,1.3,1.8,2.3,3,3c1.3,0.7,2.7,1.1,4.1,1.1c1.5,0,2.8-0.4,4.1-1.1c1.3-0.7,2.3-1.8,3-3
c0.7-1.3,1.1-2.7,1.1-4.2s-0.4-2.9-1.1-4.2c-0.7-1.3-1.7-2.3-3-3c-1.3-0.7-2.6-1.1-4.1-1.1C43.2,43.8,41.8,44.2,40.5,44.9z"/>
<path class="st3" d="M76.9,46.8c1.3,0.8,2.4,1.9,3.1,3.4c0.7,1.4,1.1,3.1,1.1,5c0,1.9-0.4,3.5-1.1,4.9c-0.7,1.4-1.8,2.5-3.1,3.3
c-1.3,0.8-2.9,1.2-4.6,1.2c-1.4,0-2.6-0.3-3.7-0.8c-1.1-0.5-2-1.3-2.7-2.4v9.8h-4.6V45.7H66v3.1c0.7-1.1,1.5-1.8,2.6-2.4
c1.1-0.5,2.3-0.8,3.7-0.8C74,45.6,75.6,46,76.9,46.8z M75.1,59.1c1-1.1,1.5-2.4,1.5-4.1c0-1.7-0.5-3-1.5-4.1
c-1-1.1-2.2-1.6-3.8-1.6c-1.6,0-2.8,0.5-3.8,1.6c-1,1-1.5,2.4-1.5,4.1c0,1.7,0.5,3,1.5,4.1c1,1.1,2.3,1.6,3.8,1.6
C72.8,60.7,74.1,60.2,75.1,59.1z"/>
<path class="st3" d="M99.3,48.1c1.5,1.7,2.3,4.1,2.3,7.2c0,0.6,0,1.1,0,1.4H87.7c0.3,1.3,0.9,2.4,1.9,3.1c0.9,0.8,2.1,1.1,3.5,1.1
c1,0,1.9-0.2,2.7-0.5c0.9-0.4,1.6-0.9,2.3-1.6l2.5,2.6c-0.9,1-2.1,1.8-3.4,2.4c-1.3,0.6-2.8,0.8-4.5,0.8c-1.9,0-3.6-0.4-5.1-1.2
c-1.5-0.8-2.6-1.9-3.4-3.3c-0.8-1.4-1.2-3.1-1.2-5c0-1.9,0.4-3.5,1.2-5c0.8-1.4,1.9-2.6,3.4-3.4c1.4-0.8,3.1-1.2,4.9-1.2
C95.5,45.6,97.8,46.4,99.3,48.1z M97.4,53.6c0-1.4-0.5-2.5-1.4-3.3c-0.9-0.8-2-1.2-3.4-1.2c-1.3,0-2.4,0.4-3.3,1.2
c-0.9,0.8-1.5,1.9-1.7,3.3H97.4z"/>
<path class="st3" d="M121.5,47.5c1.2,1.3,1.9,3.1,1.9,5.3v11.7h-4.6V54.1c0-1.3-0.4-2.3-1.1-3.1c-0.7-0.8-1.8-1.1-3-1.1
c-1.5,0-2.7,0.5-3.6,1.5s-1.3,2.3-1.3,3.8v9.2h-4.5V45.7h4.5v3.5c1.3-2.4,3.5-3.6,6.7-3.7C118.5,45.5,120.2,46.2,121.5,47.5z"/>
<path class="st3" d="M156.5,39.9h4.9l-8.3,24.5h-4.9l-5.6-18.6l-5.7,18.6h-4.8l-8.3-24.5h5l5.8,19.4l5.7-19.4h4.6l5.8,19.5
L156.5,39.9z"/>
<path class="st3" d="M168,38.4c0.5,0.5,0.7,1.2,0.7,2c0,0.8-0.2,1.4-0.7,1.9c-0.5,0.5-1.1,0.8-1.9,0.8c-0.7,0-1.4-0.3-1.9-0.8
c-0.5-0.5-0.7-1.2-0.7-1.9c0-0.8,0.2-1.4,0.7-2c0.5-0.5,1.1-0.8,1.9-0.8C166.9,37.7,167.6,37.9,168,38.4z M164,45.7h4.5v18.7H164
V45.7z"/>
<path class="st3" d="M174,39.9h16.9l0,4.1h-12.2v6.6h11.1v4.1h-11.1v9.7H174V39.9z"/>
<path class="st3" d="M197.9,38.4c0.5,0.5,0.7,1.2,0.7,2c0,0.8-0.2,1.4-0.7,1.9c-0.5,0.5-1.1,0.8-1.9,0.8c-0.7,0-1.4-0.3-1.9-0.8
c-0.5-0.5-0.7-1.2-0.7-1.9c0-0.8,0.2-1.4,0.7-2c0.5-0.5,1.1-0.8,1.9-0.8C196.8,37.7,197.4,37.9,197.9,38.4z M193.8,45.7h4.5v18.7
h-4.5V45.7z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,16 +1,13 @@
<%#
Copyright 2020 Jo-Philipp Wich <jo@mein.io>
Copyright 2021 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-%>
<div class="clear"></div>
</div>
</div>
</div>
<p class="luci">
<% local ver = require "luci.version" -%>
Powered by <%= ver.luciname %> (<%= ver.luciversion %>)
</p>
<script type="text/javascript">L.require('menu-ucentral')</script>
</body>
</html>

View File

@@ -1,5 +1,5 @@
<%#
Copyright 2020 Jo-Philipp Wich <jo@mein.io>
Copyright 2021 Jo-Philipp Wich <jo@mein.io>
Licensed to the public under the Apache License 2.0.
-%>
@@ -17,182 +17,21 @@
http.prepare_content("text/html; charset=UTF-8")
-%>
<!DOCTYPE html>
<html lang="<%=luci.i18n.context.lang%>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" media="screen" href="<%=media%>/cascade.css" />
<link rel="icon" href="<%=media%>/favicon.png" type="image/png" />
<link rel="icon" href="<%=media%>/logo.svg" type="image/svg+xml" />
<script type="text/javascript" src="<%=url('admin/translations', luci.i18n.context.lang)%><%# ?v=PKG_VERSION %>"></script>
<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
<script type="text/javascript">//<![CDATA[
(function() {
function get_children(node) {
var children = [];
for (var k in node.children) {
if (!node.children.hasOwnProperty(k))
continue;
if (!node.children[k].satisfied)
continue;
if (!node.children[k].hasOwnProperty('title'))
continue;
children.push(Object.assign(node.children[k], { name: k }));
}
return children.sort(function(a, b) {
return ((a.order || 1000) - (b.order || 1000));
});
}
function handle_mainmenu_expand(ev) {
var a = ev.target, ul1 = a.parentNode.parentNode, ul2 = a.nextElementSibling;
document.querySelectorAll('ul.mainmenu.l1 > li.active').forEach(function(li) {
if (li !== a.parentNode)
li.classList.remove('active');
});
if (!ul2)
return;
if (ul2.parentNode.offsetLeft + ul2.offsetWidth <= ul1.offsetLeft + ul1.offsetWidth)
ul2.classList.add('align-left');
ul1.classList.add('active');
a.parentNode.classList.add('active');
a.blur();
ev.preventDefault();
ev.stopPropagation();
}
function render_mainmenu(tree, url, level) {
var l = (level || 0) + 1,
ul = E('ul', { 'class': 'mainmenu l%d'.format(l) }),
children = get_children(tree);
if (children.length == 0 || l > 2)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.dispatchpath[l] == children[i].name),
activeClass = 'mainmenu-item-%s%s'.format(children[i].name, isActive ? ' selected' : '');
ul.appendChild(E('li', { 'class': activeClass }, [
E('a', {
'href': L.url(url, children[i].name),
'click': (l == 1) ? handle_mainmenu_expand : null,
}, [ _(children[i].title) ]),
render_mainmenu(children[i], url + '/' + children[i].name, l)
]));
}
if (l == 1) {
var container = document.querySelector('#mainmenu');
container.firstElementChild.appendChild(ul);
container.style.display = '';
}
return ul;
}
function render_modemenu(tree) {
var menu = document.querySelector('#modemenu'),
children = get_children(tree);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.requestpath.length ? children[i].name == L.env.requestpath[0] : i == 0);
if (i > 0)
menu.appendChild(E([], ['\u00a0|\u00a0']));
menu.appendChild(E('div', { 'class': isActive ? 'active' : null }, [
E('a', { 'href': L.url(children[i].name) }, [ _(children[i].title) ])
]));
if (isActive)
render_mainmenu(children[i], children[i].name);
}
if (menu.children.length > 1)
menu.style.display = '';
}
function render_tabmenu(tree, url, level) {
var container = document.querySelector('#tabmenu'),
l = (level || 0) + 1,
ul = E('ul', { 'class': 'cbi-tabmenu' }),
children = get_children(tree),
activeNode = null;
if (children.length == 0)
return E([]);
for (var i = 0; i < children.length; i++) {
var isActive = (L.env.dispatchpath[l + 2] == children[i].name),
activeClass = isActive ? ' cbi-tab' : '',
className = 'tabmenu-item-%s %s'.format(children[i].name, activeClass);
ul.appendChild(E('li', { 'class': className }, [
E('a', { 'href': L.url(url, children[i].name) }, [ _(children[i].title) ] )
]));
if (isActive)
activeNode = children[i];
}
container.appendChild(ul);
container.style.display = '';
if (activeNode)
container.appendChild(render_tabmenu(activeNode, url + '/' + activeNode.name, l));
return ul;
}
function toggle_sidebar(ev) {
var btn = ev.currentTarget,
bar = document.querySelector('#mainmenu');
if (btn.classList.contains('active')) {
btn.classList.remove('active');
bar.classList.remove('active');
}
else {
btn.classList.add('active');
bar.classList.add('active');
}
}
document.addEventListener('luci-loaded', function(ev) {
var tree = <%= luci.http.write_json(luci.dispatcher.menu_json() or {}) %>,
node = tree,
url = '';
render_modemenu(tree);
if (L.env.dispatchpath.length >= 3) {
for (var i = 0; i < 3 && node; i++) {
node = node.children[L.env.dispatchpath[i]];
url = url + (url ? '/' : '') + L.env.dispatchpath[i];
}
if (node)
render_tabmenu(node, url);
}
document.querySelector('#menubar > .navigation').addEventListener('click', toggle_sidebar);
});
})();
//]]></script>
<title><%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
<% if css then %><style title="text/css">
<%= css %>
</style>
<% end -%>
</head>
<body class="lang_<%=luci.i18n.context.lang%>" data-page="<%= pcdata(path) %>">
@@ -201,35 +40,28 @@
<span id="skiplink2"><a href="#content"><%:Skip to content%></a></span>
</p>
<div id="menubar">
<h2 class="navigation"><a id="navigation" name="navigation"><%:Navigation%></a></h2>
<span class="hostname"><%=(boardinfo.hostname or "?")%></span>
<span class="distversion"><%=ver.distversion%></span>
<span id="indicators">
<span id="xhr_poll_status" style="display:none" onclick="XHR.running() ? XHR.halt() : XHR.run()">
<span id="xhr_poll_status_on" style="display:none"><%:Refreshing%></span>
<span id="xhr_poll_status_off" style="display:none"><%:Paused%></span>
</span>
</span>
</div>
<div id="modemenu" style="display:none"></div>
<div id="maincontainer">
<div id="mainmenu" style="display:none">
<div></div>
<div id="page">
<div id="menubar">
<h2 class="navigation" style="visibility:hidden"><a id="navigation" name="navigation"><%:Navigation%></a></h2>
<img src="<%=media%>/logo.svg" />
<span id="indicators"></span>
</div>
<div id="maincontent">
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") and category ~= "failsafe" and path ~= "admin-system-admin-password" then -%>
<div class="alert-message warning">
<h4><%:No password set!%></h4>
<p><%:There is no password set on this router. Please configure a root password to protect the web interface and enable SSH.%></p>
<% if disp.lookup("admin/system/admin") then %>
<div class="right"><a class="btn" href="<%=url("admin/system/admin")%>"><%:Go to password configuration...%></a></div>
<% end %>
</div>
<%- end -%>
<div id="modemenu" style="display:none"></div>
<div id="tabmenu" style="display:none"></div>
<div id="maincontainer">
<div id="mainmenu"></div>
<div id="maincontent">
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") and path ~= "admin-system-admin-password" then -%>
<div class="alert-message warning">
<h4><%:No password set!%></h4>
<p><%:There is no password set on this router. Please configure a root password to protect the web interface.%></p>
<% if disp.lookup("admin/system/admin") then %>
<div class="right"><a class="btn" href="<%=url("admin/system/admin")%>"><%:Go to password configuration...%></a></div>
<% end %>
</div>
<%- end -%>
<div id="tabmenu" style="display:none"></div>

View File

@@ -1,8 +1,9 @@
#!/bin/sh
if [ "$PKG_UPGRADE" != 1 ]; then
uci get luci.themes.uCentral >/dev/null 2>&1 || \
uci batch <<-EOF
set luci.themes.ucentral=/luci-static/ucentral
set luci.themes.uCentral=/luci-static/ucentral
set luci.main.mediaurlbase=/luci-static/ucentral
commit luci
EOF