mirror of
https://github.com/Telecominfraproject/wlan-ap.git
synced 2025-12-16 17:01:37 +00:00
* add board information page to webui * fix github workflow, it actually reports fails now Signed-off-by: John Crispin <john@phrozen.org>
484 lines
15 KiB
JavaScript
484 lines
15 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require form';
|
|
'require rpc';
|
|
'require uci';
|
|
'require ui';
|
|
'require fs';
|
|
|
|
var callUciCommit = rpc.declare({
|
|
object: 'uci',
|
|
method: 'commit',
|
|
params: [ 'config' ]
|
|
});
|
|
|
|
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'),
|
|
uci.load('ucentral'),
|
|
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() {
|
|
uci.set('config', 'config', 'server', formdata.data.data.certificates.redirector);
|
|
uci.set('config', 'mqtt', 'server', formdata.data.data.certificates.redirector);
|
|
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'),
|
|
callUciCommit('ucentral')
|
|
]);
|
|
}).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.Value, 'redirector', _('Redirector'));
|
|
|
|
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
|
|
});
|