first commit
@ -0,0 +1,46 @@
|
||||
#
|
||||
# Copyright (C) 2008-2015 The LuCI Team <luci@lists.subsignal.org>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-base
|
||||
|
||||
LUCI_TYPE:=mod
|
||||
LUCI_BASENAME:=base
|
||||
|
||||
LUCI_TITLE:=LuCI core libraries
|
||||
LUCI_DEPENDS:=+lua +luci-lib-nixio +luci-lib-ip +rpcd +libubus-lua +luci-lib-jsonc +liblucihttp-lua +luci-lib-base +rpcd-mod-file +rpcd-mod-luci +cgi-io
|
||||
|
||||
PKG_LICENSE:=MIT
|
||||
|
||||
HOST_BUILD_DIR:=$(BUILD_DIR_HOST)/$(PKG_NAME)
|
||||
|
||||
include $(INCLUDE_DIR)/host-build.mk
|
||||
|
||||
define Package/luci-base/conffiles
|
||||
/etc/luci-uploads
|
||||
/etc/config/luci
|
||||
/etc/config/ucitrack
|
||||
endef
|
||||
|
||||
include ../../luci.mk
|
||||
|
||||
define Host/Configure
|
||||
endef
|
||||
|
||||
define Host/Compile
|
||||
$(MAKE) -C src/ clean po2lmo jsmin
|
||||
endef
|
||||
|
||||
define Host/Install
|
||||
$(INSTALL_DIR) $(1)/bin
|
||||
$(INSTALL_BIN) src/po2lmo $(1)/bin/po2lmo
|
||||
$(INSTALL_BIN) src/jsmin $(1)/bin/jsmin
|
||||
endef
|
||||
|
||||
$(eval $(call HostBuild))
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/lua
|
||||
require "luci.cacheloader"
|
||||
require "luci.sgi.cgi"
|
||||
luci.dispatcher.indexcache = "/tmp/luci-indexcache"
|
||||
luci.sgi.cgi.run()
|
@ -0,0 +1,802 @@
|
||||
/*
|
||||
LuCI - Lua Configuration Interface
|
||||
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008-2018 Jo-Philipp Wich <jo@mein.io>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
var cbi_d = [];
|
||||
var cbi_strings = { path: {}, label: {} };
|
||||
|
||||
function s8(bytes, off) {
|
||||
var n = bytes[off];
|
||||
return (n > 0x7F) ? (n - 256) >>> 0 : n;
|
||||
}
|
||||
|
||||
function u16(bytes, off) {
|
||||
return ((bytes[off + 1] << 8) + bytes[off]) >>> 0;
|
||||
}
|
||||
|
||||
function sfh(s) {
|
||||
if (s === null || s.length === 0)
|
||||
return null;
|
||||
|
||||
var bytes = [];
|
||||
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
var ch = s.charCodeAt(i);
|
||||
|
||||
if (ch <= 0x7F)
|
||||
bytes.push(ch);
|
||||
else if (ch <= 0x7FF)
|
||||
bytes.push(((ch >>> 6) & 0x1F) | 0xC0,
|
||||
( ch & 0x3F) | 0x80);
|
||||
else if (ch <= 0xFFFF)
|
||||
bytes.push(((ch >>> 12) & 0x0F) | 0xE0,
|
||||
((ch >>> 6) & 0x3F) | 0x80,
|
||||
( ch & 0x3F) | 0x80);
|
||||
else if (code <= 0x10FFFF)
|
||||
bytes.push(((ch >>> 18) & 0x07) | 0xF0,
|
||||
((ch >>> 12) & 0x3F) | 0x80,
|
||||
((ch >> 6) & 0x3F) | 0x80,
|
||||
( ch & 0x3F) | 0x80);
|
||||
}
|
||||
|
||||
if (!bytes.length)
|
||||
return null;
|
||||
|
||||
var hash = (bytes.length >>> 0),
|
||||
len = (bytes.length >>> 2),
|
||||
off = 0, tmp;
|
||||
|
||||
while (len--) {
|
||||
hash += u16(bytes, off);
|
||||
tmp = ((u16(bytes, off + 2) << 11) ^ hash) >>> 0;
|
||||
hash = ((hash << 16) ^ tmp) >>> 0;
|
||||
hash += hash >>> 11;
|
||||
off += 4;
|
||||
}
|
||||
|
||||
switch ((bytes.length & 3) >>> 0) {
|
||||
case 3:
|
||||
hash += u16(bytes, off);
|
||||
hash = (hash ^ (hash << 16)) >>> 0;
|
||||
hash = (hash ^ (s8(bytes, off + 2) << 18)) >>> 0;
|
||||
hash += hash >>> 11;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
hash += u16(bytes, off);
|
||||
hash = (hash ^ (hash << 11)) >>> 0;
|
||||
hash += hash >>> 17;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
hash += s8(bytes, off);
|
||||
hash = (hash ^ (hash << 10)) >>> 0;
|
||||
hash += hash >>> 1;
|
||||
break;
|
||||
}
|
||||
|
||||
hash = (hash ^ (hash << 3)) >>> 0;
|
||||
hash += hash >>> 5;
|
||||
hash = (hash ^ (hash << 4)) >>> 0;
|
||||
hash += hash >>> 17;
|
||||
hash = (hash ^ (hash << 25)) >>> 0;
|
||||
hash += hash >>> 6;
|
||||
|
||||
return (0x100000000 + hash).toString(16).substr(1);
|
||||
}
|
||||
|
||||
var plural_function = null;
|
||||
|
||||
function trimws(s) {
|
||||
return String(s).trim().replace(/[ \t\n]+/g, ' ');
|
||||
}
|
||||
|
||||
function _(s, c) {
|
||||
var k = (c != null ? trimws(c) + '\u0001' : '') + trimws(s);
|
||||
return (window.TR && TR[sfh(k)]) || s;
|
||||
}
|
||||
|
||||
function N_(n, s, p, c) {
|
||||
if (plural_function == null && window.TR)
|
||||
plural_function = new Function('n', (TR['00000000'] || 'plural=(n != 1);') + 'return +plural');
|
||||
|
||||
var i = plural_function ? plural_function(n) : (n != 1),
|
||||
k = (c != null ? trimws(c) + '\u0001' : '') + trimws(s) + '\u0002' + i.toString();
|
||||
|
||||
return (window.TR && TR[sfh(k)]) || (i ? p : s);
|
||||
}
|
||||
|
||||
|
||||
function cbi_d_add(field, dep, index) {
|
||||
var obj = (typeof(field) === 'string') ? document.getElementById(field) : field;
|
||||
if (obj) {
|
||||
var entry
|
||||
for (var i=0; i<cbi_d.length; i++) {
|
||||
if (cbi_d[i].id == obj.id) {
|
||||
entry = cbi_d[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!entry) {
|
||||
entry = {
|
||||
"node": obj,
|
||||
"id": obj.id,
|
||||
"parent": obj.parentNode.id,
|
||||
"deps": [],
|
||||
"index": index
|
||||
};
|
||||
cbi_d.unshift(entry);
|
||||
}
|
||||
entry.deps.push(dep)
|
||||
}
|
||||
}
|
||||
|
||||
function cbi_d_checkvalue(target, ref) {
|
||||
var value = null,
|
||||
query = 'input[id="'+target+'"], input[name="'+target+'"], ' +
|
||||
'select[id="'+target+'"], select[name="'+target+'"]';
|
||||
|
||||
document.querySelectorAll(query).forEach(function(i) {
|
||||
if (value === null && ((i.type !== 'radio' && i.type !== 'checkbox') || i.checked === true))
|
||||
value = i.value;
|
||||
});
|
||||
|
||||
return (((value !== null) ? value : "") == ref);
|
||||
}
|
||||
|
||||
function cbi_d_check(deps) {
|
||||
var reverse;
|
||||
var def = false;
|
||||
for (var i=0; i<deps.length; i++) {
|
||||
var istat = true;
|
||||
reverse = false;
|
||||
for (var j in deps[i]) {
|
||||
if (j == "!reverse") {
|
||||
reverse = true;
|
||||
} else if (j == "!default") {
|
||||
def = true;
|
||||
istat = false;
|
||||
} else {
|
||||
istat = (istat && cbi_d_checkvalue(j, deps[i][j]))
|
||||
}
|
||||
}
|
||||
|
||||
if (istat ^ reverse) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
function cbi_d_update() {
|
||||
var state = false;
|
||||
for (var i=0; i<cbi_d.length; i++) {
|
||||
var entry = cbi_d[i];
|
||||
var node = document.getElementById(entry.id);
|
||||
var parent = document.getElementById(entry.parent);
|
||||
|
||||
if (node && node.parentNode && !cbi_d_check(entry.deps)) {
|
||||
node.parentNode.removeChild(node);
|
||||
state = true;
|
||||
}
|
||||
else if (parent && (!node || !node.parentNode) && cbi_d_check(entry.deps)) {
|
||||
var next = undefined;
|
||||
|
||||
for (next = parent.firstChild; next; next = next.nextSibling) {
|
||||
if (next.getAttribute && parseInt(next.getAttribute('data-index'), 10) > entry.index)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!next)
|
||||
parent.appendChild(entry.node);
|
||||
else
|
||||
parent.insertBefore(entry.node, next);
|
||||
|
||||
state = true;
|
||||
}
|
||||
|
||||
// hide optionals widget if no choices remaining
|
||||
if (parent && parent.parentNode && parent.getAttribute('data-optionals'))
|
||||
parent.parentNode.style.display = (parent.options.length <= 1) ? 'none' : '';
|
||||
}
|
||||
|
||||
if (entry && entry.parent)
|
||||
cbi_tag_last(parent);
|
||||
|
||||
if (state)
|
||||
cbi_d_update();
|
||||
else if (parent)
|
||||
parent.dispatchEvent(new CustomEvent('dependency-update', { bubbles: true }));
|
||||
}
|
||||
|
||||
function cbi_init() {
|
||||
var nodes;
|
||||
|
||||
document.querySelectorAll('.cbi-dropdown').forEach(function(node) {
|
||||
cbi_dropdown_init(node);
|
||||
node.addEventListener('cbi-dropdown-change', cbi_d_update);
|
||||
});
|
||||
|
||||
nodes = document.querySelectorAll('[data-strings]');
|
||||
|
||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||
var str = JSON.parse(node.getAttribute('data-strings'));
|
||||
for (var key in str) {
|
||||
for (var key2 in str[key]) {
|
||||
var dst = cbi_strings[key] || (cbi_strings[key] = { });
|
||||
dst[key2] = str[key][key2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes = document.querySelectorAll('[data-depends]');
|
||||
|
||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||
var index = parseInt(node.getAttribute('data-index'), 10);
|
||||
var depends = JSON.parse(node.getAttribute('data-depends'));
|
||||
if (!isNaN(index) && depends.length > 0) {
|
||||
for (var alt = 0; alt < depends.length; alt++)
|
||||
cbi_d_add(node, depends[alt], index);
|
||||
}
|
||||
}
|
||||
|
||||
nodes = document.querySelectorAll('[data-update]');
|
||||
|
||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||
var events = node.getAttribute('data-update').split(' ');
|
||||
for (var j = 0, event; (event = events[j]) !== undefined; j++)
|
||||
node.addEventListener(event, cbi_d_update);
|
||||
}
|
||||
|
||||
nodes = document.querySelectorAll('[data-choices]');
|
||||
|
||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||
var choices = JSON.parse(node.getAttribute('data-choices')),
|
||||
options = {};
|
||||
|
||||
for (var j = 0; j < choices[0].length; j++)
|
||||
options[choices[0][j]] = choices[1][j];
|
||||
|
||||
var def = (node.getAttribute('data-optional') === 'true')
|
||||
? node.placeholder || '' : null;
|
||||
|
||||
var cb = new L.ui.Combobox(node.value, options, {
|
||||
name: node.getAttribute('name'),
|
||||
sort: choices[0],
|
||||
select_placeholder: def || _('-- Please choose --'),
|
||||
custom_placeholder: node.getAttribute('data-manual') || _('-- custom --')
|
||||
});
|
||||
|
||||
var n = cb.render();
|
||||
n.addEventListener('cbi-dropdown-change', cbi_d_update);
|
||||
node.parentNode.replaceChild(n, node);
|
||||
}
|
||||
|
||||
nodes = document.querySelectorAll('[data-dynlist]');
|
||||
|
||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||
var choices = JSON.parse(node.getAttribute('data-dynlist')),
|
||||
values = JSON.parse(node.getAttribute('data-values') || '[]'),
|
||||
options = null;
|
||||
|
||||
if (choices[0] && choices[0].length) {
|
||||
options = {};
|
||||
|
||||
for (var j = 0; j < choices[0].length; j++)
|
||||
options[choices[0][j]] = choices[1][j];
|
||||
}
|
||||
|
||||
var dl = new L.ui.DynamicList(values, options, {
|
||||
name: node.getAttribute('data-prefix'),
|
||||
sort: choices[0],
|
||||
datatype: choices[2],
|
||||
optional: choices[3],
|
||||
placeholder: node.getAttribute('data-placeholder')
|
||||
});
|
||||
|
||||
var n = dl.render();
|
||||
n.addEventListener('cbi-dynlist-change', cbi_d_update);
|
||||
node.parentNode.replaceChild(n, node);
|
||||
}
|
||||
|
||||
nodes = document.querySelectorAll('[data-type]');
|
||||
|
||||
for (var i = 0, node; (node = nodes[i]) !== undefined; i++) {
|
||||
cbi_validate_field(node, node.getAttribute('data-optional') === 'true',
|
||||
node.getAttribute('data-type'));
|
||||
}
|
||||
|
||||
document.querySelectorAll('.cbi-tooltip:not(:empty)').forEach(function(s) {
|
||||
s.parentNode.classList.add('cbi-tooltip-container');
|
||||
});
|
||||
|
||||
document.querySelectorAll('.cbi-section-remove > input[name^="cbi.rts"]').forEach(function(i) {
|
||||
var handler = function(ev) {
|
||||
var bits = this.name.split(/\./),
|
||||
section = document.getElementById('cbi-' + bits[2] + '-' + bits[3]);
|
||||
|
||||
section.style.opacity = (ev.type === 'mouseover') ? 0.5 : '';
|
||||
};
|
||||
|
||||
i.addEventListener('mouseover', handler);
|
||||
i.addEventListener('mouseout', handler);
|
||||
});
|
||||
|
||||
var tasks = [];
|
||||
|
||||
document.querySelectorAll('[data-ui-widget]').forEach(function(node) {
|
||||
var args = JSON.parse(node.getAttribute('data-ui-widget') || '[]'),
|
||||
widget = new (Function.prototype.bind.apply(L.ui[args[0]], args)),
|
||||
markup = widget.render();
|
||||
|
||||
tasks.push(Promise.resolve(markup).then(function(markup) {
|
||||
markup.addEventListener('widget-change', cbi_d_update);
|
||||
node.parentNode.replaceChild(markup, node);
|
||||
}));
|
||||
});
|
||||
|
||||
Promise.all(tasks).then(cbi_d_update);
|
||||
}
|
||||
|
||||
function cbi_validate_form(form, errmsg)
|
||||
{
|
||||
/* if triggered by a section removal or addition, don't validate */
|
||||
if (form.cbi_state == 'add-section' || form.cbi_state == 'del-section')
|
||||
return true;
|
||||
|
||||
if (form.cbi_validators) {
|
||||
for (var i = 0; i < form.cbi_validators.length; i++) {
|
||||
var validator = form.cbi_validators[i];
|
||||
|
||||
if (!validator() && errmsg) {
|
||||
alert(errmsg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function cbi_validate_named_section_add(input)
|
||||
{
|
||||
var button = input.parentNode.parentNode.querySelector('.cbi-button-add');
|
||||
if (input.value !== '') {
|
||||
button.disabled = false;
|
||||
}
|
||||
else {
|
||||
button.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function cbi_validate_reset(form)
|
||||
{
|
||||
window.setTimeout(
|
||||
function() { cbi_validate_form(form, null) }, 100
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function cbi_validate_field(cbid, optional, type)
|
||||
{
|
||||
var field = isElem(cbid) ? cbid : document.getElementById(cbid);
|
||||
var validatorFn;
|
||||
|
||||
try {
|
||||
var cbiValidator = L.validation.create(field, type, optional);
|
||||
validatorFn = cbiValidator.validate.bind(cbiValidator);
|
||||
}
|
||||
catch(e) {
|
||||
validatorFn = null;
|
||||
};
|
||||
|
||||
if (validatorFn !== null) {
|
||||
var form = findParent(field, 'form');
|
||||
|
||||
if (!form.cbi_validators)
|
||||
form.cbi_validators = [ ];
|
||||
|
||||
form.cbi_validators.push(validatorFn);
|
||||
|
||||
field.addEventListener("blur", validatorFn);
|
||||
field.addEventListener("keyup", validatorFn);
|
||||
field.addEventListener("cbi-dropdown-change", validatorFn);
|
||||
|
||||
if (matchesElem(field, 'select')) {
|
||||
field.addEventListener("change", validatorFn);
|
||||
field.addEventListener("click", validatorFn);
|
||||
}
|
||||
|
||||
validatorFn();
|
||||
}
|
||||
}
|
||||
|
||||
function cbi_row_swap(elem, up, store)
|
||||
{
|
||||
var tr = findParent(elem.parentNode, '.cbi-section-table-row');
|
||||
|
||||
if (!tr)
|
||||
return false;
|
||||
|
||||
tr.classList.remove('flash');
|
||||
|
||||
if (up) {
|
||||
var prev = tr.previousElementSibling;
|
||||
|
||||
if (prev && prev.classList.contains('cbi-section-table-row'))
|
||||
tr.parentNode.insertBefore(tr, prev);
|
||||
else
|
||||
return;
|
||||
}
|
||||
else {
|
||||
var next = tr.nextElementSibling ? tr.nextElementSibling.nextElementSibling : null;
|
||||
|
||||
if (next && next.classList.contains('cbi-section-table-row'))
|
||||
tr.parentNode.insertBefore(tr, next);
|
||||
else if (!next)
|
||||
tr.parentNode.appendChild(tr);
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
var ids = [ ];
|
||||
|
||||
for (var i = 0, n = 0; i < tr.parentNode.childNodes.length; i++) {
|
||||
var node = tr.parentNode.childNodes[i];
|
||||
if (node.classList && node.classList.contains('cbi-section-table-row')) {
|
||||
node.classList.remove('cbi-rowstyle-1');
|
||||
node.classList.remove('cbi-rowstyle-2');
|
||||
node.classList.add((n++ % 2) ? 'cbi-rowstyle-2' : 'cbi-rowstyle-1');
|
||||
|
||||
if (/-([^\-]+)$/.test(node.id))
|
||||
ids.push(RegExp.$1);
|
||||
}
|
||||
}
|
||||
|
||||
var input = document.getElementById(store);
|
||||
if (input)
|
||||
input.value = ids.join(' ');
|
||||
|
||||
window.scrollTo(0, tr.offsetTop);
|
||||
void tr.offsetWidth;
|
||||
tr.classList.add('flash');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function cbi_tag_last(container)
|
||||
{
|
||||
var last;
|
||||
|
||||
for (var i = 0; i < container.childNodes.length; i++) {
|
||||
var c = container.childNodes[i];
|
||||
if (matchesElem(c, 'div')) {
|
||||
c.classList.remove('cbi-value-last');
|
||||
last = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (last)
|
||||
last.classList.add('cbi-value-last');
|
||||
}
|
||||
|
||||
function cbi_submit(elem, name, value, action)
|
||||
{
|
||||
var form = elem.form || findParent(elem, 'form');
|
||||
|
||||
if (!form)
|
||||
return false;
|
||||
|
||||
if (action)
|
||||
form.action = action;
|
||||
|
||||
if (name) {
|
||||
var hidden = form.querySelector('input[type="hidden"][name="%s"]'.format(name)) ||
|
||||
E('input', { type: 'hidden', name: name });
|
||||
|
||||
hidden.value = value || '1';
|
||||
form.appendChild(hidden);
|
||||
}
|
||||
|
||||
form.submit();
|
||||
return true;
|
||||
}
|
||||
|
||||
String.prototype.format = function()
|
||||
{
|
||||
if (!RegExp)
|
||||
return;
|
||||
|
||||
var html_esc = [/&/g, '&', /"/g, '"', /'/g, ''', /</g, '<', />/g, '>'];
|
||||
var quot_esc = [/"/g, '"', /'/g, '''];
|
||||
|
||||
function esc(s, r) {
|
||||
var t = typeof(s);
|
||||
|
||||
if (s == null || t === 'object' || t === 'function')
|
||||
return '';
|
||||
|
||||
if (t !== 'string')
|
||||
s = String(s);
|
||||
|
||||
for (var i = 0; i < r.length; i += 2)
|
||||
s = s.replace(r[i], r[i+1]);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
var str = this;
|
||||
var out = '';
|
||||
var re = /^(([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X|q|h|j|t|m))/;
|
||||
var a = b = [], numSubstitutions = 0, numMatches = 0;
|
||||
|
||||
while (a = re.exec(str)) {
|
||||
var m = a[1];
|
||||
var leftpart = a[2], pPad = a[3], pJustify = a[4], pMinLength = a[5];
|
||||
var pPrecision = a[6], pType = a[7];
|
||||
|
||||
numMatches++;
|
||||
|
||||
if (pType == '%') {
|
||||
subst = '%';
|
||||
}
|
||||
else {
|
||||
if (numSubstitutions < arguments.length) {
|
||||
var param = arguments[numSubstitutions++];
|
||||
|
||||
var pad = '';
|
||||
if (pPad && pPad.substr(0,1) == "'")
|
||||
pad = leftpart.substr(1,1);
|
||||
else if (pPad)
|
||||
pad = pPad;
|
||||
else
|
||||
pad = ' ';
|
||||
|
||||
var justifyRight = true;
|
||||
if (pJustify && pJustify === "-")
|
||||
justifyRight = false;
|
||||
|
||||
var minLength = -1;
|
||||
if (pMinLength)
|
||||
minLength = +pMinLength;
|
||||
|
||||
var precision = -1;
|
||||
if (pPrecision && pType == 'f')
|
||||
precision = +pPrecision.substring(1);
|
||||
|
||||
var subst = param;
|
||||
|
||||
switch(pType) {
|
||||
case 'b':
|
||||
subst = Math.floor(+param || 0).toString(2);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
subst = String.fromCharCode(+param || 0);
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
subst = Math.floor(+param || 0).toFixed(0);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
var n = +param || 0;
|
||||
subst = Math.floor((n < 0) ? 0x100000000 + n : n).toFixed(0);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
subst = (precision > -1)
|
||||
? ((+param || 0.0)).toFixed(precision)
|
||||
: (+param || 0.0);
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
subst = Math.floor(+param || 0).toString(8);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
subst = param;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
subst = Math.floor(+param || 0).toString(16).toLowerCase();
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
subst = Math.floor(+param || 0).toString(16).toUpperCase();
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
subst = esc(param, html_esc);
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
subst = esc(param, quot_esc);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
var td = 0;
|
||||
var th = 0;
|
||||
var tm = 0;
|
||||
var ts = (param || 0);
|
||||
|
||||
if (ts > 59) {
|
||||
tm = Math.floor(ts / 60);
|
||||
ts = (ts % 60);
|
||||
}
|
||||
|
||||
if (tm > 59) {
|
||||
th = Math.floor(tm / 60);
|
||||
tm = (tm % 60);
|
||||
}
|
||||
|
||||
if (th > 23) {
|
||||
td = Math.floor(th / 24);
|
||||
th = (th % 24);
|
||||
}
|
||||
|
||||
subst = (td > 0)
|
||||
? String.format('%dd %dh %dm %ds', td, th, tm, ts)
|
||||
: String.format('%dh %dm %ds', th, tm, ts);
|
||||
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
var mf = pMinLength ? +pMinLength : 1000;
|
||||
var pr = pPrecision ? ~~(10 * +('0' + pPrecision)) : 2;
|
||||
|
||||
var i = 0;
|
||||
var val = (+param || 0);
|
||||
var units = [ ' ', ' K', ' M', ' G', ' T', ' P', ' E' ];
|
||||
|
||||
for (i = 0; (i < units.length) && (val > mf); i++)
|
||||
val /= mf;
|
||||
|
||||
if (i)
|
||||
subst = val.toFixed(pr) + units[i] + (mf == 1024 ? 'i' : '');
|
||||
else
|
||||
subst = val + ' ';
|
||||
|
||||
pMinLength = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pMinLength) {
|
||||
subst = subst.toString();
|
||||
for (var i = subst.length; i < pMinLength; i++)
|
||||
if (pJustify == '-')
|
||||
subst = subst + ' ';
|
||||
else
|
||||
subst = pad + subst;
|
||||
}
|
||||
|
||||
out += leftpart + subst;
|
||||
str = str.substr(m.length);
|
||||
}
|
||||
|
||||
return out + str;
|
||||
}
|
||||
|
||||
String.prototype.nobr = function()
|
||||
{
|
||||
return this.replace(/[\s\n]+/g, ' ');
|
||||
}
|
||||
|
||||
String.format = function()
|
||||
{
|
||||
var a = [ ];
|
||||
|
||||
for (var i = 1; i < arguments.length; i++)
|
||||
a.push(arguments[i]);
|
||||
|
||||
return ''.format.apply(arguments[0], a);
|
||||
}
|
||||
|
||||
String.nobr = function()
|
||||
{
|
||||
var a = [ ];
|
||||
|
||||
for (var i = 1; i < arguments.length; i++)
|
||||
a.push(arguments[i]);
|
||||
|
||||
return ''.nobr.apply(arguments[0], a);
|
||||
}
|
||||
|
||||
if (window.NodeList && !NodeList.prototype.forEach) {
|
||||
NodeList.prototype.forEach = function (callback, thisArg) {
|
||||
thisArg = thisArg || window;
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
callback.call(thisArg, this[i], i, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame = function(f) {
|
||||
window.setTimeout(function() {
|
||||
f(new Date().getTime())
|
||||
}, 1000/30);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function isElem(e) { return L.dom.elem(e) }
|
||||
function toElem(s) { return L.dom.parse(s) }
|
||||
function matchesElem(node, selector) { return L.dom.matches(node, selector) }
|
||||
function findParent(node, selector) { return L.dom.parent(node, selector) }
|
||||
function E() { return L.dom.create.apply(L.dom, arguments) }
|
||||
|
||||
if (typeof(window.CustomEvent) !== 'function') {
|
||||
function CustomEvent(event, params) {
|
||||
params = params || { bubbles: false, cancelable: false, detail: undefined };
|
||||
var evt = document.createEvent('CustomEvent');
|
||||
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
|
||||
return evt;
|
||||
}
|
||||
|
||||
CustomEvent.prototype = window.Event.prototype;
|
||||
window.CustomEvent = CustomEvent;
|
||||
}
|
||||
|
||||
function cbi_dropdown_init(sb) {
|
||||
if (sb && L.dom.findClassInstance(sb) instanceof L.ui.Dropdown)
|
||||
return;
|
||||
|
||||
var dl = new L.ui.Dropdown(sb, null, { name: sb.getAttribute('name') });
|
||||
return dl.bind(sb);
|
||||
}
|
||||
|
||||
function cbi_update_table(table, data, placeholder) {
|
||||
var target = isElem(table) ? table : document.querySelector(table);
|
||||
|
||||
if (!isElem(target))
|
||||
return;
|
||||
|
||||
var t = L.dom.findClassInstance(target);
|
||||
|
||||
if (!(t instanceof L.ui.Table)) {
|
||||
t = new L.ui.Table(target);
|
||||
L.dom.bindClassInstance(target, t);
|
||||
}
|
||||
|
||||
t.update(data, placeholder);
|
||||
}
|
||||
|
||||
function showModal(title, children)
|
||||
{
|
||||
return L.showModal(title, children);
|
||||
}
|
||||
|
||||
function hideModal()
|
||||
{
|
||||
return L.hideModal();
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('validation-failure', function(ev) {
|
||||
if (ev.target === document.activeElement)
|
||||
L.showTooltip(ev);
|
||||
});
|
||||
|
||||
document.addEventListener('validation-success', function(ev) {
|
||||
if (ev.target === document.activeElement)
|
||||
L.hideTooltip(ev);
|
||||
});
|
||||
|
||||
L.require('ui').then(function(ui) {
|
||||
document.querySelectorAll('.table').forEach(cbi_update_table);
|
||||
});
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
|
||||
<defs>
|
||||
<radialGradient id="a" cx="0" cy="1" r="1">
|
||||
<stop offset="0" stop-color="#89b"/>
|
||||
<stop offset="1" stop-color="#def"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g stroke="#000" stroke-width="2">
|
||||
<path d="M9 2h23l2 1 9 9 1 2v31l-2 1H9l-1-1V4l1-2z" fill="url(#a)"/>
|
||||
<path d="M32 2v11l1 1h11" fill="url(#a)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 389 B |
@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
|
||||
<defs>
|
||||
<radialGradient id="a" cx="0" cy="0" r="1">
|
||||
<stop offset="0" stop-color="#ddf"/>
|
||||
<stop offset="1" stop-color="#46b"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g stroke="#024" stroke-width="2">
|
||||
<path d="M44 14l1 2v25l-1 1H4l-1-1V18v0l1-6 1-2h17l2 2v1l1 1z" fill="url(#a)"/>
|
||||
<path d="M3 18h21l1-1v-3" fill="none"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 415 B |
@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
|
||||
<defs>
|
||||
<radialGradient id="a" cx="0" cy="1" r="1">
|
||||
<stop offset="0" stop-color="#89b"/>
|
||||
<stop offset="1" stop-color="#def"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g stroke="#000" stroke-width="2">
|
||||
<path d="M9 2h23l2 1 9 9 1 2v31l-2 1H9l-1-1V4l1-2z" fill="url(#a)"/>
|
||||
<path d="M32 2v11l1 1h11" fill="url(#a)"/>
|
||||
</g>
|
||||
<path d="M13 33h13v6l12-10-12-11v7H13z" fill="#035"/>
|
||||
</svg>
|
After Width: | Height: | Size: 444 B |
@ -0,0 +1,560 @@
|
||||
'use strict';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require tools.prng as random';
|
||||
|
||||
|
||||
function initFirewallState() {
|
||||
return L.resolveDefault(uci.load('firewall'));
|
||||
}
|
||||
|
||||
function parseEnum(s, values) {
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
s = String(s).toUpperCase();
|
||||
|
||||
if (s == '')
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < values.length; i++)
|
||||
if (values[i].toUpperCase().indexOf(s) == 0)
|
||||
return values[i];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function parsePolicy(s, defaultValue) {
|
||||
return parseEnum(s, ['DROP', 'REJECT', 'ACCEPT']) || (arguments.length < 2 ? null : defaultValue);
|
||||
}
|
||||
|
||||
|
||||
var Firewall, AbstractFirewallItem, Defaults, Zone, Forwarding, Redirect, Rule;
|
||||
|
||||
function lookupZone(name) {
|
||||
var z = uci.get('firewall', name);
|
||||
|
||||
if (z != null && z['.type'] == 'zone')
|
||||
return new Zone(z['.name']);
|
||||
|
||||
var sections = uci.sections('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != name)
|
||||
continue;
|
||||
|
||||
return new Zone(sections[i]['.name']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getColorForName(forName) {
|
||||
if (forName == null)
|
||||
return '#eeeeee';
|
||||
else if (forName == 'lan')
|
||||
return '#90f090';
|
||||
else if (forName == 'wan')
|
||||
return '#f09090';
|
||||
|
||||
return random.derive_color(forName);
|
||||
}
|
||||
|
||||
|
||||
Firewall = L.Class.extend({
|
||||
getDefaults: function() {
|
||||
return initFirewallState().then(function() {
|
||||
return new Defaults();
|
||||
});
|
||||
},
|
||||
|
||||
newZone: function() {
|
||||
return initFirewallState().then(L.bind(function() {
|
||||
var name = 'newzone',
|
||||
count = 1;
|
||||
|
||||
while (this.getZone(name) != null)
|
||||
name = 'newzone%d'.format(++count);
|
||||
|
||||
return this.addZone(name);
|
||||
}, this));
|
||||
},
|
||||
|
||||
addZone: function(name) {
|
||||
return initFirewallState().then(L.bind(function() {
|
||||
if (name == null || !/^[a-zA-Z0-9_]+$/.test(name))
|
||||
return null;
|
||||
|
||||
if (lookupZone(name) != null)
|
||||
return null;
|
||||
|
||||
var d = new Defaults(),
|
||||
z = uci.add('firewall', 'zone');
|
||||
|
||||
uci.set('firewall', z, 'name', name);
|
||||
uci.set('firewall', z, 'input', d.getInput() || 'DROP');
|
||||
uci.set('firewall', z, 'output', d.getOutput() || 'DROP');
|
||||
uci.set('firewall', z, 'forward', d.getForward() || 'DROP');
|
||||
|
||||
return new Zone(z);
|
||||
}, this));
|
||||
},
|
||||
|
||||
getZone: function(name) {
|
||||
return initFirewallState().then(function() {
|
||||
return lookupZone(name);
|
||||
});
|
||||
},
|
||||
|
||||
getZones: function() {
|
||||
return initFirewallState().then(function() {
|
||||
var sections = uci.sections('firewall', 'zone'),
|
||||
zones = [];
|
||||
|
||||
for (var i = 0; i < sections.length; i++)
|
||||
zones.push(new Zone(sections[i]['.name']));
|
||||
|
||||
zones.sort(function(a, b) { return a.getName() > b.getName() });
|
||||
|
||||
return zones;
|
||||
});
|
||||
},
|
||||
|
||||
getZoneByNetwork: function(network) {
|
||||
return initFirewallState().then(function() {
|
||||
var sections = uci.sections('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++)
|
||||
if (L.toArray(sections[i].network).indexOf(network) != -1)
|
||||
return new Zone(sections[i]['.name']);
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
deleteZone: function(name) {
|
||||
return initFirewallState().then(function() {
|
||||
var section = uci.get('firewall', name),
|
||||
found = false;
|
||||
|
||||
if (section != null && section['.type'] == 'zone') {
|
||||
found = true;
|
||||
name = section.name;
|
||||
uci.remove('firewall', section['.name']);
|
||||
}
|
||||
else if (name != null) {
|
||||
var sections = uci.sections('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != name)
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
uci.remove('firewall', sections[i]['.name']);
|
||||
}
|
||||
}
|
||||
|
||||
if (found == true) {
|
||||
sections = uci.sections('firewall');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i]['.type'] != 'rule' &&
|
||||
sections[i]['.type'] != 'redirect' &&
|
||||
sections[i]['.type'] != 'forwarding')
|
||||
continue;
|
||||
|
||||
if (sections[i].src == name || sections[i].dest == name)
|
||||
uci.remove('firewall', sections[i]['.name']);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
});
|
||||
},
|
||||
|
||||
renameZone: function(oldName, newName) {
|
||||
return initFirewallState().then(L.bind(function() {
|
||||
if (oldName == null || newName == null || !/^[a-zA-Z0-9_]+$/.test(newName))
|
||||
return false;
|
||||
|
||||
if (lookupZone(newName) != null)
|
||||
return false;
|
||||
|
||||
var sections = uci.sections('firewall', 'zone'),
|
||||
found = false;
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != oldName)
|
||||
continue;
|
||||
|
||||
uci.set('firewall', sections[i]['.name'], 'name', newName);
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found == true) {
|
||||
sections = uci.sections('firewall');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i]['.type'] != 'rule' &&
|
||||
sections[i]['.type'] != 'redirect' &&
|
||||
sections[i]['.type'] != 'forwarding')
|
||||
continue;
|
||||
|
||||
if (sections[i].src == oldName)
|
||||
uci.set('firewall', sections[i]['.name'], 'src', newName);
|
||||
|
||||
if (sections[i].dest == oldName)
|
||||
uci.set('firewall', sections[i]['.name'], 'dest', newName);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}, this));
|
||||
},
|
||||
|
||||
deleteNetwork: function(network) {
|
||||
return this.getZones().then(L.bind(function(zones) {
|
||||
var rv = false;
|
||||
|
||||
for (var i = 0; i < zones.length; i++)
|
||||
if (zones[i].deleteNetwork(network))
|
||||
rv = true;
|
||||
|
||||
return rv;
|
||||
}, this));
|
||||
},
|
||||
|
||||
getColorForName: getColorForName,
|
||||
|
||||
getZoneColorStyle: function(zone) {
|
||||
var hex = (zone instanceof Zone) ? zone.getColor() : getColorForName((zone != null && zone != '*') ? zone : null);
|
||||
|
||||
return '--zone-color-rgb:%d, %d, %d; background-color:rgb(var(--zone-color-rgb))'.format(
|
||||
parseInt(hex.substring(1, 3), 16),
|
||||
parseInt(hex.substring(3, 5), 16),
|
||||
parseInt(hex.substring(5, 7), 16)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
AbstractFirewallItem = L.Class.extend({
|
||||
get: function(option) {
|
||||
return uci.get('firewall', this.sid, option);
|
||||
},
|
||||
|
||||
set: function(option, value) {
|
||||
return uci.set('firewall', this.sid, option, value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Defaults = AbstractFirewallItem.extend({
|
||||
__init__: function() {
|
||||
var sections = uci.sections('firewall', 'defaults');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
this.sid = sections[i]['.name'];
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.sid == null)
|
||||
this.sid = uci.add('firewall', 'defaults');
|
||||
},
|
||||
|
||||
isSynFlood: function() {
|
||||
return (this.get('syn_flood') == '1');
|
||||
},
|
||||
|
||||
isDropInvalid: function() {
|
||||
return (this.get('drop_invalid') == '1');
|
||||
},
|
||||
|
||||
getInput: function() {
|
||||
return parsePolicy(this.get('input'), 'DROP');
|
||||
},
|
||||
|
||||
getOutput: function() {
|
||||
return parsePolicy(this.get('output'), 'DROP');
|
||||
},
|
||||
|
||||
getForward: function() {
|
||||
return parsePolicy(this.get('forward'), 'DROP');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Zone = AbstractFirewallItem.extend({
|
||||
__init__: function(name) {
|
||||
var section = uci.get('firewall', name);
|
||||
|
||||
if (section != null && section['.type'] == 'zone') {
|
||||
this.sid = name;
|
||||
this.data = section;
|
||||
}
|
||||
else if (name != null) {
|
||||
var sections = uci.get('firewall', 'zone');
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].name != name)
|
||||
continue;
|
||||
|
||||
this.sid = sections[i]['.name'];
|
||||
this.data = sections[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isMasquerade: function() {
|
||||
return (this.get('masq') == '1');
|
||||
},
|
||||
|
||||
getName: function() {
|
||||
return this.get('name');
|
||||
},
|
||||
|
||||
getNetwork: function() {
|
||||
return this.get('network');
|
||||
},
|
||||
|
||||
getInput: function() {
|
||||
return parsePolicy(this.get('input'), (new Defaults()).getInput());
|
||||
},
|
||||
|
||||
getOutput: function() {
|
||||
return parsePolicy(this.get('output'), (new Defaults()).getOutput());
|
||||
},
|
||||
|
||||
getForward: function() {
|
||||
return parsePolicy(this.get('forward'), (new Defaults()).getForward());
|
||||
},
|
||||
|
||||
addNetwork: function(network) {
|
||||
var section = uci.get('network', network);
|
||||
|
||||
if (section == null || section['.type'] != 'interface')
|
||||
return false;
|
||||
|
||||
var newNetworks = this.getNetworks();
|
||||
|
||||
if (newNetworks.filter(function(net) { return net == network }).length)
|
||||
return false;
|
||||
|
||||
newNetworks.push(network);
|
||||
this.set('network', newNetworks);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
deleteNetwork: function(network) {
|
||||
var oldNetworks = this.getNetworks(),
|
||||
newNetworks = oldNetworks.filter(function(net) { return net != network });
|
||||
|
||||
if (newNetworks.length > 0)
|
||||
this.set('network', newNetworks);
|
||||
else
|
||||
this.set('network', null);
|
||||
|
||||
return (newNetworks.length < oldNetworks.length);
|
||||
},
|
||||
|
||||
getNetworks: function() {
|
||||
return L.toArray(this.get('network'));
|
||||
},
|
||||
|
||||
clearNetworks: function() {
|
||||
this.set('network', null);
|
||||
},
|
||||
|
||||
getDevices: function() {
|
||||
return L.toArray(this.get('device'));
|
||||
},
|
||||
|
||||
getSubnets: function() {
|
||||
return L.toArray(this.get('subnet'));
|
||||
},
|
||||
|
||||
getForwardingsBy: function(what) {
|
||||
var sections = uci.sections('firewall', 'forwarding'),
|
||||
forwards = [];
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].src == null || sections[i].dest == null)
|
||||
continue;
|
||||
|
||||
if (sections[i][what] != this.getName())
|
||||
continue;
|
||||
|
||||
forwards.push(new Forwarding(sections[i]['.name']));
|
||||
}
|
||||
|
||||
return forwards;
|
||||
},
|
||||
|
||||
addForwardingTo: function(dest) {
|
||||
var forwards = this.getForwardingsBy('src'),
|
||||
zone = lookupZone(dest);
|
||||
|
||||
if (zone == null || zone.getName() == this.getName())
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < forwards.length; i++)
|
||||
if (forwards[i].getDestination() == zone.getName())
|
||||
return null;
|
||||
|
||||
var sid = uci.add('firewall', 'forwarding');
|
||||
|
||||
uci.set('firewall', sid, 'src', this.getName());
|
||||
uci.set('firewall', sid, 'dest', zone.getName());
|
||||
|
||||
return new Forwarding(sid);
|
||||
},
|
||||
|
||||
addForwardingFrom: function(src) {
|
||||
var forwards = this.getForwardingsBy('dest'),
|
||||
zone = lookupZone(src);
|
||||
|
||||
if (zone == null || zone.getName() == this.getName())
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < forwards.length; i++)
|
||||
if (forwards[i].getSource() == zone.getName())
|
||||
return null;
|
||||
|
||||
var sid = uci.add('firewall', 'forwarding');
|
||||
|
||||
uci.set('firewall', sid, 'src', zone.getName());
|
||||
uci.set('firewall', sid, 'dest', this.getName());
|
||||
|
||||
return new Forwarding(sid);
|
||||
},
|
||||
|
||||
deleteForwardingsBy: function(what) {
|
||||
var sections = uci.sections('firewall', 'forwarding'),
|
||||
found = false;
|
||||
|
||||
for (var i = 0; i < sections.length; i++) {
|
||||
if (sections[i].src == null || sections[i].dest == null)
|
||||
continue;
|
||||
|
||||
if (sections[i][what] != this.getName())
|
||||
continue;
|
||||
|
||||
uci.remove('firewall', sections[i]['.name']);
|
||||
found = true;
|
||||
}
|
||||
|
||||
return found;
|
||||
},
|
||||
|
||||
deleteForwarding: function(forwarding) {
|
||||
if (!(forwarding instanceof Forwarding))
|
||||
return false;
|
||||
|
||||
var section = uci.get('firewall', forwarding.sid);
|
||||
|
||||
if (!section || section['.type'] != 'forwarding')
|
||||
return false;
|
||||
|
||||
uci.remove('firewall', section['.name']);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
addRedirect: function(options) {
|
||||
var sid = uci.add('firewall', 'redirect');
|
||||
|
||||
if (options != null && typeof(options) == 'object')
|
||||
for (var key in options)
|
||||
if (options.hasOwnProperty(key))
|
||||
uci.set('firewall', sid, key, options[key]);
|
||||
|
||||
uci.set('firewall', sid, 'src', this.getName());
|
||||
|
||||
return new Redirect(sid);
|
||||
},
|
||||
|
||||
addRule: function(options) {
|
||||
var sid = uci.add('firewall', 'rule');
|
||||
|
||||
if (options != null && typeof(options) == 'object')
|
||||
for (var key in options)
|
||||
if (options.hasOwnProperty(key))
|
||||
uci.set('firewall', sid, key, options[key]);
|
||||
|
||||
uci.set('firewall', sid, 'src', this.getName());
|
||||
|
||||
return new Rule(sid);
|
||||
},
|
||||
|
||||
getColor: function(forName) {
|
||||
var name = (arguments.length > 0 ? forName : this.getName());
|
||||
|
||||
return getColorForName(name);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Forwarding = AbstractFirewallItem.extend({
|
||||
__init__: function(sid) {
|
||||
this.sid = sid;
|
||||
},
|
||||
|
||||
getSource: function() {
|
||||
return this.get('src');
|
||||
},
|
||||
|
||||
getDestination: function() {
|
||||
return this.get('dest');
|
||||
},
|
||||
|
||||
getSourceZone: function() {
|
||||
return lookupZone(this.getSource());
|
||||
},
|
||||
|
||||
getDestinationZone: function() {
|
||||
return lookupZone(this.getDestination());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Rule = AbstractFirewallItem.extend({
|
||||
getSource: function() {
|
||||
return this.get('src');
|
||||
},
|
||||
|
||||
getDestination: function() {
|
||||
return this.get('dest');
|
||||
},
|
||||
|
||||
getSourceZone: function() {
|
||||
return lookupZone(this.getSource());
|
||||
},
|
||||
|
||||
getDestinationZone: function() {
|
||||
return lookupZone(this.getDestination());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Redirect = AbstractFirewallItem.extend({
|
||||
getSource: function() {
|
||||
return this.get('src');
|
||||
},
|
||||
|
||||
getDestination: function() {
|
||||
return this.get('dest');
|
||||
},
|
||||
|
||||
getSourceZone: function() {
|
||||
return lookupZone(this.getSource());
|
||||
},
|
||||
|
||||
getDestinationZone: function() {
|
||||
return lookupZone(this.getDestination());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return Firewall;
|
@ -0,0 +1,429 @@
|
||||
'use strict';
|
||||
'require rpc';
|
||||
'require request';
|
||||
'require baseclass';
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileStatEntry
|
||||
* @memberof LuCI.fs
|
||||
|
||||
* @property {string} name - Name of the directory entry
|
||||
* @property {string} type - Type of the entry, one of `block`, `char`, `directory`, `fifo`, `symlink`, `file`, `socket` or `unknown`
|
||||
* @property {number} size - Size in bytes
|
||||
* @property {number} mode - Access permissions
|
||||
* @property {number} atime - Last access time in seconds since epoch
|
||||
* @property {number} mtime - Last modification time in seconds since epoch
|
||||
* @property {number} ctime - Last change time in seconds since epoch
|
||||
* @property {number} inode - Inode number
|
||||
* @property {number} uid - Numeric owner id
|
||||
* @property {number} gid - Numeric group id
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileExecResult
|
||||
* @memberof LuCI.fs
|
||||
*
|
||||
* @property {number} code - The exit code of the invoked command
|
||||
* @property {string} [stdout] - The stdout produced by the command, if any
|
||||
* @property {string} [stderr] - The stderr produced by the command, if any
|
||||
*/
|
||||
|
||||
var callFileList, callFileStat, callFileRead, callFileWrite, callFileRemove,
|
||||
callFileExec, callFileMD5;
|
||||
|
||||
callFileList = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'list',
|
||||
params: [ 'path' ]
|
||||
});
|
||||
|
||||
callFileStat = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'stat',
|
||||
params: [ 'path' ]
|
||||
});
|
||||
|
||||
callFileRead = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'read',
|
||||
params: [ 'path' ]
|
||||
});
|
||||
|
||||
callFileWrite = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'write',
|
||||
params: [ 'path', 'data', 'mode' ]
|
||||
});
|
||||
|
||||
callFileRemove = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'remove',
|
||||
params: [ 'path' ]
|
||||
});
|
||||
|
||||
callFileExec = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'exec',
|
||||
params: [ 'command', 'params', 'env' ]
|
||||
});
|
||||
|
||||
callFileMD5 = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'md5',
|
||||
params: [ 'path' ]
|
||||
});
|
||||
|
||||
var rpcErrors = [
|
||||
null,
|
||||
'InvalidCommandError',
|
||||
'InvalidArgumentError',
|
||||
'MethodNotFoundError',
|
||||
'NotFoundError',
|
||||
'NoDataError',
|
||||
'PermissionError',
|
||||
'TimeoutError',
|
||||
'UnsupportedError'
|
||||
];
|
||||
|
||||
function handleRpcReply(expect, rc) {
|
||||
if (typeof(rc) == 'number' && rc != 0) {
|
||||
var e = new Error(rpc.getStatusText(rc)); e.name = rpcErrors[rc] || 'Error';
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (expect) {
|
||||
var type = Object.prototype.toString;
|
||||
|
||||
for (var key in expect) {
|
||||
if (rc != null && key != '')
|
||||
rc = rc[key];
|
||||
|
||||
if (rc == null || type.call(rc) != type.call(expect[key])) {
|
||||
var e = new Error(_('Unexpected reply data format')); e.name = 'TypeError';
|
||||
throw e;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
function handleCgiIoReply(res) {
|
||||
if (!res.ok || res.status != 200) {
|
||||
var e = new Error(res.statusText);
|
||||
switch (res.status) {
|
||||
case 400:
|
||||
e.name = 'InvalidArgumentError';
|
||||
break;
|
||||
|
||||
case 403:
|
||||
e.name = 'PermissionError';
|
||||
break;
|
||||
|
||||
case 404:
|
||||
e.name = 'NotFoundError';
|
||||
break;
|
||||
|
||||
default:
|
||||
e.name = 'Error';
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
switch (this.type) {
|
||||
case 'blob':
|
||||
return res.blob();
|
||||
|
||||
case 'json':
|
||||
return res.json();
|
||||
|
||||
default:
|
||||
return res.text();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @class fs
|
||||
* @memberof LuCI
|
||||
* @hideconstructor
|
||||
* @classdesc
|
||||
*
|
||||
* Provides high level utilities to wrap file system related RPC calls.
|
||||
* To import the class in views, use `'require fs'`, to import it in
|
||||
* external JavaScript, use `L.require("fs").then(...)`.
|
||||
*/
|
||||
var FileSystem = baseclass.extend(/** @lends LuCI.fs.prototype */ {
|
||||
/**
|
||||
* Obtains a listing of the specified directory.
|
||||
*
|
||||
* @param {string} path
|
||||
* The directory path to list.
|
||||
*
|
||||
* @returns {Promise<LuCI.fs.FileStatEntry[]>}
|
||||
* Returns a promise resolving to an array of stat detail objects or
|
||||
* rejecting with an error stating the failure reason.
|
||||
*/
|
||||
list: function(path) {
|
||||
return callFileList(path).then(handleRpcReply.bind(this, { entries: [] }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return file stat information on the specified path.
|
||||
*
|
||||
* @param {string} path
|
||||
* The filesystem path to stat.
|
||||
*
|
||||
* @returns {Promise<LuCI.fs.FileStatEntry>}
|
||||
* Returns a promise resolving to a stat detail object or
|
||||
* rejecting with an error stating the failure reason.
|
||||
*/
|
||||
stat: function(path) {
|
||||
return callFileStat(path).then(handleRpcReply.bind(this, { '': {} }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Read the contents of the given file and return them.
|
||||
* Note: this function is unsuitable for obtaining binary data.
|
||||
*
|
||||
* @param {string} path
|
||||
* The file path to read.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
* Returns a promise resolving to a string containing the file contents or
|
||||
* rejecting with an error stating the failure reason.
|
||||
*/
|
||||
read: function(path) {
|
||||
return callFileRead(path).then(handleRpcReply.bind(this, { data: '' }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Write the given data to the specified file path.
|
||||
* If the specified file path does not exist, it will be created, given
|
||||
* sufficient permissions.
|
||||
*
|
||||
* Note: `data` will be converted to a string using `String(data)` or to
|
||||
* `''` when it is `null`.
|
||||
*
|
||||
* @param {string} path
|
||||
* The file path to write to.
|
||||
*
|
||||
* @param {*} [data]
|
||||
* The file data to write. If it is null, it will be set to an empty
|
||||
* string.
|
||||
*
|
||||
* @param {number} [mode]
|
||||
* The permissions to use on file creation. Default is 420 (0644).
|
||||
*
|
||||
* @returns {Promise<number>}
|
||||
* Returns a promise resolving to `0` or rejecting with an error stating
|
||||
* the failure reason.
|
||||
*/
|
||||
write: function(path, data, mode) {
|
||||
data = (data != null) ? String(data) : '';
|
||||
mode = (mode != null) ? mode : 420; // 0644
|
||||
return callFileWrite(path, data, mode).then(handleRpcReply.bind(this, { '': 0 }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Unlink the given file.
|
||||
*
|
||||
* @param {string}
|
||||
* The file path to remove.
|
||||
*
|
||||
* @returns {Promise<number>}
|
||||
* Returns a promise resolving to `0` or rejecting with an error stating
|
||||
* the failure reason.
|
||||
*/
|
||||
remove: function(path) {
|
||||
return callFileRemove(path).then(handleRpcReply.bind(this, { '': 0 }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute the specified command, optionally passing params and
|
||||
* environment variables.
|
||||
*
|
||||
* Note: The `command` must be either the path to an executable,
|
||||
* or a basename without arguments in which case it will be searched
|
||||
* in $PATH. If specified, the values given in `params` will be passed
|
||||
* as arguments to the command.
|
||||
*
|
||||
* The key/value pairs in the optional `env` table are translated to
|
||||
* `setenv()` calls prior to running the command.
|
||||
*
|
||||
* @param {string} command
|
||||
* The command to invoke.
|
||||
*
|
||||
* @param {string[]} [params]
|
||||
* The arguments to pass to the command.
|
||||
*
|
||||
* @param {Object.<string, string>} [env]
|
||||
* Environment variables to set.
|
||||
*
|
||||
* @returns {Promise<LuCI.fs.FileExecResult>}
|
||||
* Returns a promise resolving to an object describing the execution
|
||||
* results or rejecting with an error stating the failure reason.
|
||||
*/
|
||||
exec: function(command, params, env) {
|
||||
if (!Array.isArray(params))
|
||||
params = null;
|
||||
|
||||
if (!L.isObject(env))
|
||||
env = null;
|
||||
|
||||
return callFileExec(command, params, env).then(handleRpcReply.bind(this, { '': {} }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Read the contents of the given file, trim leading and trailing white
|
||||
* space and return the trimmed result. In case of errors, return an empty
|
||||
* string instead.
|
||||
*
|
||||
* Note: this function is useful to read single-value files in `/sys`
|
||||
* or `/proc`.
|
||||
*
|
||||
* This function is guaranteed to not reject its promises, on failure,
|
||||
* an empty string will be returned.
|
||||
*
|
||||
* @param {string} path
|
||||
* The file path to read.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
* Returns a promise resolving to the file contents or the empty string
|
||||
* on failure.
|
||||
*/
|
||||
trimmed: function(path) {
|
||||
return L.resolveDefault(this.read(path), '').then(function(s) {
|
||||
return s.trim();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Read the contents of the given file, split it into lines, trim
|
||||
* leading and trailing white space of each line and return the
|
||||
* resulting array.
|
||||
*
|
||||
* This function is guaranteed to not reject its promises, on failure,
|
||||
* an empty array will be returned.
|
||||
*
|
||||
* @param {string} path
|
||||
* The file path to read.
|
||||
*
|
||||
* @returns {Promise<string[]>}
|
||||
* Returns a promise resolving to an array containing the stripped lines
|
||||
* of the given file or `[]` on failure.
|
||||
*/
|
||||
lines: function(path) {
|
||||
return L.resolveDefault(this.read(path), '').then(function(s) {
|
||||
var lines = [];
|
||||
|
||||
s = s.trim();
|
||||
|
||||
if (s != '') {
|
||||
var l = s.split(/\n/);
|
||||
|
||||
for (var i = 0; i < l.length; i++)
|
||||
lines.push(l[i].trim());
|
||||
}
|
||||
|
||||
return lines;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Read the contents of the given file and return them, bypassing ubus.
|
||||
*
|
||||
* This function will read the requested file through the cgi-io
|
||||
* helper applet at `/cgi-bin/cgi-download` which bypasses the ubus rpc
|
||||
* transport. This is useful to fetch large file contents which might
|
||||
* exceed the ubus message size limits or which contain binary data.
|
||||
*
|
||||
* The cgi-io helper will enforce the same access permission rules as
|
||||
* the ubus based read call.
|
||||
*
|
||||
* @param {string} path
|
||||
* The file path to read.
|
||||
*
|
||||
* @param {string} [type=text]
|
||||
* The expected type of read file contents. Valid values are `text` to
|
||||
* interpret the contents as string, `json` to parse the contents as JSON
|
||||
* or `blob` to return the contents as Blob instance.
|
||||
*
|
||||
* @returns {Promise<*>}
|
||||
* Returns a promise resolving with the file contents interpreted according
|
||||
* to the specified type or rejecting with an error stating the failure
|
||||
* reason.
|
||||
*/
|
||||
read_direct: function(path, type) {
|
||||
var postdata = 'sessionid=%s&path=%s'
|
||||
.format(encodeURIComponent(L.env.sessionid), encodeURIComponent(path));
|
||||
|
||||
return request.post(L.env.cgi_base + '/cgi-download', postdata, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
responseType: (type == 'blob') ? 'blob' : 'text'
|
||||
}).then(handleCgiIoReply.bind({ type: type }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute the specified command, bypassing ubus.
|
||||
*
|
||||
* Note: The `command` must be either the path to an executable,
|
||||
* or a basename without arguments in which case it will be searched
|
||||
* in $PATH. If specified, the values given in `params` will be passed
|
||||
* as arguments to the command.
|
||||
*
|
||||
* This function will invoke the requested commands through the cgi-io
|
||||
* helper applet at `/cgi-bin/cgi-exec` which bypasses the ubus rpc
|
||||
* transport. This is useful to fetch large command outputs which might
|
||||
* exceed the ubus message size limits or which contain binary data.
|
||||
*
|
||||
* The cgi-io helper will enforce the same access permission rules as
|
||||
* the ubus based exec call.
|
||||
*
|
||||
* @param {string} command
|
||||
* The command to invoke.
|
||||
*
|
||||
* @param {string[]} [params]
|
||||
* The arguments to pass to the command.
|
||||
*
|
||||
* @param {string} [type=text]
|
||||
* The expected output type of the invoked program. Valid values are
|
||||
* `text` to interpret the output as string, `json` to parse the output
|
||||
* as JSON or `blob` to return the output as Blob instance.
|
||||
*
|
||||
* @param {boolean} [latin1=false]
|
||||
* Whether to encode the command line as Latin1 instead of UTF-8. This
|
||||
* is usually not needed but can be useful for programs that cannot
|
||||
* handle UTF-8 input.
|
||||
*
|
||||
* @returns {Promise<*>}
|
||||
* Returns a promise resolving with the command stdout output interpreted
|
||||
* according to the specified type or rejecting with an error stating the
|
||||
* failure reason.
|
||||
*/
|
||||
exec_direct: function(command, params, type, latin1) {
|
||||
var cmdstr = String(command)
|
||||
.replace(/\\/g, '\\\\').replace(/(\s)/g, '\\$1');
|
||||
|
||||
if (Array.isArray(params))
|
||||
for (var i = 0; i < params.length; i++)
|
||||
cmdstr += ' ' + String(params[i])
|
||||
.replace(/\\/g, '\\\\').replace(/(\s)/g, '\\$1');
|
||||
|
||||
if (latin1)
|
||||
cmdstr = escape(cmdstr).replace(/\+/g, '%2b');
|
||||
else
|
||||
cmdstr = encodeURIComponent(cmdstr);
|
||||
|
||||
var postdata = 'sessionid=%s&command=%s'
|
||||
.format(encodeURIComponent(L.env.sessionid), cmdstr);
|
||||
|
||||
return request.post(L.env.cgi_base + '/cgi-exec', postdata, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
responseType: (type == 'blob') ? 'blob' : 'text'
|
||||
}).then(handleCgiIoReply.bind({ type: type }));
|
||||
}
|
||||
});
|
||||
|
||||
return FileSystem;
|
After Width: | Height: | Size: 651 B |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 646 B |
After Width: | Height: | Size: 385 B |
After Width: | Height: | Size: 664 B |
After Width: | Height: | Size: 384 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 489 B |
After Width: | Height: | Size: 561 B |
After Width: | Height: | Size: 451 B |
After Width: | Height: | Size: 430 B |
After Width: | Height: | Size: 454 B |
After Width: | Height: | Size: 454 B |
After Width: | Height: | Size: 440 B |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 656 B |
After Width: | Height: | Size: 388 B |
After Width: | Height: | Size: 339 B |
After Width: | Height: | Size: 234 B |
After Width: | Height: | Size: 656 B |
After Width: | Height: | Size: 388 B |
After Width: | Height: | Size: 745 B |
After Width: | Height: | Size: 480 B |
@ -0,0 +1,5 @@
|
||||
/* Licensed under the BSD license. Copyright 2014 - Bram Stein. All rights reserved.
|
||||
* https://github.com/bramstein/promis */
|
||||
(function(){'use strict';var f,g=[];function l(a){g.push(a);1==g.length&&f()}function m(){for(;g.length;)g[0](),g.shift()}f=function(){setTimeout(m)};function n(a){this.a=p;this.b=void 0;this.f=[];var b=this;try{a(function(a){q(b,a)},function(a){r(b,a)})}catch(c){r(b,c)}}var p=2;function t(a){return new n(function(b,c){c(a)})}function u(a){return new n(function(b){b(a)})}function q(a,b){if(a.a==p){if(b==a)throw new TypeError;var c=!1;try{var d=b&&b.then;if(null!=b&&"object"==typeof b&&"function"==typeof d){d.call(b,function(b){c||q(a,b);c=!0},function(b){c||r(a,b);c=!0});return}}catch(e){c||r(a,e);return}a.a=0;a.b=b;v(a)}}
|
||||
function r(a,b){if(a.a==p){if(b==a)throw new TypeError;a.a=1;a.b=b;v(a)}}function v(a){l(function(){if(a.a!=p)for(;a.f.length;){var b=a.f.shift(),c=b[0],d=b[1],e=b[2],b=b[3];try{0==a.a?"function"==typeof c?e(c.call(void 0,a.b)):e(a.b):1==a.a&&("function"==typeof d?e(d.call(void 0,a.b)):b(a.b))}catch(h){b(h)}}})}n.prototype.g=function(a){return this.c(void 0,a)};n.prototype.c=function(a,b){var c=this;return new n(function(d,e){c.f.push([a,b,d,e]);v(c)})};
|
||||
function w(a){return new n(function(b,c){function d(c){return function(d){h[c]=d;e+=1;e==a.length&&b(h)}}var e=0,h=[];0==a.length&&b(h);for(var k=0;k<a.length;k+=1)u(a[k]).c(d(k),c)})}function x(a){return new n(function(b,c){for(var d=0;d<a.length;d+=1)u(a[d]).c(b,c)})};window.Promise||(window.Promise=n,window.Promise.resolve=u,window.Promise.reject=t,window.Promise.race=x,window.Promise.all=w,window.Promise.prototype.then=n.prototype.c,window.Promise.prototype["catch"]=n.prototype.g,window.Promise.prototype.finally=function(a){return this.c(a,a)});}());
|
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
'require rpc';
|
||||
'require form';
|
||||
'require network';
|
||||
|
||||
var callFileRead = rpc.declare({
|
||||
object: 'file',
|
||||
method: 'read',
|
||||
params: [ 'path' ],
|
||||
expect: { data: '' },
|
||||
filter: function(value) { return value.trim() }
|
||||
});
|
||||
|
||||
return network.registerProtocol('dhcp', {
|
||||
getI18n: function() {
|
||||
return _('DHCP client');
|
||||
},
|
||||
|
||||
renderFormOptions: function(s) {
|
||||
var o;
|
||||
|
||||
o = s.taboption('general', form.Value, 'hostname', _('Hostname to send when requesting DHCP'));
|
||||
o.default = '';
|
||||
o.value('', _('Send the hostname of this device'));
|
||||
o.value('*', _('Do not send a hostname'));
|
||||
o.datatype = 'or(hostname, "*")';
|
||||
o.load = function(section_id) {
|
||||
return callFileRead('/proc/sys/kernel/hostname').then(L.bind(function(hostname) {
|
||||
this.placeholder = hostname;
|
||||
return form.Value.prototype.load.apply(this, [section_id]);
|
||||
}, this));
|
||||
};
|
||||
|
||||
o = s.taboption('advanced', form.Flag, 'broadcast', _('Use broadcast flag'), _('Required for certain ISPs, e.g. Charter with DOCSIS 3'));
|
||||
o.default = o.disabled;
|
||||
|
||||
o = s.taboption('advanced', form.Value, 'clientid', _('Client ID to send when requesting DHCP'));
|
||||
o.datatype = 'hexstring';
|
||||
|
||||
s.taboption('advanced', form.Value, 'vendorid', _('Vendor Class to send when requesting DHCP'));
|
||||
}
|
||||
});
|
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
'require network';
|
||||
|
||||
return network.registerProtocol('none', {
|
||||
getI18n: function() {
|
||||
return _('Unmanaged');
|
||||
}
|
||||
});
|
@ -0,0 +1,196 @@
|
||||
'use strict';
|
||||
'require form';
|
||||
'require network';
|
||||
'require validation';
|
||||
|
||||
function isCIDR(value) {
|
||||
return Array.isArray(value) || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/(\d{1,2}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.test(value);
|
||||
}
|
||||
|
||||
function calculateBroadcast(s, use_cfgvalue) {
|
||||
var readfn = use_cfgvalue ? 'cfgvalue' : 'formvalue',
|
||||
addropt = s.children.filter(function(o) { return o.option == 'ipaddr'})[0],
|
||||
addrvals = addropt ? L.toArray(addropt[readfn](s.section)) : [],
|
||||
maskopt = s.children.filter(function(o) { return o.option == 'netmask'})[0],
|
||||
maskval = maskopt ? maskopt[readfn](s.section) : null,
|
||||
firstsubnet = maskval ? addrvals[0] + '/' + maskval : addrvals.filter(function(a) { return a.indexOf('/') > 0 })[0];
|
||||
|
||||
if (firstsubnet == null)
|
||||
return null;
|
||||
|
||||
var addr_mask = firstsubnet.split('/'),
|
||||
addr = validation.parseIPv4(addr_mask[0]),
|
||||
mask = addr_mask[1];
|
||||
|
||||
if (!isNaN(mask))
|
||||
mask = validation.parseIPv4(network.prefixToMask(+mask));
|
||||
else
|
||||
mask = validation.parseIPv4(mask);
|
||||
|
||||
var bc = [
|
||||
addr[0] | (~mask[0] >>> 0 & 255),
|
||||
addr[1] | (~mask[1] >>> 0 & 255),
|
||||
addr[2] | (~mask[2] >>> 0 & 255),
|
||||
addr[3] | (~mask[3] >>> 0 & 255)
|
||||
];
|
||||
|
||||
return bc.join('.');
|
||||
}
|
||||
|
||||
function validateBroadcast(section_id, value) {
|
||||
var opt = this.map.lookupOption('broadcast', section_id),
|
||||
node = opt ? this.map.findElement('id', opt[0].cbid(section_id)) : null,
|
||||
addr = node ? calculateBroadcast(this.section, false) : null;
|
||||
|
||||
if (node != null) {
|
||||
if (addr != null)
|
||||
node.querySelector('input').setAttribute('placeholder', addr);
|
||||
else
|
||||
node.querySelector('input').removeAttribute('placeholder');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return network.registerProtocol('static', {
|
||||
CBIIPValue: form.Value.extend({
|
||||
handleSwitch: function(section_id, option_index, ev) {
|
||||
var maskopt = this.map.lookupOption('netmask', section_id);
|
||||
|
||||
if (maskopt == null || !this.isValid(section_id))
|
||||
return;
|
||||
|
||||
var maskval = maskopt[0].formvalue(section_id),
|
||||
addrval = this.formvalue(section_id),
|
||||
prefix = maskval ? network.maskToPrefix(maskval) : 32;
|
||||
|
||||
if (prefix == null)
|
||||
return;
|
||||
|
||||
this.datatype = 'or(cidr4,ipmask4)';
|
||||
|
||||
var parent = L.dom.parent(ev.target, '.cbi-value-field');
|
||||
L.dom.content(parent, form.DynamicList.prototype.renderWidget.apply(this, [
|
||||
section_id,
|
||||
option_index,
|
||||
addrval ? '%s/%d'.format(addrval, prefix) : ''
|
||||
]));
|
||||
|
||||
var masknode = this.map.findElement('id', maskopt[0].cbid(section_id));
|
||||
if (masknode) {
|
||||
parent = L.dom.parent(masknode, '.cbi-value');
|
||||
parent.parentNode.removeChild(parent);
|
||||
}
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var maskopt = this.map.lookupOption('netmask', section_id),
|
||||
widget = isCIDR(cfgvalue) ? 'DynamicList' : 'Value';
|
||||
|
||||
if (widget == 'DynamicList') {
|
||||
this.datatype = 'or(cidr4,ipmask4)';
|
||||
this.placeholder = _('Add IPv4 address…');
|
||||
}
|
||||
else {
|
||||
this.datatype = 'ip4addr("nomask")';
|
||||
}
|
||||
|
||||
var node = form[widget].prototype.renderWidget.apply(this, [ section_id, option_index, cfgvalue ]);
|
||||
|
||||
if (widget == 'Value')
|
||||
L.dom.append(node, E('button', {
|
||||
'class': 'cbi-button cbi-button-neutral',
|
||||
'title': _('Switch to CIDR list notation'),
|
||||
'aria-label': _('Switch to CIDR list notation'),
|
||||
'click': L.bind(this.handleSwitch, this, section_id, option_index)
|
||||
}, '…'));
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
validate: validateBroadcast
|
||||
}),
|
||||
|
||||
CBINetmaskValue: form.Value.extend({
|
||||
render: function(option_index, section_id, in_table) {
|
||||
var addropt = this.section.children.filter(function(o) { return o.option == 'ipaddr' })[0],
|
||||
addrval = addropt ? addropt.cfgvalue(section_id) : null;
|
||||
|
||||
if (addrval != null && isCIDR(addrval))
|
||||
return E([]);
|
||||
|
||||
this.value('255.255.255.0');
|
||||
this.value('255.255.0.0');
|
||||
this.value('255.0.0.0');
|
||||
|
||||
return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
|
||||
},
|
||||
|
||||
validate: validateBroadcast
|
||||
}),
|
||||
|
||||
CBIGatewayValue: form.Value.extend({
|
||||
datatype: 'ip4addr("nomask")',
|
||||
|
||||
render: function(option_index, section_id, in_table) {
|
||||
return network.getWANNetworks().then(L.bind(function(wans) {
|
||||
if (wans.length == 1) {
|
||||
var gwaddr = wans[0].getGatewayAddr();
|
||||
this.placeholder = gwaddr ? '%s (%s)'.format(gwaddr, wans[0].getName()) : '';
|
||||
}
|
||||
|
||||
return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
|
||||
}, this));
|
||||
},
|
||||
|
||||
validate: function(section_id, value) {
|
||||
var addropt = this.section.children.filter(function(o) { return o.option == 'ipaddr' })[0],
|
||||
addrval = addropt ? L.toArray(addropt.cfgvalue(section_id)) : null;
|
||||
|
||||
if (addrval != null) {
|
||||
for (var i = 0; i < addrval.length; i++) {
|
||||
var addr = addrval[i].split('/')[0];
|
||||
if (value == addr)
|
||||
return _('The gateway address must not be a local IP address');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}),
|
||||
|
||||
CBIBroadcastValue: form.Value.extend({
|
||||
datatype: 'ip4addr("nomask")',
|
||||
|
||||
render: function(option_index, section_id, in_table) {
|
||||
this.placeholder = calculateBroadcast(this.section, true);
|
||||
return form.Value.prototype.render.apply(this, [ option_index, section_id, in_table ]);
|
||||
}
|
||||
}),
|
||||
|
||||
getI18n: function() {
|
||||
return _('Static address');
|
||||
},
|
||||
|
||||
renderFormOptions: function(s) {
|
||||
var o;
|
||||
|
||||
s.taboption('general', this.CBIIPValue, 'ipaddr', _('IPv4 address'));
|
||||
s.taboption('general', this.CBINetmaskValue, 'netmask', _('IPv4 netmask'));
|
||||
s.taboption('general', this.CBIGatewayValue, 'gateway', _('IPv4 gateway'));
|
||||
s.taboption('general', this.CBIBroadcastValue, 'broadcast', _('IPv4 broadcast'));
|
||||
|
||||
o = s.taboption('general', form.DynamicList, 'ip6addr', _('IPv6 address'));
|
||||
o.datatype = 'ip6addr';
|
||||
o.placeholder = _('Add IPv6 address…');
|
||||
o.depends('ip6assign', '');
|
||||
|
||||
o = s.taboption('general', form.Value, 'ip6gw', _('IPv6 gateway'));
|
||||
o.datatype = 'ip6addr("nomask")';
|
||||
o.depends('ip6assign', '');
|
||||
|
||||
o = s.taboption('general', form.Value, 'ip6prefix', _('IPv6 routed prefix'), _('Public prefix routed to this device for distribution to clients.'));
|
||||
o.datatype = 'ip6addr';
|
||||
o.depends('ip6assign', '');
|
||||
}
|
||||
});
|
@ -0,0 +1,485 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
'require request';
|
||||
|
||||
var rpcRequestID = 1,
|
||||
rpcSessionID = L.env.sessionid || '00000000000000000000000000000000',
|
||||
rpcBaseURL = L.url('admin/ubus'),
|
||||
rpcInterceptorFns = [];
|
||||
|
||||
/**
|
||||
* @class rpc
|
||||
* @memberof LuCI
|
||||
* @hideconstructor
|
||||
* @classdesc
|
||||
*
|
||||
* The `LuCI.rpc` class provides high level ubus JSON-RPC abstractions
|
||||
* and means for listing and invoking remove RPC methods.
|
||||
*/
|
||||
return baseclass.extend(/** @lends LuCI.rpc.prototype */ {
|
||||
/* privates */
|
||||
call: function(req, cb, nobatch) {
|
||||
var q = '';
|
||||
|
||||
if (Array.isArray(req)) {
|
||||
if (req.length == 0)
|
||||
return Promise.resolve([]);
|
||||
|
||||
for (var i = 0; i < req.length; i++)
|
||||
if (req[i].params)
|
||||
q += '%s%s.%s'.format(
|
||||
q ? ';' : '/',
|
||||
req[i].params[1],
|
||||
req[i].params[2]
|
||||
);
|
||||
}
|
||||
|
||||
return request.post(rpcBaseURL + q, req, {
|
||||
timeout: (L.env.rpctimeout || 20) * 1000,
|
||||
nobatch: nobatch,
|
||||
credentials: true
|
||||
}).then(cb, cb);
|
||||
},
|
||||
|
||||
parseCallReply: function(req, res) {
|
||||
var msg = null;
|
||||
|
||||
if (res instanceof Error)
|
||||
return req.reject(res);
|
||||
|
||||
try {
|
||||
if (!res.ok)
|
||||
L.raise('RPCError', 'RPC call to %s/%s failed with HTTP error %d: %s',
|
||||
req.object, req.method, res.status, res.statusText || '?');
|
||||
|
||||
msg = res.json();
|
||||
}
|
||||
catch (e) {
|
||||
return req.reject(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* The interceptor args are intentionally swapped.
|
||||
* Response is passed as first arg to align with Request class interceptors
|
||||
*/
|
||||
Promise.all(rpcInterceptorFns.map(function(fn) { return fn(msg, req) }))
|
||||
.then(this.handleCallReply.bind(this, req, msg))
|
||||
.catch(req.reject);
|
||||
},
|
||||
|
||||
handleCallReply: function(req, msg) {
|
||||
var type = Object.prototype.toString,
|
||||
ret = null;
|
||||
|
||||
try {
|
||||
/* verify message frame */
|
||||
if (!L.isObject(msg) || msg.jsonrpc != '2.0')
|
||||
L.raise('RPCError', 'RPC call to %s/%s returned invalid message frame',
|
||||
req.object, req.method);
|
||||
|
||||
/* check error condition */
|
||||
if (L.isObject(msg.error) && msg.error.code && msg.error.message)
|
||||
L.raise('RPCError', 'RPC call to %s/%s failed with error %d: %s',
|
||||
req.object, req.method, msg.error.code, msg.error.message || '?');
|
||||
}
|
||||
catch (e) {
|
||||
return req.reject(e);
|
||||
}
|
||||
|
||||
if (!req.object && !req.method) {
|
||||
ret = msg.result;
|
||||
}
|
||||
else if (Array.isArray(msg.result)) {
|
||||
if (req.raise && msg.result[0] !== 0)
|
||||
L.raise('RPCError', 'RPC call to %s/%s failed with ubus code %d: %s',
|
||||
req.object, req.method, msg.result[0], this.getStatusText(msg.result[0]));
|
||||
|
||||
ret = (msg.result.length > 1) ? msg.result[1] : msg.result[0];
|
||||
}
|
||||
|
||||
if (req.expect) {
|
||||
for (var key in req.expect) {
|
||||
if (ret != null && key != '')
|
||||
ret = ret[key];
|
||||
|
||||
if (ret == null || type.call(ret) != type.call(req.expect[key]))
|
||||
ret = req.expect[key];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* apply filter */
|
||||
if (typeof(req.filter) == 'function') {
|
||||
req.priv[0] = ret;
|
||||
req.priv[1] = req.params;
|
||||
ret = req.filter.apply(this, req.priv);
|
||||
}
|
||||
|
||||
req.resolve(ret);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lists available remote ubus objects or the method signatures of
|
||||
* specific objects.
|
||||
*
|
||||
* This function has two signatures and is sensitive to the number of
|
||||
* arguments passed to it:
|
||||
* - `list()` -
|
||||
* Returns an array containing the names of all remote `ubus` objects
|
||||
* - `list("objname", ...)`
|
||||
* Returns method signatures for each given `ubus` object name.
|
||||
*
|
||||
* @param {...string} [objectNames]
|
||||
* If any object names are given, this function will return the method
|
||||
* signatures of each given object.
|
||||
*
|
||||
* @returns {Promise<Array<string>|Object<string, Object<string, Object<string, string>>>>}
|
||||
* When invoked without arguments, this function will return a promise
|
||||
* resolving to an array of `ubus` object names. When invoked with one or
|
||||
* more arguments, a promise resolving to an object describing the method
|
||||
* signatures of each requested `ubus` object name will be returned.
|
||||
*/
|
||||
list: function() {
|
||||
var msg = {
|
||||
jsonrpc: '2.0',
|
||||
id: rpcRequestID++,
|
||||
method: 'list',
|
||||
params: arguments.length ? this.varargs(arguments) : undefined
|
||||
};
|
||||
|
||||
return new Promise(L.bind(function(resolveFn, rejectFn) {
|
||||
/* store request info */
|
||||
var req = {
|
||||
resolve: resolveFn,
|
||||
reject: rejectFn
|
||||
};
|
||||
|
||||
/* call rpc */
|
||||
this.call(msg, this.parseCallReply.bind(this, req));
|
||||
}, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* @typedef {Object} DeclareOptions
|
||||
* @memberof LuCI.rpc
|
||||
*
|
||||
* @property {string} object
|
||||
* The name of the remote `ubus` object to invoke.
|
||||
*
|
||||
* @property {string} method
|
||||
* The name of the remote `ubus` method to invoke.
|
||||
*
|
||||
* @property {string[]} [params]
|
||||
* Lists the named parameters expected by the remote `ubus` RPC method.
|
||||
* The arguments passed to the resulting generated method call function
|
||||
* will be mapped to named parameters in the order they appear in this
|
||||
* array.
|
||||
*
|
||||
* Extraneous parameters passed to the generated function will not be
|
||||
* sent to the remote procedure but are passed to the
|
||||
* {@link LuCI.rpc~filterFn filter function} if one is specified.
|
||||
*
|
||||
* Examples:
|
||||
* - `params: [ "foo", "bar" ]` -
|
||||
* When the resulting call function is invoked with `fn(true, false)`,
|
||||
* the corresponding args object sent to the remote procedure will be
|
||||
* `{ foo: true, bar: false }`.
|
||||
* - `params: [ "test" ], filter: function(reply, args, extra) { ... }` -
|
||||
* When the resultung generated function is invoked with
|
||||
* `fn("foo", "bar", "baz")` then `{ "test": "foo" }` will be sent as
|
||||
* argument to the remote procedure and the filter function will be
|
||||
* invoked with `filterFn(reply, [ "foo" ], "bar", "baz")`
|
||||
*
|
||||
* @property {Object<string,*>} [expect]
|
||||
* Describes the expected return data structure. The given object is
|
||||
* supposed to contain a single key selecting the value to use from
|
||||
* the returned `ubus` reply object. The value of the sole key within
|
||||
* the `expect` object is used to infer the expected type of the received
|
||||
* `ubus` reply data.
|
||||
*
|
||||
* If the received data does not contain `expect`'s key, or if the
|
||||
* type of the data differs from the type of the value in the expect
|
||||
* object, the expect object's value is returned as default instead.
|
||||
*
|
||||
* The key in the `expect` object may be an empty string (`''`) in which
|
||||
* case the entire reply object is selected instead of one of its subkeys.
|
||||
*
|
||||
* If the `expect` option is omitted, the received reply will be returned
|
||||
* as-is, regardless of its format or type.
|
||||
*
|
||||
* Examples:
|
||||
* - `expect: { '': { error: 'Invalid response' } }` -
|
||||
* This requires the entire `ubus` reply to be a plain JavaScript
|
||||
* object. If the reply isn't an object but e.g. an array or a numeric
|
||||
* error code instead, it will get replaced with
|
||||
* `{ error: 'Invalid response' }` instead.
|
||||
* - `expect: { results: [] }` -
|
||||
* This requires the received `ubus` reply to be an object containing
|
||||
* a key `results` with an array as value. If the received reply does
|
||||
* not contain such a key, or if `reply.results` points to a non-array
|
||||
* value, the empty array (`[]`) will be used instead.
|
||||
* - `expect: { success: false }` -
|
||||
* This requires the received `ubus` reply to be an object containing
|
||||
* a key `success` with a boolean value. If the reply does not contain
|
||||
* `success` or if `reply.success` is not a boolean value, `false` will
|
||||
* be returned as default instead.
|
||||
*
|
||||
* @property {LuCI.rpc~filterFn} [filter]
|
||||
* Specfies an optional filter function which is invoked to transform the
|
||||
* received reply data before it is returned to the caller.
|
||||
*
|
||||
* @property {boolean} [reject=false]
|
||||
* If set to `true`, non-zero ubus call status codes are treated as fatal
|
||||
* error and lead to the rejection of the call promise. The default
|
||||
* behaviour is to resolve with the call return code value instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The filter function is invoked to transform a received `ubus` RPC call
|
||||
* reply before returning it to the caller.
|
||||
*
|
||||
* @callback LuCI.rpc~filterFn
|
||||
*
|
||||
* @param {*} data
|
||||
* The received `ubus` reply data or a subset of it as described in the
|
||||
* `expect` option of the RPC call declaration. In case of remote call
|
||||
* errors, `data` is numeric `ubus` error code instead.
|
||||
*
|
||||
* @param {Array<*>} args
|
||||
* The arguments the RPC method has been invoked with.
|
||||
*
|
||||
* @param {...*} extraArgs
|
||||
* All extraneous arguments passed to the RPC method exceeding the number
|
||||
* of arguments describes in the RPC call declaration.
|
||||
*
|
||||
* @return {*}
|
||||
* The return value of the filter function will be returned to the caller
|
||||
* of the RPC method as-is.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The generated invocation function is returned by
|
||||
* {@link LuCI.rpc#declare rpc.declare()} and encapsulates a single
|
||||
* RPC method call.
|
||||
*
|
||||
* Calling this function will execute a remote `ubus` HTTP call request
|
||||
* using the arguments passed to it as arguments and return a promise
|
||||
* resolving to the received reply values.
|
||||
*
|
||||
* @callback LuCI.rpc~invokeFn
|
||||
*
|
||||
* @param {...*} params
|
||||
* The parameters to pass to the remote procedure call. The given
|
||||
* positional arguments will be named to named RPC parameters according
|
||||
* to the names specified in the `params` array of the method declaration.
|
||||
*
|
||||
* Any additional parameters exceeding the amount of arguments in the
|
||||
* `params` declaration are passed as private extra arguments to the
|
||||
* declared filter function.
|
||||
*
|
||||
* @return {Promise<*>}
|
||||
* Returns a promise resolving to the result data of the remote `ubus`
|
||||
* RPC method invocation, optionally substituted and filtered according
|
||||
* to the `expect` and `filter` declarations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Describes a remote RPC call procedure and returns a function
|
||||
* implementing it.
|
||||
*
|
||||
* @param {LuCI.rpc.DeclareOptions} options
|
||||
* If any object names are given, this function will return the method
|
||||
* signatures of each given object.
|
||||
*
|
||||
* @returns {LuCI.rpc~invokeFn}
|
||||
* Returns a new function implementing the method call described in
|
||||
* `options`.
|
||||
*/
|
||||
declare: function(options) {
|
||||
return Function.prototype.bind.call(function(rpc, options) {
|
||||
var args = this.varargs(arguments, 2);
|
||||
return new Promise(function(resolveFn, rejectFn) {
|
||||
/* build parameter object */
|
||||
var p_off = 0;
|
||||
var params = { };
|
||||
if (Array.isArray(options.params))
|
||||
for (p_off = 0; p_off < options.params.length; p_off++)
|
||||
params[options.params[p_off]] = args[p_off];
|
||||
|
||||
/* all remaining arguments are private args */
|
||||
var priv = [ undefined, undefined ];
|
||||
for (; p_off < args.length; p_off++)
|
||||
priv.push(args[p_off]);
|
||||
|
||||
/* store request info */
|
||||
var req = {
|
||||
expect: options.expect,
|
||||
filter: options.filter,
|
||||
resolve: resolveFn,
|
||||
reject: rejectFn,
|
||||
params: params,
|
||||
priv: priv,
|
||||
object: options.object,
|
||||
method: options.method,
|
||||
raise: options.reject
|
||||
};
|
||||
|
||||
/* build message object */
|
||||
var msg = {
|
||||
jsonrpc: '2.0',
|
||||
id: rpcRequestID++,
|
||||
method: 'call',
|
||||
params: [
|
||||
rpcSessionID,
|
||||
options.object,
|
||||
options.method,
|
||||
params
|
||||
]
|
||||
};
|
||||
|
||||
/* call rpc */
|
||||
rpc.call(msg, rpc.parseCallReply.bind(rpc, req), options.nobatch);
|
||||
});
|
||||
}, this, this, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current RPC session id.
|
||||
*
|
||||
* @returns {string}
|
||||
* Returns the 32 byte session ID string used for authenticating remote
|
||||
* requests.
|
||||
*/
|
||||
getSessionID: function() {
|
||||
return rpcSessionID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the RPC session id to use.
|
||||
*
|
||||
* @param {string} sid
|
||||
* Sets the 32 byte session ID string used for authenticating remote
|
||||
* requests.
|
||||
*/
|
||||
setSessionID: function(sid) {
|
||||
rpcSessionID = sid;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current RPC base URL.
|
||||
*
|
||||
* @returns {string}
|
||||
* Returns the RPC URL endpoint to issue requests against.
|
||||
*/
|
||||
getBaseURL: function() {
|
||||
return rpcBaseURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the RPC base URL to use.
|
||||
*
|
||||
* @param {string} sid
|
||||
* Sets the RPC URL endpoint to issue requests against.
|
||||
*/
|
||||
setBaseURL: function(url) {
|
||||
rpcBaseURL = url;
|
||||
},
|
||||
|
||||
/**
|
||||
* Translates a numeric `ubus` error code into a human readable
|
||||
* description.
|
||||
*
|
||||
* @param {number} statusCode
|
||||
* The numeric status code.
|
||||
*
|
||||
* @returns {string}
|
||||
* Returns the textual description of the code.
|
||||
*/
|
||||
getStatusText: function(statusCode) {
|
||||
switch (statusCode) {
|
||||
case 0: return _('Command OK');
|
||||
case 1: return _('Invalid command');
|
||||
case 2: return _('Invalid argument');
|
||||
case 3: return _('Method not found');
|
||||
case 4: return _('Resource not found');
|
||||
case 5: return _('No data received');
|
||||
case 6: return _('Permission denied');
|
||||
case 7: return _('Request timeout');
|
||||
case 8: return _('Not supported');
|
||||
case 9: return _('Unspecified error');
|
||||
case 10: return _('Connection lost');
|
||||
default: return _('Unknown error code');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Registered interceptor functions are invoked before the standard reply
|
||||
* parsing and handling logic.
|
||||
*
|
||||
* By returning rejected promises, interceptor functions can cause the
|
||||
* invocation function to fail, regardless of the received reply.
|
||||
*
|
||||
* Interceptors may also modify their message argument in-place to
|
||||
* rewrite received replies before they're processed by the standard
|
||||
* response handling code.
|
||||
*
|
||||
* A common use case for such functions is to detect failing RPC replies
|
||||
* due to expired authentication in order to trigger a new login.
|
||||
*
|
||||
* @callback LuCI.rpc~interceptorFn
|
||||
*
|
||||
* @param {*} msg
|
||||
* The unprocessed, JSON decoded remote RPC method call reply.
|
||||
*
|
||||
* Since interceptors run before the standard parsing logic, the reply
|
||||
* data is not verified for correctness or filtered according to
|
||||
* `expect` and `filter` specifications in the declarations.
|
||||
*
|
||||
* @param {Object} req
|
||||
* The related request object which is an extended variant of the
|
||||
* declaration object, allowing access to internals of the invocation
|
||||
* function such as `filter`, `expect` or `params` values.
|
||||
*
|
||||
* @return {Promise<*>|*}
|
||||
* Interceptor functions may return a promise to defer response
|
||||
* processing until some delayed work completed. Any values the returned
|
||||
* promise resolves to are ignored.
|
||||
*
|
||||
* When the returned promise rejects with an error, the invocation
|
||||
* function will fail too, forwarding the error to the caller.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers a new interceptor function.
|
||||
*
|
||||
* @param {LuCI.rpc~interceptorFn} interceptorFn
|
||||
* The inteceptor function to register.
|
||||
*
|
||||
* @returns {LuCI.rpc~interceptorFn}
|
||||
* Returns the given function value.
|
||||
*/
|
||||
addInterceptor: function(interceptorFn) {
|
||||
if (typeof(interceptorFn) == 'function')
|
||||
rpcInterceptorFns.push(interceptorFn);
|
||||
return interceptorFn;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a registered interceptor function.
|
||||
*
|
||||
* @param {LuCI.rpc~interceptorFn} interceptorFn
|
||||
* The inteceptor function to remove.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Returns `true` if the given function has been removed or `false`
|
||||
* if it has not been found.
|
||||
*/
|
||||
removeInterceptor: function(interceptorFn) {
|
||||
var oldlen = rpcInterceptorFns.length, i = oldlen;
|
||||
while (i--)
|
||||
if (rpcInterceptorFns[i] === interceptorFn)
|
||||
rpcInterceptorFns.splice(i, 1);
|
||||
return (rpcInterceptorFns.length < oldlen);
|
||||
}
|
||||
});
|
@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
var s = [0x0000, 0x0000, 0x0000, 0x0000];
|
||||
|
||||
function mul(a, b) {
|
||||
var r = [0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000];
|
||||
|
||||
for (var j = 0; j < 4; j++) {
|
||||
var k = 0;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var t = a[i] * b[j] + r[i+j] + k;
|
||||
r[i+j] = t & 0xffff;
|
||||
k = t >>> 16;
|
||||
}
|
||||
r[j+4] = k;
|
||||
}
|
||||
|
||||
r.length = 4;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function add(a, n) {
|
||||
var r = [0x0000, 0x0000, 0x0000, 0x0000],
|
||||
k = n;
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var t = a[i] + k;
|
||||
r[i] = t & 0xffff;
|
||||
k = t >>> 16;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function shr(a, n) {
|
||||
var r = [a[0], a[1], a[2], a[3], 0x0000],
|
||||
i = 4,
|
||||
k = 0;
|
||||
|
||||
for (; n > 16; n -= 16, i--)
|
||||
for (var j = 0; j < 4; j++)
|
||||
r[j] = r[j+1];
|
||||
|
||||
for (; i > 0; i--) {
|
||||
var s = r[i-1];
|
||||
r[i-1] = (s >>> n) | k;
|
||||
k = ((s & ((1 << n) - 1)) << (16 - n));
|
||||
}
|
||||
|
||||
r.length = 4;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
return L.Class.extend({
|
||||
seed: function(n) {
|
||||
n = (n - 1)|0;
|
||||
s[0] = n & 0xffff;
|
||||
s[1] = n >>> 16;
|
||||
s[2] = 0;
|
||||
s[3] = 0;
|
||||
},
|
||||
|
||||
int: function() {
|
||||
s = mul(s, [0x7f2d, 0x4c95, 0xf42d, 0x5851]);
|
||||
s = add(s, 1);
|
||||
|
||||
var r = shr(s, 33);
|
||||
return (r[1] << 16) | r[0];
|
||||
},
|
||||
|
||||
get: function() {
|
||||
var r = (this.int() % 0x7fffffff) / 0x7fffffff, l, u;
|
||||
|
||||
switch (arguments.length) {
|
||||
case 0:
|
||||
return r;
|
||||
|
||||
case 1:
|
||||
l = 1;
|
||||
u = arguments[0]|0;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
l = arguments[0]|0;
|
||||
u = arguments[1]|0;
|
||||
break;
|
||||
}
|
||||
|
||||
return Math.floor(r * (u - l + 1)) + l;
|
||||
},
|
||||
|
||||
derive_color: function(string) {
|
||||
this.seed(parseInt(sfh(string), 16));
|
||||
|
||||
var r = this.get(128),
|
||||
g = this.get(128),
|
||||
min = 0,
|
||||
max = 128;
|
||||
|
||||
if ((r + g) < 128)
|
||||
min = 128 - r - g;
|
||||
else
|
||||
max = 255 - r - g;
|
||||
|
||||
var b = min + Math.floor(this.get() * (max - min));
|
||||
|
||||
return '#%02x%02x%02x'.format(0xff - r, 0xff - g, 0xff - b);
|
||||
}
|
||||
});
|
@ -0,0 +1,629 @@
|
||||
'use strict';
|
||||
'require ui';
|
||||
'require form';
|
||||
'require network';
|
||||
'require firewall';
|
||||
'require fs';
|
||||
|
||||
function getUsers() {
|
||||
return fs.lines('/etc/passwd').then(function(lines) {
|
||||
return lines.map(function(line) { return line.split(/:/)[0] });
|
||||
});
|
||||
}
|
||||
|
||||
function getGroups() {
|
||||
return fs.lines('/etc/group').then(function(lines) {
|
||||
return lines.map(function(line) { return line.split(/:/)[0] });
|
||||
});
|
||||
}
|
||||
|
||||
var CBIZoneSelect = form.ListValue.extend({
|
||||
__name__: 'CBI.ZoneSelect',
|
||||
|
||||
load: function(section_id) {
|
||||
return Promise.all([ firewall.getZones(), network.getNetworks() ]).then(L.bind(function(zn) {
|
||||
this.zones = zn[0];
|
||||
this.networks = zn[1];
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
filter: function(section_id, value) {
|
||||
return true;
|
||||
},
|
||||
|
||||
lookupZone: function(name) {
|
||||
return this.zones.filter(function(zone) { return zone.getName() == name })[0];
|
||||
},
|
||||
|
||||
lookupNetwork: function(name) {
|
||||
return this.networks.filter(function(network) { return network.getName() == name })[0];
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
|
||||
isOutputOnly = false,
|
||||
choices = {};
|
||||
|
||||
if (this.option == 'dest') {
|
||||
for (var i = 0; i < this.section.children.length; i++) {
|
||||
var opt = this.section.children[i];
|
||||
if (opt.option == 'src') {
|
||||
var val = opt.cfgvalue(section_id) || opt.default;
|
||||
isOutputOnly = (val == null || val == '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.title = isOutputOnly ? _('Output zone') : _('Destination zone');
|
||||
}
|
||||
|
||||
if (this.allowlocal) {
|
||||
choices[''] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': firewall.getZoneColorStyle(null)
|
||||
}, [
|
||||
E('strong', _('Device')),
|
||||
(this.allowany || this.allowlocal)
|
||||
? E('span', ' (%s)'.format(this.option != 'dest' ? _('output') : _('input'))) : ''
|
||||
]);
|
||||
}
|
||||
else if (!this.multiple && (this.rmempty || this.optional)) {
|
||||
choices[''] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': firewall.getZoneColorStyle(null)
|
||||
}, E('em', _('unspecified')));
|
||||
}
|
||||
|
||||
if (this.allowany) {
|
||||
choices['*'] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': firewall.getZoneColorStyle(null)
|
||||
}, [
|
||||
E('strong', _('Any zone')),
|
||||
(this.allowany && this.allowlocal && !isOutputOnly) ? E('span', ' (%s)'.format(_('forward'))) : ''
|
||||
]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.zones.length; i++) {
|
||||
var zone = this.zones[i],
|
||||
name = zone.getName(),
|
||||
networks = zone.getNetworks(),
|
||||
ifaces = [];
|
||||
|
||||
if (!this.filter(section_id, name))
|
||||
continue;
|
||||
|
||||
for (var j = 0; j < networks.length; j++) {
|
||||
var network = this.lookupNetwork(networks[j]);
|
||||
|
||||
if (!network)
|
||||
continue;
|
||||
|
||||
var span = E('span', {
|
||||
'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
|
||||
}, network.getName() + ': ');
|
||||
|
||||
var devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
|
||||
|
||||
for (var k = 0; k < devices.length; k++) {
|
||||
span.appendChild(E('img', {
|
||||
'title': devices[k].getI18n(),
|
||||
'src': L.resource('icons/%s%s.png'.format(devices[k].getType(), devices[k].isUp() ? '' : '_disabled'))
|
||||
}));
|
||||
}
|
||||
|
||||
if (!devices.length)
|
||||
span.appendChild(E('em', _('(empty)')));
|
||||
|
||||
ifaces.push(span);
|
||||
}
|
||||
|
||||
if (!ifaces.length)
|
||||
ifaces.push(E('em', _('(empty)')));
|
||||
|
||||
choices[name] = E('span', {
|
||||
'class': 'zonebadge',
|
||||
'style': firewall.getZoneColorStyle(zone)
|
||||
}, [ E('strong', name) ].concat(ifaces));
|
||||
}
|
||||
|
||||
var widget = new ui.Dropdown(values, choices, {
|
||||
id: this.cbid(section_id),
|
||||
sort: true,
|
||||
multiple: this.multiple,
|
||||
optional: this.optional || this.rmempty,
|
||||
disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
|
||||
select_placeholder: E('em', _('unspecified')),
|
||||
display_items: this.display_size || this.size || 3,
|
||||
dropdown_items: this.dropdown_size || this.size || 5,
|
||||
validate: L.bind(this.validate, this, section_id),
|
||||
create: !this.nocreate,
|
||||
create_markup: '' +
|
||||
'<li data-value="{{value}}">' +
|
||||
'<span class="zonebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">' +
|
||||
'<strong>{{value}}:</strong> <em>('+_('create')+')</em>' +
|
||||
'</span>' +
|
||||
'</li>'
|
||||
});
|
||||
|
||||
var elem = widget.render();
|
||||
|
||||
if (this.option == 'src') {
|
||||
elem.addEventListener('cbi-dropdown-change', L.bind(function(ev) {
|
||||
var opt = this.map.lookupOption('dest', section_id),
|
||||
val = ev.detail.instance.getValue();
|
||||
|
||||
if (opt == null)
|
||||
return;
|
||||
|
||||
var cbid = opt[0].cbid(section_id),
|
||||
label = document.querySelector('label[for="widget.%s"]'.format(cbid)),
|
||||
node = document.getElementById(cbid);
|
||||
|
||||
L.dom.content(label, val == '' ? _('Output zone') : _('Destination zone'));
|
||||
|
||||
if (val == '') {
|
||||
if (L.dom.callClassMethod(node, 'getValue') == '')
|
||||
L.dom.callClassMethod(node, 'setValue', '*');
|
||||
|
||||
var emptyval = node.querySelector('[data-value=""]'),
|
||||
anyval = node.querySelector('[data-value="*"]');
|
||||
|
||||
L.dom.content(anyval.querySelector('span'), E('strong', _('Any zone')));
|
||||
|
||||
if (emptyval != null)
|
||||
emptyval.parentNode.removeChild(emptyval);
|
||||
}
|
||||
else {
|
||||
var anyval = node.querySelector('[data-value="*"]'),
|
||||
emptyval = node.querySelector('[data-value=""]');
|
||||
|
||||
if (emptyval == null) {
|
||||
emptyval = anyval.cloneNode(true);
|
||||
emptyval.removeAttribute('display');
|
||||
emptyval.removeAttribute('selected');
|
||||
emptyval.setAttribute('data-value', '');
|
||||
}
|
||||
|
||||
if (opt[0].allowlocal)
|
||||
L.dom.content(emptyval.querySelector('span'), [
|
||||
E('strong', _('Device')), E('span', ' (%s)'.format(_('input')))
|
||||
]);
|
||||
|
||||
L.dom.content(anyval.querySelector('span'), [
|
||||
E('strong', _('Any zone')), E('span', ' (%s)'.format(_('forward')))
|
||||
]);
|
||||
|
||||
anyval.parentNode.insertBefore(emptyval, anyval);
|
||||
}
|
||||
|
||||
}, this));
|
||||
}
|
||||
else if (isOutputOnly) {
|
||||
var emptyval = elem.querySelector('[data-value=""]');
|
||||
emptyval.parentNode.removeChild(emptyval);
|
||||
}
|
||||
|
||||
return elem;
|
||||
},
|
||||
});
|
||||
|
||||
var CBIZoneForwards = form.DummyValue.extend({
|
||||
__name__: 'CBI.ZoneForwards',
|
||||
|
||||
load: function(section_id) {
|
||||
return Promise.all([
|
||||
firewall.getDefaults(),
|
||||
firewall.getZones(),
|
||||
network.getNetworks(),
|
||||
network.getDevices()
|
||||
]).then(L.bind(function(dznd) {
|
||||
this.defaults = dznd[0];
|
||||
this.zones = dznd[1];
|
||||
this.networks = dznd[2];
|
||||
this.devices = dznd[3];
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
renderZone: function(zone) {
|
||||
var name = zone.getName(),
|
||||
networks = zone.getNetworks(),
|
||||
devices = zone.getDevices(),
|
||||
subnets = zone.getSubnets(),
|
||||
ifaces = [];
|
||||
|
||||
for (var j = 0; j < networks.length; j++) {
|
||||
var network = this.networks.filter(function(net) { return net.getName() == networks[j] })[0];
|
||||
|
||||
if (!network)
|
||||
continue;
|
||||
|
||||
var span = E('span', {
|
||||
'class': 'ifacebadge' + (network.getName() == this.network ? ' ifacebadge-active' : '')
|
||||
}, network.getName() + ': ');
|
||||
|
||||
var subdevs = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
|
||||
|
||||
for (var k = 0; k < subdevs.length && subdevs[k]; k++) {
|
||||
span.appendChild(E('img', {
|
||||
'title': subdevs[k].getI18n(),
|
||||
'src': L.resource('icons/%s%s.png'.format(subdevs[k].getType(), subdevs[k].isUp() ? '' : '_disabled'))
|
||||
}));
|
||||
}
|
||||
|
||||
if (!subdevs.length)
|
||||
span.appendChild(E('em', _('(empty)')));
|
||||
|
||||
ifaces.push(span);
|
||||
}
|
||||
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
var device = this.devices.filter(function(dev) { return dev.getName() == devices[i] })[0],
|
||||
title = device ? device.getI18n() : _('Absent Interface'),
|
||||
type = device ? device.getType() : 'ethernet',
|
||||
up = device ? device.isUp() : false;
|
||||
|
||||
ifaces.push(E('span', { 'class': 'ifacebadge' }, [
|
||||
E('img', {
|
||||
'title': title,
|
||||
'src': L.resource('icons/%s%s.png'.format(type, up ? '' : '_disabled'))
|
||||
}),
|
||||
device ? device.getName() : devices[i]
|
||||
]));
|
||||
}
|
||||
|
||||
if (subnets.length > 0)
|
||||
ifaces.push(E('span', { 'class': 'ifacebadge' }, [ '{ %s }'.format(subnets.join('; ')) ]));
|
||||
|
||||
if (!ifaces.length)
|
||||
ifaces.push(E('span', { 'class': 'ifacebadge' }, E('em', _('(empty)'))));
|
||||
|
||||
return E('label', {
|
||||
'class': 'zonebadge cbi-tooltip-container',
|
||||
'style': firewall.getZoneColorStyle(zone)
|
||||
}, [
|
||||
E('strong', name),
|
||||
E('div', { 'class': 'cbi-tooltip' }, ifaces)
|
||||
]);
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var value = (cfgvalue != null) ? cfgvalue : this.default,
|
||||
zone = this.zones.filter(function(z) { return z.getName() == value })[0];
|
||||
|
||||
if (!zone)
|
||||
return E([]);
|
||||
|
||||
var forwards = zone.getForwardingsBy('src'),
|
||||
dzones = [];
|
||||
|
||||
for (var i = 0; i < forwards.length; i++) {
|
||||
var dzone = forwards[i].getDestinationZone();
|
||||
|
||||
if (!dzone)
|
||||
continue;
|
||||
|
||||
dzones.push(this.renderZone(dzone));
|
||||
}
|
||||
|
||||
if (!dzones.length)
|
||||
dzones.push(E('label', { 'class': 'zonebadge zonebadge-empty' },
|
||||
E('strong', this.defaults.getForward())));
|
||||
|
||||
return E('div', { 'class': 'zone-forwards' }, [
|
||||
E('div', { 'class': 'zone-src' }, this.renderZone(zone)),
|
||||
E('span', '⇒'),
|
||||
E('div', { 'class': 'zone-dest' }, dzones)
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
var CBINetworkSelect = form.ListValue.extend({
|
||||
__name__: 'CBI.NetworkSelect',
|
||||
|
||||
load: function(section_id) {
|
||||
return network.getNetworks().then(L.bind(function(networks) {
|
||||
this.networks = networks;
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
filter: function(section_id, value) {
|
||||
return true;
|
||||
},
|
||||
|
||||
renderIfaceBadge: function(network) {
|
||||
var span = E('span', { 'class': 'ifacebadge' }, network.getName() + ': '),
|
||||
devices = network.isBridge() ? network.getDevices() : L.toArray(network.getDevice());
|
||||
|
||||
for (var j = 0; j < devices.length && devices[j]; j++) {
|
||||
span.appendChild(E('img', {
|
||||
'title': devices[j].getI18n(),
|
||||
'src': L.resource('icons/%s%s.png'.format(devices[j].getType(), devices[j].isUp() ? '' : '_disabled'))
|
||||
}));
|
||||
}
|
||||
|
||||
if (!devices.length) {
|
||||
span.appendChild(E('em', { 'class': 'hide-close' }, _('(no interfaces attached)')));
|
||||
span.appendChild(E('em', { 'class': 'hide-open' }, '-'));
|
||||
}
|
||||
|
||||
return span;
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
|
||||
choices = {},
|
||||
checked = {};
|
||||
|
||||
for (var i = 0; i < values.length; i++)
|
||||
checked[values[i]] = true;
|
||||
|
||||
values = [];
|
||||
|
||||
if (!this.multiple && (this.rmempty || this.optional))
|
||||
choices[''] = E('em', _('unspecified'));
|
||||
|
||||
for (var i = 0; i < this.networks.length; i++) {
|
||||
var network = this.networks[i],
|
||||
name = network.getName();
|
||||
|
||||
if (name == this.exclude || !this.filter(section_id, name))
|
||||
continue;
|
||||
|
||||
if (name == 'loopback' && !this.loopback)
|
||||
continue;
|
||||
|
||||
if (this.novirtual && network.isVirtual())
|
||||
continue;
|
||||
|
||||
if (checked[name])
|
||||
values.push(name);
|
||||
|
||||
choices[name] = this.renderIfaceBadge(network);
|
||||
}
|
||||
|
||||
var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
|
||||
id: this.cbid(section_id),
|
||||
sort: true,
|
||||
multiple: this.multiple,
|
||||
optional: this.optional || this.rmempty,
|
||||
disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
|
||||
select_placeholder: E('em', _('unspecified')),
|
||||
display_items: this.display_size || this.size || 3,
|
||||
dropdown_items: this.dropdown_size || this.size || 5,
|
||||
datatype: this.multiple ? 'list(uciname)' : 'uciname',
|
||||
validate: L.bind(this.validate, this, section_id),
|
||||
create: !this.nocreate,
|
||||
create_markup: '' +
|
||||
'<li data-value="{{value}}">' +
|
||||
'<span class="ifacebadge" style="background:repeating-linear-gradient(45deg,rgba(204,204,204,0.5),rgba(204,204,204,0.5) 5px,rgba(255,255,255,0.5) 5px,rgba(255,255,255,0.5) 10px)">' +
|
||||
'{{value}}: <em>('+_('create')+')</em>' +
|
||||
'</span>' +
|
||||
'</li>'
|
||||
});
|
||||
|
||||
return widget.render();
|
||||
},
|
||||
|
||||
textvalue: function(section_id) {
|
||||
var cfgvalue = this.cfgvalue(section_id),
|
||||
values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
|
||||
rv = E([]);
|
||||
|
||||
for (var i = 0; i < (this.networks || []).length; i++) {
|
||||
var network = this.networks[i],
|
||||
name = network.getName();
|
||||
|
||||
if (values.indexOf(name) == -1)
|
||||
continue;
|
||||
|
||||
if (rv.length)
|
||||
L.dom.append(rv, ' ');
|
||||
|
||||
L.dom.append(rv, this.renderIfaceBadge(network));
|
||||
}
|
||||
|
||||
if (!rv.firstChild)
|
||||
rv.appendChild(E('em', _('unspecified')));
|
||||
|
||||
return rv;
|
||||
},
|
||||
});
|
||||
|
||||
var CBIDeviceSelect = form.ListValue.extend({
|
||||
__name__: 'CBI.DeviceSelect',
|
||||
|
||||
load: function(section_id) {
|
||||
return Promise.all([
|
||||
network.getDevices(),
|
||||
this.noaliases ? null : network.getNetworks()
|
||||
]).then(L.bind(function(data) {
|
||||
this.devices = data[0];
|
||||
this.networks = data[1];
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
filter: function(section_id, value) {
|
||||
return true;
|
||||
},
|
||||
|
||||
renderWidget: function(section_id, option_index, cfgvalue) {
|
||||
var values = L.toArray((cfgvalue != null) ? cfgvalue : this.default),
|
||||
choices = {},
|
||||
checked = {},
|
||||
order = [];
|
||||
|
||||
for (var i = 0; i < values.length; i++)
|
||||
checked[values[i]] = true;
|
||||
|
||||
values = [];
|
||||
|
||||
if (!this.multiple && (this.rmempty || this.optional))
|
||||
choices[''] = E('em', _('unspecified'));
|
||||
|
||||
for (var i = 0; i < this.devices.length; i++) {
|
||||
var device = this.devices[i],
|
||||
name = device.getName(),
|
||||
type = device.getType();
|
||||
|
||||
if (name == 'lo' || name == this.exclude || !this.filter(section_id, name))
|
||||
continue;
|
||||
|
||||
if (this.noaliases && type == 'alias')
|
||||
continue;
|
||||
|
||||
if (this.nobridges && type == 'bridge')
|
||||
continue;
|
||||
|
||||
if (this.noinactive && device.isUp() == false)
|
||||
continue;
|
||||
|
||||
var item = E([
|
||||
E('img', {
|
||||
'title': device.getI18n(),
|
||||
'src': L.resource('icons/%s%s.png'.format(type, device.isUp() ? '' : '_disabled'))
|
||||
}),
|
||||
E('span', { 'class': 'hide-open' }, [ name ]),
|
||||
E('span', { 'class': 'hide-close'}, [ device.getI18n() ])
|
||||
]);
|
||||
|
||||
var networks = device.getNetworks();
|
||||
|
||||
if (networks.length > 0)
|
||||
L.dom.append(item.lastChild, [ ' (', networks.map(function(n) { return n.getName() }).join(', '), ')' ]);
|
||||
|
||||
if (checked[name])
|
||||
values.push(name);
|
||||
|
||||
choices[name] = item;
|
||||
order.push(name);
|
||||
}
|
||||
|
||||
if (this.networks != null) {
|
||||
for (var i = 0; i < this.networks.length; i++) {
|
||||
var net = this.networks[i],
|
||||
device = network.instantiateDevice('@%s'.format(net.getName()), net),
|
||||
name = device.getName();
|
||||
|
||||
if (name == '@loopback' || name == this.exclude || !this.filter(section_id, name))
|
||||
continue;
|
||||
|
||||
if (this.noinactive && net.isUp() == false)
|
||||
continue;
|
||||
|
||||
var item = E([
|
||||
E('img', {
|
||||
'title': device.getI18n(),
|
||||
'src': L.resource('icons/alias%s.png'.format(net.isUp() ? '' : '_disabled'))
|
||||
}),
|
||||
E('span', { 'class': 'hide-open' }, [ name ]),
|
||||
E('span', { 'class': 'hide-close'}, [ device.getI18n() ])
|
||||
]);
|
||||
|
||||
if (checked[name])
|
||||
values.push(name);
|
||||
|
||||
choices[name] = item;
|
||||
order.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.nocreate) {
|
||||
var keys = Object.keys(checked).sort(L.naturalCompare);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (choices.hasOwnProperty(keys[i]))
|
||||
continue;
|
||||
|
||||
choices[keys[i]] = E([
|
||||
E('img', {
|
||||
'title': _('Absent Interface'),
|
||||
'src': L.resource('icons/ethernet_disabled.png')
|
||||
}),
|
||||
E('span', { 'class': 'hide-open' }, [ keys[i] ]),
|
||||
E('span', { 'class': 'hide-close'}, [ '%s: "%h"'.format(_('Absent Interface'), keys[i]) ])
|
||||
]);
|
||||
|
||||
values.push(keys[i]);
|
||||
order.push(keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var widget = new ui.Dropdown(this.multiple ? values : values[0], choices, {
|
||||
id: this.cbid(section_id),
|
||||
sort: order,
|
||||
multiple: this.multiple,
|
||||
optional: this.optional || this.rmempty,
|
||||
disabled: (this.readonly != null) ? this.readonly : this.map.readonly,
|
||||
select_placeholder: E('em', _('unspecified')),
|
||||
display_items: this.display_size || this.size || 3,
|
||||
dropdown_items: this.dropdown_size || this.size || 5,
|
||||
validate: L.bind(this.validate, this, section_id),
|
||||
create: !this.nocreate,
|
||||
create_markup: '' +
|
||||
'<li data-value="{{value}}">' +
|
||||
'<img title="'+_('Custom Interface')+': "{{value}}"" src="'+L.resource('icons/ethernet_disabled.png')+'" />' +
|
||||
'<span class="hide-open">{{value}}</span>' +
|
||||
'<span class="hide-close">'+_('Custom Interface')+': "{{value}}"</span>' +
|
||||
'</li>'
|
||||
});
|
||||
|
||||
return widget.render();
|
||||
},
|
||||
});
|
||||
|
||||
var CBIUserSelect = form.ListValue.extend({
|
||||
__name__: 'CBI.UserSelect',
|
||||
|
||||
load: function(section_id) {
|
||||
return getUsers().then(L.bind(function(users) {
|
||||
delete this.keylist;
|
||||
delete this.vallist;
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
this.value(users[i]);
|
||||
}
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
filter: function(section_id, value) {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
var CBIGroupSelect = form.ListValue.extend({
|
||||
__name__: 'CBI.GroupSelect',
|
||||
|
||||
load: function(section_id) {
|
||||
return getGroups().then(L.bind(function(groups) {
|
||||
for (var i = 0; i < groups.length; i++) {
|
||||
this.value(groups[i]);
|
||||
}
|
||||
|
||||
return this.super('load', section_id);
|
||||
}, this));
|
||||
},
|
||||
|
||||
filter: function(section_id, value) {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
return L.Class.extend({
|
||||
ZoneSelect: CBIZoneSelect,
|
||||
ZoneForwards: CBIZoneForwards,
|
||||
NetworkSelect: CBINetworkSelect,
|
||||
DeviceSelect: CBIDeviceSelect,
|
||||
UserSelect: CBIUserSelect,
|
||||
GroupSelect: CBIGroupSelect,
|
||||
});
|
@ -0,0 +1,988 @@
|
||||
'use strict';
|
||||
'require rpc';
|
||||
'require baseclass';
|
||||
|
||||
function isEmpty(object, ignore) {
|
||||
for (var property in object)
|
||||
if (object.hasOwnProperty(property) && property != ignore)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class uci
|
||||
* @memberof LuCI
|
||||
* @hideconstructor
|
||||
* @classdesc
|
||||
*
|
||||
* The `LuCI.uci` class utilizes {@link LuCI.rpc} to declare low level
|
||||
* remote UCI `ubus` procedures and implements a local caching and data
|
||||
* manipulation layer on top to allow for synchroneous operations on
|
||||
* UCI configuration data.
|
||||
*/
|
||||
return baseclass.extend(/** @lends LuCI.uci.prototype */ {
|
||||
__init__: function() {
|
||||
this.state = {
|
||||
newidx: 0,
|
||||
values: { },
|
||||
creates: { },
|
||||
changes: { },
|
||||
deletes: { },
|
||||
reorder: { }
|
||||
};
|
||||
|
||||
this.loaded = {};
|
||||
},
|
||||
|
||||
callLoad: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'get',
|
||||
params: [ 'config' ],
|
||||
expect: { values: { } },
|
||||
reject: true
|
||||
}),
|
||||
|
||||
callOrder: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'order',
|
||||
params: [ 'config', 'sections' ],
|
||||
reject: true
|
||||
}),
|
||||
|
||||
callAdd: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'add',
|
||||
params: [ 'config', 'type', 'name', 'values' ],
|
||||
expect: { section: '' },
|
||||
reject: true
|
||||
}),
|
||||
|
||||
callSet: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'set',
|
||||
params: [ 'config', 'section', 'values' ],
|
||||
reject: true
|
||||
}),
|
||||
|
||||
callDelete: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'delete',
|
||||
params: [ 'config', 'section', 'options' ],
|
||||
reject: true
|
||||
}),
|
||||
|
||||
callApply: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'apply',
|
||||
params: [ 'timeout', 'rollback' ],
|
||||
reject: true
|
||||
}),
|
||||
|
||||
callConfirm: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'confirm',
|
||||
reject: true
|
||||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Generates a new, unique section ID for the given configuration.
|
||||
*
|
||||
* Note that the generated ID is temporary, it will get replaced by an
|
||||
* identifier in the form `cfgXXXXXX` once the configuration is saved
|
||||
* by the remote `ubus` UCI api.
|
||||
*
|
||||
* @param {string} config
|
||||
* The configuration to generate the new section ID for.
|
||||
*
|
||||
* @returns {string}
|
||||
* A newly generated, unique section ID in the form `newXXXXXX`
|
||||
* where `X` denotes a hexadecimal digit.
|
||||
*/
|
||||
createSID: function(conf) {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
sid;
|
||||
|
||||
do {
|
||||
sid = "new%06x".format(Math.random() * 0xFFFFFF);
|
||||
} while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid]));
|
||||
|
||||
return sid;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves a given section ID in extended notation to the internal
|
||||
* section ID value.
|
||||
*
|
||||
* @param {string} config
|
||||
* The configuration to resolve the section ID for.
|
||||
*
|
||||
* @param {string} sid
|
||||
* The section ID to resolve. If the ID is in the form `@typename[#]`,
|
||||
* it will get resolved to an internal anonymous ID in the forms
|
||||
* `cfgXXXXXX`/`newXXXXXX` or to the name of a section in case it points
|
||||
* to a named section. When the given ID is not in extended notation,
|
||||
* it will be returned as-is.
|
||||
*
|
||||
* @returns {string|null}
|
||||
* Returns the resolved section ID or the original given ID if it was
|
||||
* not in extended notation. Returns `null` when an extended ID could
|
||||
* not be resolved to existing section ID.
|
||||
*/
|
||||
resolveSID: function(conf, sid) {
|
||||
if (typeof(sid) != 'string')
|
||||
return sid;
|
||||
|
||||
var m = /^@([a-zA-Z0-9_-]+)\[(-?[0-9]+)\]$/.exec(sid);
|
||||
|
||||
if (m) {
|
||||
var type = m[1],
|
||||
pos = +m[2],
|
||||
sections = this.sections(conf, type),
|
||||
section = sections[pos >= 0 ? pos : sections.length + pos];
|
||||
|
||||
return section ? section['.name'] : null;
|
||||
}
|
||||
|
||||
return sid;
|
||||
},
|
||||
|
||||
/* private */
|
||||
reorderSections: function() {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
r = this.state.reorder,
|
||||
tasks = [];
|
||||
|
||||
if (Object.keys(r).length === 0)
|
||||
return Promise.resolve();
|
||||
|
||||
/*
|
||||
gather all created and existing sections, sort them according
|
||||
to their index value and issue an uci order call
|
||||
*/
|
||||
for (var c in r) {
|
||||
var o = [ ];
|
||||
|
||||
if (n[c])
|
||||
for (var s in n[c])
|
||||
o.push(n[c][s]);
|
||||
|
||||
for (var s in v[c])
|
||||
o.push(v[c][s]);
|
||||
|
||||
if (o.length > 0) {
|
||||
o.sort(function(a, b) {
|
||||
return (a['.index'] - b['.index']);
|
||||
});
|
||||
|
||||
var sids = [ ];
|
||||
|
||||
for (var i = 0; i < o.length; i++)
|
||||
sids.push(o[i]['.name']);
|
||||
|
||||
tasks.push(this.callOrder(c, sids));
|
||||
}
|
||||
}
|
||||
|
||||
this.state.reorder = { };
|
||||
return Promise.all(tasks);
|
||||
},
|
||||
|
||||
/* private */
|
||||
loadPackage: function(packageName) {
|
||||
if (this.loaded[packageName] == null)
|
||||
return (this.loaded[packageName] = this.callLoad(packageName));
|
||||
|
||||
return Promise.resolve(this.loaded[packageName]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the given UCI configurations from the remote `ubus` api.
|
||||
*
|
||||
* Loaded configurations are cached and only loaded once. Subsequent
|
||||
* load operations of the same configurations will return the cached
|
||||
* data.
|
||||
*
|
||||
* To force reloading a configuration, it has to be unloaded with
|
||||
* {@link LuCI.uci#unload uci.unload()} first.
|
||||
*
|
||||
* @param {string|string[]} config
|
||||
* The name of the configuration or an array of configuration
|
||||
* names to load.
|
||||
*
|
||||
* @returns {Promise<string[]>}
|
||||
* Returns a promise resolving to the names of the configurations
|
||||
* that have been successfully loaded.
|
||||
*/
|
||||
load: function(packages) {
|
||||
var self = this,
|
||||
pkgs = [ ],
|
||||
tasks = [];
|
||||
|
||||
if (!Array.isArray(packages))
|
||||
packages = [ packages ];
|
||||
|
||||
for (var i = 0; i < packages.length; i++)
|
||||
if (!self.state.values[packages[i]]) {
|
||||
pkgs.push(packages[i]);
|
||||
tasks.push(self.loadPackage(packages[i]));
|
||||
}
|
||||
|
||||
return Promise.all(tasks).then(function(responses) {
|
||||
for (var i = 0; i < responses.length; i++)
|
||||
self.state.values[pkgs[i]] = responses[i];
|
||||
|
||||
if (responses.length)
|
||||
document.dispatchEvent(new CustomEvent('uci-loaded'));
|
||||
|
||||
return pkgs;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Unloads the given UCI configurations from the local cache.
|
||||
*
|
||||
* @param {string|string[]} config
|
||||
* The name of the configuration or an array of configuration
|
||||
* names to unload.
|
||||
*/
|
||||
unload: function(packages) {
|
||||
if (!Array.isArray(packages))
|
||||
packages = [ packages ];
|
||||
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
delete this.state.values[packages[i]];
|
||||
delete this.state.creates[packages[i]];
|
||||
delete this.state.changes[packages[i]];
|
||||
delete this.state.deletes[packages[i]];
|
||||
|
||||
delete this.loaded[packages[i]];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new section of the given type to the given configuration,
|
||||
* optionally named according to the given name.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to add the section to.
|
||||
*
|
||||
* @param {string} type
|
||||
* The type of the section to add.
|
||||
*
|
||||
* @param {string} [name]
|
||||
* The name of the section to add. If the name is omitted, an anonymous
|
||||
* section will be added instead.
|
||||
*
|
||||
* @returns {string}
|
||||
* Returns the section ID of the newly added section which is equivalent
|
||||
* to the given name for non-anonymous sections.
|
||||
*/
|
||||
add: function(conf, type, name) {
|
||||
var n = this.state.creates,
|
||||
sid = name || this.createSID(conf);
|
||||
|
||||
if (!n[conf])
|
||||
n[conf] = { };
|
||||
|
||||
n[conf][sid] = {
|
||||
'.type': type,
|
||||
'.name': sid,
|
||||
'.create': name,
|
||||
'.anonymous': !name,
|
||||
'.index': 1000 + this.state.newidx++
|
||||
};
|
||||
|
||||
return sid;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the section with the given ID from the given configuration.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to remove the section from.
|
||||
*
|
||||
* @param {string} sid
|
||||
* The ID of the section to remove.
|
||||
*/
|
||||
remove: function(conf, sid) {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes;
|
||||
|
||||
/* requested deletion of a just created section */
|
||||
if (n[conf] && n[conf][sid]) {
|
||||
delete n[conf][sid];
|
||||
}
|
||||
else if (v[conf] && v[conf][sid]) {
|
||||
if (c[conf])
|
||||
delete c[conf][sid];
|
||||
|
||||
if (!d[conf])
|
||||
d[conf] = { };
|
||||
|
||||
d[conf][sid] = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A section object represents the options and their corresponding values
|
||||
* enclosed within a configuration section, as well as some additional
|
||||
* meta data such as sort indexes and internal ID.
|
||||
*
|
||||
* Any internal metadata fields are prefixed with a dot which is isn't
|
||||
* an allowed character for normal option names.
|
||||
*
|
||||
* @typedef {Object<string, boolean|number|string|string[]>} SectionObject
|
||||
* @memberof LuCI.uci
|
||||
*
|
||||
* @property {boolean} .anonymous
|
||||
* The `.anonymous` property specifies whether the configuration is
|
||||
* anonymous (`true`) or named (`false`).
|
||||
*
|
||||
* @property {number} .index
|
||||
* The `.index` property specifes the sort order of the section.
|
||||
*
|
||||
* @property {string} .name
|
||||
* The `.name` property holds the name of the section object. It may be
|
||||
* either an anonymous ID in the form `cfgXXXXXX` or `newXXXXXX` with `X`
|
||||
* being a hexadecimal digit or a string holding the name of the section.
|
||||
*
|
||||
* @property {string} .type
|
||||
* The `.type` property contains the type of the corresponding uci
|
||||
* section.
|
||||
*
|
||||
* @property {string|string[]} *
|
||||
* A section object may contain an arbitrary number of further properties
|
||||
* representing the uci option enclosed in the section.
|
||||
*
|
||||
* All option property names will be in the form `[A-Za-z0-9_]+` and
|
||||
* either contain a string value or an array of strings, in case the
|
||||
* underlying option is an UCI list.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The sections callback is invoked for each section found within
|
||||
* the given configuration and receives the section object and its
|
||||
* associated name as arguments.
|
||||
*
|
||||
* @callback LuCI.uci~sectionsFn
|
||||
*
|
||||
* @param {LuCI.uci.SectionObject} section
|
||||
* The section object.
|
||||
*
|
||||
* @param {string} sid
|
||||
* The name or ID of the section.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enumerates the sections of the given configuration, optionally
|
||||
* filtered by type.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to enumerate the sections for.
|
||||
*
|
||||
* @param {string} [type]
|
||||
* Enumerate only sections of the given type. If omitted, enumerate
|
||||
* all sections.
|
||||
*
|
||||
* @param {LuCI.uci~sectionsFn} [cb]
|
||||
* An optional callback to invoke for each enumerated section.
|
||||
*
|
||||
* @returns {Array<LuCI.uci.SectionObject>}
|
||||
* Returns a sorted array of the section objects within the given
|
||||
* configuration, filtered by type of a type has been specified.
|
||||
*/
|
||||
sections: function(conf, type, cb) {
|
||||
var sa = [ ],
|
||||
v = this.state.values[conf],
|
||||
n = this.state.creates[conf],
|
||||
c = this.state.changes[conf],
|
||||
d = this.state.deletes[conf];
|
||||
|
||||
if (!v)
|
||||
return sa;
|
||||
|
||||
for (var s in v)
|
||||
if (!d || d[s] !== true)
|
||||
if (!type || v[s]['.type'] == type)
|
||||
sa.push(Object.assign({ }, v[s], c ? c[s] : null));
|
||||
|
||||
if (n)
|
||||
for (var s in n)
|
||||
if (!type || n[s]['.type'] == type)
|
||||
sa.push(Object.assign({ }, n[s]));
|
||||
|
||||
sa.sort(function(a, b) {
|
||||
return a['.index'] - b['.index'];
|
||||
});
|
||||
|
||||
for (var i = 0; i < sa.length; i++)
|
||||
sa[i]['.index'] = i;
|
||||
|
||||
if (typeof(cb) == 'function')
|
||||
for (var i = 0; i < sa.length; i++)
|
||||
cb.call(this, sa[i], sa[i]['.name']);
|
||||
|
||||
return sa;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the value of the given option within the specified section
|
||||
* of the given configuration or the entire section object if the
|
||||
* option name is omitted.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to read the value from.
|
||||
*
|
||||
* @param {string} sid
|
||||
* The name or ID of the section to read.
|
||||
*
|
||||
* @param {string} [option]
|
||||
* The option name to read the value from. If the option name is
|
||||
* omitted or `null`, the entire section is returned instead.
|
||||
*
|
||||
* @returns {null|string|string[]|LuCI.uci.SectionObject}
|
||||
* - Returns a string containing the option value in case of a
|
||||
* plain UCI option.
|
||||
* - Returns an array of strings containing the option values in
|
||||
* case of `option` pointing to an UCI list.
|
||||
* - Returns a {@link LuCI.uci.SectionObject section object} if
|
||||
* the `option` argument has been omitted or is `null`.
|
||||
* - Returns `null` if the config, section or option has not been
|
||||
* found or if the corresponding configuration is not loaded.
|
||||
*/
|
||||
get: function(conf, sid, opt) {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes;
|
||||
|
||||
sid = this.resolveSID(conf, sid);
|
||||
|
||||
if (sid == null)
|
||||
return null;
|
||||
|
||||
/* requested option in a just created section */
|
||||
if (n[conf] && n[conf][sid]) {
|
||||
if (!n[conf])
|
||||
return null;
|
||||
|
||||
if (opt == null)
|
||||
return n[conf][sid];
|
||||
|
||||
return n[conf][sid][opt];
|
||||
}
|
||||
|
||||
/* requested an option value */
|
||||
if (opt != null) {
|
||||
/* check whether option was deleted */
|
||||
if (d[conf] && d[conf][sid])
|
||||
if (d[conf][sid] === true || d[conf][sid][opt])
|
||||
return null;
|
||||
|
||||
/* check whether option was changed */
|
||||
if (c[conf] && c[conf][sid] && c[conf][sid][opt] != null)
|
||||
return c[conf][sid][opt];
|
||||
|
||||
/* return base value */
|
||||
if (v[conf] && v[conf][sid])
|
||||
return v[conf][sid][opt];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* requested an entire section */
|
||||
if (v[conf]) {
|
||||
/* check whether entire section was deleted */
|
||||
if (d[conf] && d[conf][sid] === true)
|
||||
return null;
|
||||
|
||||
var s = v[conf][sid] || null;
|
||||
|
||||
if (s) {
|
||||
/* merge changes */
|
||||
if (c[conf] && c[conf][sid])
|
||||
for (var opt in c[conf][sid])
|
||||
if (c[conf][sid][opt] != null)
|
||||
s[opt] = c[conf][sid][opt];
|
||||
|
||||
/* merge deletions */
|
||||
if (d[conf] && d[conf][sid])
|
||||
for (var opt in d[conf][sid])
|
||||
delete s[opt];
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the value of the given option within the specified section
|
||||
* of the given configuration.
|
||||
*
|
||||
* If either config, section or option is null, or if `option` begins
|
||||
* with a dot, the function will do nothing.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to set the option value in.
|
||||
*
|
||||
* @param {string} sid
|
||||
* The name or ID of the section to set the option value in.
|
||||
*
|
||||
* @param {string} option
|
||||
* The option name to set the value for.
|
||||
*
|
||||
* @param {null|string|string[]} value
|
||||
* The option value to set. If the value is `null` or an empty string,
|
||||
* the option will be removed, otherwise it will be set or overwritten
|
||||
* with the given value.
|
||||
*/
|
||||
set: function(conf, sid, opt, val) {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes;
|
||||
|
||||
sid = this.resolveSID(conf, sid);
|
||||
|
||||
if (sid == null || opt == null || opt.charAt(0) == '.')
|
||||
return;
|
||||
|
||||
if (n[conf] && n[conf][sid]) {
|
||||
if (val != null)
|
||||
n[conf][sid][opt] = val;
|
||||
else
|
||||
delete n[conf][sid][opt];
|
||||
}
|
||||
else if (val != null && val !== '') {
|
||||
/* do not set within deleted section */
|
||||
if (d[conf] && d[conf][sid] === true)
|
||||
return;
|
||||
|
||||
/* only set in existing sections */
|
||||
if (!v[conf] || !v[conf][sid])
|
||||
return;
|
||||
|
||||
if (!c[conf])
|
||||
c[conf] = {};
|
||||
|
||||
if (!c[conf][sid])
|
||||
c[conf][sid] = {};
|
||||
|
||||
/* undelete option */
|
||||
if (d[conf] && d[conf][sid]) {
|
||||
if (isEmpty(d[conf][sid], opt))
|
||||
delete d[conf][sid];
|
||||
else
|
||||
delete d[conf][sid][opt];
|
||||
}
|
||||
|
||||
c[conf][sid][opt] = val;
|
||||
}
|
||||
else {
|
||||
/* revert any change for to-be-deleted option */
|
||||
if (c[conf] && c[conf][sid]) {
|
||||
if (isEmpty(c[conf][sid], opt))
|
||||
delete c[conf][sid];
|
||||
else
|
||||
delete c[conf][sid][opt];
|
||||
}
|
||||
|
||||
/* only delete existing options */
|
||||
if (v[conf] && v[conf][sid] && v[conf][sid].hasOwnProperty(opt)) {
|
||||
if (!d[conf])
|
||||
d[conf] = { };
|
||||
|
||||
if (!d[conf][sid])
|
||||
d[conf][sid] = { };
|
||||
|
||||
if (d[conf][sid] !== true)
|
||||
d[conf][sid][opt] = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the given option within the specified section of the given
|
||||
* configuration.
|
||||
*
|
||||
* This function is a convenience wrapper around
|
||||
* `uci.set(config, section, option, null)`.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to remove the option from.
|
||||
*
|
||||
* @param {string} sid
|
||||
* The name or ID of the section to remove the option from.
|
||||
*
|
||||
* @param {string} option
|
||||
* The name of the option to remove.
|
||||
*/
|
||||
unset: function(conf, sid, opt) {
|
||||
return this.set(conf, sid, opt, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the value of the given option or the entire section object of
|
||||
* the first found section of the specified type or the first found
|
||||
* section of the entire configuration if no type is specfied.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to read the value from.
|
||||
*
|
||||
* @param {string} [type]
|
||||
* The type of the first section to find. If it is `null`, the first
|
||||
* section of the entire config is read, otherwise the first section
|
||||
* matching the given type.
|
||||
*
|
||||
* @param {string} [option]
|
||||
* The option name to read the value from. If the option name is
|
||||
* omitted or `null`, the entire section is returned instead.
|
||||
*
|
||||
* @returns {null|string|string[]|LuCI.uci.SectionObject}
|
||||
* - Returns a string containing the option value in case of a
|
||||
* plain UCI option.
|
||||
* - Returns an array of strings containing the option values in
|
||||
* case of `option` pointing to an UCI list.
|
||||
* - Returns a {@link LuCI.uci.SectionObject section object} if
|
||||
* the `option` argument has been omitted or is `null`.
|
||||
* - Returns `null` if the config, section or option has not been
|
||||
* found or if the corresponding configuration is not loaded.
|
||||
*/
|
||||
get_first: function(conf, type, opt) {
|
||||
var sid = null;
|
||||
|
||||
this.sections(conf, type, function(s) {
|
||||
if (sid == null)
|
||||
sid = s['.name'];
|
||||
});
|
||||
|
||||
return this.get(conf, sid, opt);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the value of the given option within the first found section
|
||||
* of the given configuration matching the specified type or within
|
||||
* the first section of the entire config when no type has is specified.
|
||||
*
|
||||
* If either config, type or option is null, or if `option` begins
|
||||
* with a dot, the function will do nothing.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to set the option value in.
|
||||
*
|
||||
* @param {string} [type]
|
||||
* The type of the first section to find. If it is `null`, the first
|
||||
* section of the entire config is written to, otherwise the first
|
||||
* section matching the given type is used.
|
||||
*
|
||||
* @param {string} option
|
||||
* The option name to set the value for.
|
||||
*
|
||||
* @param {null|string|string[]} value
|
||||
* The option value to set. If the value is `null` or an empty string,
|
||||
* the option will be removed, otherwise it will be set or overwritten
|
||||
* with the given value.
|
||||
*/
|
||||
set_first: function(conf, type, opt, val) {
|
||||
var sid = null;
|
||||
|
||||
this.sections(conf, type, function(s) {
|
||||
if (sid == null)
|
||||
sid = s['.name'];
|
||||
});
|
||||
|
||||
return this.set(conf, sid, opt, val);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the given option within the first found section of the given
|
||||
* configuration matching the specified type or within the first section
|
||||
* of the entire config when no type has is specified.
|
||||
*
|
||||
* This function is a convenience wrapper around
|
||||
* `uci.set_first(config, type, option, null)`.
|
||||
*
|
||||
* @param {string} config
|
||||
* The name of the configuration to set the option value in.
|
||||
*
|
||||
* @param {string} [type]
|
||||
* The type of the first section to find. If it is `null`, the first
|
||||
* section of the entire config is written to, otherwise the first
|
||||
* section matching the given type is used.
|
||||
*
|
||||
* @param {string} option
|
||||
* The option name to set the value for.
|
||||
*/
|
||||
unset_first: function(conf, type, opt) {
|
||||
return this.set_first(conf, type, opt, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Move the first specified section within the given configuration
|
||||
* before or after the second specified section.
|
||||
*
|
||||
* @param {string} config
|
||||
* The configuration to move the section within.
|
||||
*
|
||||
* @param {string} sid1
|
||||
* The ID of the section to move within the configuration.
|
||||
*
|
||||
* @param {string} [sid2]
|
||||
* The ID of the target section for the move operation. If the
|
||||
* `after` argument is `false` or not specified, the section named by
|
||||
* `sid1` will be moved before this target section, if the `after`
|
||||
* argument is `true`, the `sid1` section will be moved after this
|
||||
* section.
|
||||
*
|
||||
* When the `sid2` argument is `null`, the section specified by `sid1`
|
||||
* is moved to the end of the configuration.
|
||||
*
|
||||
* @param {boolean} [after=false]
|
||||
* When `true`, the section `sid1` is moved after the section `sid2`,
|
||||
* when `false`, the section `sid1` is moved before `sid2`.
|
||||
*
|
||||
* If `sid2` is null, then this parameter has no effect and the section
|
||||
* `sid1` is moved to the end of the configuration instead.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* Returns `true` when the section was successfully moved, or `false`
|
||||
* when either the section specified by `sid1` or by `sid2` is not found.
|
||||
*/
|
||||
move: function(conf, sid1, sid2, after) {
|
||||
var sa = this.sections(conf),
|
||||
s1 = null, s2 = null;
|
||||
|
||||
sid1 = this.resolveSID(conf, sid1);
|
||||
sid2 = this.resolveSID(conf, sid2);
|
||||
|
||||
for (var i = 0; i < sa.length; i++) {
|
||||
if (sa[i]['.name'] != sid1)
|
||||
continue;
|
||||
|
||||
s1 = sa[i];
|
||||
sa.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (s1 == null)
|
||||
return false;
|
||||
|
||||
if (sid2 == null) {
|
||||
sa.push(s1);
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < sa.length; i++) {
|
||||
if (sa[i]['.name'] != sid2)
|
||||
continue;
|
||||
|
||||
s2 = sa[i];
|
||||
sa.splice(i + !!after, 0, s1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (s2 == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < sa.length; i++)
|
||||
this.get(conf, sa[i]['.name'])['.index'] = i;
|
||||
|
||||
this.state.reorder[conf] = true;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Submits all local configuration changes to the remove `ubus` api,
|
||||
* adds, removes and reorders remote sections as needed and reloads
|
||||
* all loaded configurations to resynchronize the local state with
|
||||
* the remote configuration values.
|
||||
*
|
||||
* @returns {string[]}
|
||||
* Returns a promise resolving to an array of configuration names which
|
||||
* have been reloaded by the save operation.
|
||||
*/
|
||||
save: function() {
|
||||
var v = this.state.values,
|
||||
n = this.state.creates,
|
||||
c = this.state.changes,
|
||||
d = this.state.deletes,
|
||||
r = this.state.reorder,
|
||||
self = this,
|
||||
snew = [ ],
|
||||
pkgs = { },
|
||||
tasks = [];
|
||||
|
||||
if (d)
|
||||
for (var conf in d) {
|
||||
for (var sid in d[conf]) {
|
||||
var o = d[conf][sid];
|
||||
|
||||
if (o === true)
|
||||
tasks.push(self.callDelete(conf, sid, null));
|
||||
else
|
||||
tasks.push(self.callDelete(conf, sid, Object.keys(o)));
|
||||
}
|
||||
|
||||
pkgs[conf] = true;
|
||||
}
|
||||
|
||||
if (n)
|
||||
for (var conf in n) {
|
||||
for (var sid in n[conf]) {
|
||||
var p = {
|
||||
config: conf,
|
||||
values: { }
|
||||
};
|
||||
|
||||
for (var k in n[conf][sid]) {
|
||||
if (k == '.type')
|
||||
p.type = n[conf][sid][k];
|
||||
else if (k == '.create')
|
||||
p.name = n[conf][sid][k];
|
||||
else if (k.charAt(0) != '.')
|
||||
p.values[k] = n[conf][sid][k];
|
||||
}
|
||||
|
||||
snew.push(n[conf][sid]);
|
||||
tasks.push(self.callAdd(p.config, p.type, p.name, p.values));
|
||||
}
|
||||
|
||||
pkgs[conf] = true;
|
||||
}
|
||||
|
||||
if (c)
|
||||
for (var conf in c) {
|
||||
for (var sid in c[conf])
|
||||
tasks.push(self.callSet(conf, sid, c[conf][sid]));
|
||||
|
||||
pkgs[conf] = true;
|
||||
}
|
||||
|
||||
if (r)
|
||||
for (var conf in r)
|
||||
pkgs[conf] = true;
|
||||
|
||||
return Promise.all(tasks).then(function(responses) {
|
||||
/*
|
||||
array "snew" holds references to the created uci sections,
|
||||
use it to assign the returned names of the new sections
|
||||
*/
|
||||
for (var i = 0; i < snew.length; i++)
|
||||
snew[i]['.name'] = responses[i];
|
||||
|
||||
return self.reorderSections();
|
||||
}).then(function() {
|
||||
pkgs = Object.keys(pkgs);
|
||||
|
||||
self.unload(pkgs);
|
||||
|
||||
return self.load(pkgs);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Instructs the remote `ubus` UCI api to commit all saved changes with
|
||||
* rollback protection and attempts to confirm the pending commit
|
||||
* operation to cancel the rollback timer.
|
||||
*
|
||||
* @param {number} [timeout=10]
|
||||
* Override the confirmation timeout after which a rollback is triggered.
|
||||
*
|
||||
* @returns {Promise<number>}
|
||||
* Returns a promise resolving/rejecting with the `ubus` RPC status code.
|
||||
*/
|
||||
apply: function(timeout) {
|
||||
var self = this,
|
||||
date = new Date();
|
||||
|
||||
if (typeof(timeout) != 'number' || timeout < 1)
|
||||
timeout = 10;
|
||||
|
||||
return self.callApply(timeout, true).then(function(rv) {
|
||||
if (rv != 0)
|
||||
return Promise.reject(rv);
|
||||
|
||||
var try_deadline = date.getTime() + 1000 * timeout;
|
||||
var try_confirm = function() {
|
||||
return self.callConfirm().then(function(rv) {
|
||||
if (rv != 0) {
|
||||
if (date.getTime() < try_deadline)
|
||||
window.setTimeout(try_confirm, 250);
|
||||
else
|
||||
return Promise.reject(rv);
|
||||
}
|
||||
|
||||
return rv;
|
||||
});
|
||||
};
|
||||
|
||||
window.setTimeout(try_confirm, 1000);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* An UCI change record is a plain array containing the change operation
|
||||
* name as first element, the affected section ID as second argument
|
||||
* and an optional third and fourth argument whose meanings depend on
|
||||
* the operation.
|
||||
*
|
||||
* @typedef {string[]} ChangeRecord
|
||||
* @memberof LuCI.uci
|
||||
*
|
||||
* @property {string} 0
|
||||
* The operation name - may be one of `add`, `set`, `remove`, `order`,
|
||||
* `list-add`, `list-del` or `rename`.
|
||||
*
|
||||
* @property {string} 1
|
||||
* The section ID targeted by the operation.
|
||||
*
|
||||
* @property {string} 2
|
||||
* The meaning of the third element depends on the operation.
|
||||
* - For `add` it is type of the section that has been added
|
||||
* - For `set` it either is the option name if a fourth element exists,
|
||||
* or the type of a named section which has been added when the change
|
||||
* entry only contains three elements.
|
||||
* - For `remove` it contains the name of the option that has been
|
||||
* removed.
|
||||
* - For `order` it specifies the new sort index of the section.
|
||||
* - For `list-add` it contains the name of the list option a new value
|
||||
* has been added to.
|
||||
* - For `list-del` it contains the name of the list option a value has
|
||||
* been removed from.
|
||||
* - For `rename` it contains the name of the option that has been
|
||||
* renamed if a fourth element exists, else it contains the new name
|
||||
* a section has been renamed to if the change entry only contains
|
||||
* three elements.
|
||||
*
|
||||
* @property {string} 4
|
||||
* The meaning of the fourth element depends on the operation.
|
||||
* - For `set` it is the value an option has been set to.
|
||||
* - For `list-add` it is the new value that has been added to a
|
||||
* list option.
|
||||
* - For `rename` it is the new name of an option that has been
|
||||
* renamed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches uncommitted UCI changes from the remote `ubus` RPC api.
|
||||
*
|
||||
* @method
|
||||
* @returns {Promise<Object<string, Array<LuCI.uci.ChangeRecord>>>}
|
||||
* Returns a promise resolving to an object containing the configuration
|
||||
* names as keys and arrays of related change records as values.
|
||||
*/
|
||||
changes: rpc.declare({
|
||||
object: 'uci',
|
||||
method: 'changes',
|
||||
expect: { changes: { } }
|
||||
})
|
||||
});
|
@ -0,0 +1,614 @@
|
||||
'use strict';
|
||||
'require baseclass';
|
||||
|
||||
function bytelen(x) {
|
||||
return new Blob([x]).size;
|
||||
}
|
||||
|
||||
var Validator = baseclass.extend({
|
||||
__name__: 'Validation',
|
||||
|
||||
__init__: function(field, type, optional, vfunc, validatorFactory) {
|
||||
this.field = field;
|
||||
this.optional = optional;
|
||||
this.vfunc = vfunc;
|
||||
this.vstack = validatorFactory.compile(type);
|
||||
this.factory = validatorFactory;
|
||||
},
|
||||
|
||||
assert: function(condition, message) {
|
||||
if (!condition) {
|
||||
this.field.classList.add('cbi-input-invalid');
|
||||
this.error = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.field.classList.remove('cbi-input-invalid');
|
||||
this.error = null;
|
||||
return true;
|
||||
},
|
||||
|
||||
apply: function(name, value, args) {
|
||||
var func;
|
||||
|
||||
if (typeof(name) === 'function')
|
||||
func = name;
|
||||
else if (typeof(this.factory.types[name]) === 'function')
|
||||
func = this.factory.types[name];
|
||||
else
|
||||
return false;
|
||||
|
||||
if (value != null)
|
||||
this.value = value;
|
||||
|
||||
return func.apply(this, args);
|
||||
},
|
||||
|
||||
validate: function() {
|
||||
/* element is detached */
|
||||
if (!findParent(this.field, 'body') && !findParent(this.field, '[data-field]'))
|
||||
return true;
|
||||
|
||||
this.field.classList.remove('cbi-input-invalid');
|
||||
this.value = (this.field.value != null) ? this.field.value : '';
|
||||
this.error = null;
|
||||
|
||||
var valid;
|
||||
|
||||
if (this.value.length === 0)
|
||||
valid = this.assert(this.optional, _('non-empty value'));
|
||||
else
|
||||
valid = this.vstack[0].apply(this, this.vstack[1]);
|
||||
|
||||
if (valid !== true) {
|
||||
var message = _('Expecting: %s').format(this.error);
|
||||
this.field.setAttribute('data-tooltip', message);
|
||||
this.field.setAttribute('data-tooltip-style', 'error');
|
||||
this.field.dispatchEvent(new CustomEvent('validation-failure', {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
message: message
|
||||
}
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(this.vfunc) == 'function')
|
||||
valid = this.vfunc(this.value);
|
||||
|
||||
if (valid !== true) {
|
||||
this.assert(false, valid);
|
||||
this.field.setAttribute('data-tooltip', valid);
|
||||
this.field.setAttribute('data-tooltip-style', 'error');
|
||||
this.field.dispatchEvent(new CustomEvent('validation-failure', {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
message: valid
|
||||
}
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
|
||||
this.field.removeAttribute('data-tooltip');
|
||||
this.field.removeAttribute('data-tooltip-style');
|
||||
this.field.dispatchEvent(new CustomEvent('validation-success', { bubbles: true }));
|
||||
return true;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
var ValidatorFactory = baseclass.extend({
|
||||
__name__: 'ValidatorFactory',
|
||||
|
||||
create: function(field, type, optional, vfunc) {
|
||||
return new Validator(field, type, optional, vfunc, this);
|
||||
},
|
||||
|
||||
compile: function(code) {
|
||||
var pos = 0;
|
||||
var esc = false;
|
||||
var depth = 0;
|
||||
var stack = [ ];
|
||||
|
||||
code += ',';
|
||||
|
||||
for (var i = 0; i < code.length; i++) {
|
||||
if (esc) {
|
||||
esc = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (code.charCodeAt(i))
|
||||
{
|
||||
case 92:
|
||||
esc = true;
|
||||
break;
|
||||
|
||||
case 40:
|
||||
case 44:
|
||||
if (depth <= 0) {
|
||||
if (pos < i) {
|
||||
var label = code.substring(pos, i);
|
||||
label = label.replace(/\\(.)/g, '$1');
|
||||
label = label.replace(/^[ \t]+/g, '');
|
||||
label = label.replace(/[ \t]+$/g, '');
|
||||
|
||||
if (label && !isNaN(label)) {
|
||||
stack.push(parseFloat(label));
|
||||
}
|
||||
else if (label.match(/^(['"]).*\1$/)) {
|
||||
stack.push(label.replace(/^(['"])(.*)\1$/, '$2'));
|
||||
}
|
||||
else if (typeof this.types[label] == 'function') {
|
||||
stack.push(this.types[label]);
|
||||
stack.push(null);
|
||||
}
|
||||
else {
|
||||
L.raise('SyntaxError', 'Unhandled token "%s"', label);
|
||||
}
|
||||
}
|
||||
|
||||
pos = i+1;
|
||||
}
|
||||
|
||||
depth += (code.charCodeAt(i) == 40);
|
||||
break;
|
||||
|
||||
case 41:
|
||||
if (--depth <= 0) {
|
||||
if (typeof stack[stack.length-2] != 'function')
|
||||
L.raise('SyntaxError', 'Argument list follows non-function');
|
||||
|
||||
stack[stack.length-1] = this.compile(code.substring(pos, i));
|
||||
pos = i+1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stack;
|
||||
},
|
||||
|
||||
parseInteger: function(x) {
|
||||
return (/^-?\d+$/.test(x) ? +x : NaN);
|
||||
},
|
||||
|
||||
parseDecimal: function(x) {
|
||||
return (/^-?\d+(?:\.\d+)?$/.test(x) ? +x : NaN);
|
||||
},
|
||||
|
||||
parseIPv4: function(x) {
|
||||
if (!x.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))
|
||||
return null;
|
||||
|
||||
if (RegExp.$1 > 255 || RegExp.$2 > 255 || RegExp.$3 > 255 || RegExp.$4 > 255)
|
||||
return null;
|
||||
|
||||
return [ +RegExp.$1, +RegExp.$2, +RegExp.$3, +RegExp.$4 ];
|
||||
},
|
||||
|
||||
parseIPv6: function(x) {
|
||||
if (x.match(/^([a-fA-F0-9:]+):(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/)) {
|
||||
var v6 = RegExp.$1, v4 = this.parseIPv4(RegExp.$2);
|
||||
|
||||
if (!v4)
|
||||
return null;
|
||||
|
||||
x = v6 + ':' + (v4[0] * 256 + v4[1]).toString(16)
|
||||
+ ':' + (v4[2] * 256 + v4[3]).toString(16);
|
||||
}
|
||||
|
||||
if (!x.match(/^[a-fA-F0-9:]+$/))
|
||||
return null;
|
||||
|
||||
var prefix_suffix = x.split(/::/);
|
||||
|
||||
if (prefix_suffix.length > 2)
|
||||
return null;
|
||||
|
||||
var prefix = (prefix_suffix[0] || '0').split(/:/);
|
||||
var suffix = prefix_suffix.length > 1 ? (prefix_suffix[1] || '0').split(/:/) : [];
|
||||
|
||||
if (suffix.length ? (prefix.length + suffix.length > 7)
|
||||
: ((prefix_suffix.length < 2 && prefix.length < 8) || prefix.length > 8))
|
||||
return null;
|
||||
|
||||
var i, word;
|
||||
var words = [];
|
||||
|
||||
for (i = 0, word = parseInt(prefix[0], 16); i < prefix.length; word = parseInt(prefix[++i], 16))
|
||||
if (prefix[i].length <= 4 && !isNaN(word) && word <= 0xFFFF)
|
||||
words.push(word);
|
||||
else
|
||||
return null;
|
||||
|
||||
for (i = 0; i < (8 - prefix.length - suffix.length); i++)
|
||||
words.push(0);
|
||||
|
||||
for (i = 0, word = parseInt(suffix[0], 16); i < suffix.length; word = parseInt(suffix[++i], 16))
|
||||
if (suffix[i].length <= 4 && !isNaN(word) && word <= 0xFFFF)
|
||||
words.push(word);
|
||||
else
|
||||
return null;
|
||||
|
||||
return words;
|
||||
},
|
||||
|
||||
types: {
|
||||
integer: function() {
|
||||
return this.assert(!isNaN(this.factory.parseInteger(this.value)), _('valid integer value'));
|
||||
},
|
||||
|
||||
uinteger: function() {
|
||||
return this.assert(this.factory.parseInteger(this.value) >= 0, _('positive integer value'));
|
||||
},
|
||||
|
||||
float: function() {
|
||||
return this.assert(!isNaN(this.factory.parseDecimal(this.value)), _('valid decimal value'));
|
||||
},
|
||||
|
||||
ufloat: function() {
|
||||
return this.assert(this.factory.parseDecimal(this.value) >= 0, _('positive decimal value'));
|
||||
},
|
||||
|
||||
ipaddr: function(nomask) {
|
||||
return this.assert(this.apply('ip4addr', null, [nomask]) || this.apply('ip6addr', null, [nomask]),
|
||||
nomask ? _('valid IP address') : _('valid IP address or prefix'));
|
||||
},
|
||||
|
||||
ip4addr: function(nomask) {
|
||||
var re = nomask ? /^(\d+\.\d+\.\d+\.\d+)$/ : /^(\d+\.\d+\.\d+\.\d+)(?:\/(\d+\.\d+\.\d+\.\d+)|\/(\d{1,2}))?$/,
|
||||
m = this.value.match(re);
|
||||
|
||||
return this.assert(m && this.factory.parseIPv4(m[1]) && (m[2] ? this.factory.parseIPv4(m[2]) : (m[3] ? this.apply('ip4prefix', m[3]) : true)),
|
||||
nomask ? _('valid IPv4 address') : _('valid IPv4 address or network'));
|
||||
},
|
||||
|
||||
ip6addr: function(nomask) {
|
||||
var re = nomask ? /^([0-9a-fA-F:.]+)$/ : /^([0-9a-fA-F:.]+)(?:\/(\d{1,3}))?$/,
|
||||
m = this.value.match(re);
|
||||
|
||||
return this.assert(m && this.factory.parseIPv6(m[1]) && (m[2] ? this.apply('ip6prefix', m[2]) : true),
|
||||
nomask ? _('valid IPv6 address') : _('valid IPv6 address or prefix'));
|
||||
},
|
||||
|
||||
ip4prefix: function() {
|
||||
return this.assert(!isNaN(this.value) && this.value >= 0 && this.value <= 32,
|
||||
_('valid IPv4 prefix value (0-32)'));
|
||||
},
|
||||
|
||||
ip6prefix: function() {
|
||||
return this.assert(!isNaN(this.value) && this.value >= 0 && this.value <= 128,
|
||||
_('valid IPv6 prefix value (0-128)'));
|
||||
},
|
||||
|
||||
cidr: function(negative) {
|
||||
return this.assert(this.apply('cidr4', null, [negative]) || this.apply('cidr6', null, [negative]),
|
||||
_('valid IPv4 or IPv6 CIDR'));
|
||||
},
|
||||
|
||||
cidr4: function(negative) {
|
||||
var m = this.value.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(-)?(\d{1,2})$/);
|
||||
return this.assert(m && this.factory.parseIPv4(m[1]) && (negative || !m[2]) && this.apply('ip4prefix', m[3]),
|
||||
_('valid IPv4 CIDR'));
|
||||
},
|
||||
|
||||
cidr6: function(negative) {
|
||||
var m = this.value.match(/^([0-9a-fA-F:.]+)\/(-)?(\d{1,3})$/);
|
||||
return this.assert(m && this.factory.parseIPv6(m[1]) && (negative || !m[2]) && this.apply('ip6prefix', m[3]),
|
||||
_('valid IPv6 CIDR'));
|
||||
},
|
||||
|
||||
ipnet4: function() {
|
||||
var m = this.value.match(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
||||
return this.assert(m && this.factory.parseIPv4(m[1]) && this.factory.parseIPv4(m[2]), _('IPv4 network in address/netmask notation'));
|
||||
},
|
||||
|
||||
ipnet6: function() {
|
||||
var m = this.value.match(/^([0-9a-fA-F:.]+)\/([0-9a-fA-F:.]+)$/);
|
||||
return this.assert(m && this.factory.parseIPv6(m[1]) && this.factory.parseIPv6(m[2]), _('IPv6 network in address/netmask notation'));
|
||||
},
|
||||
|
||||
ip6hostid: function() {
|
||||
if (this.value == "eui64" || this.value == "random")
|
||||
return true;
|
||||
|
||||
var v6 = this.factory.parseIPv6(this.value);
|
||||
return this.assert(!(!v6 || v6[0] || v6[1] || v6[2] || v6[3]), _('valid IPv6 host id'));
|
||||
},
|
||||
|
||||
ipmask: function(negative) {
|
||||
return this.assert(this.apply('ipmask4', null, [negative]) || this.apply('ipmask6', null, [negative]),
|
||||
_('valid network in address/netmask notation'));
|
||||
},
|
||||
|
||||
ipmask4: function(negative) {
|
||||
return this.assert(this.apply('cidr4', null, [negative]) || this.apply('ipnet4') || this.apply('ip4addr'),
|
||||
_('valid IPv4 network'));
|
||||
},
|
||||
|
||||
ipmask6: function(negative) {
|
||||
return this.assert(this.apply('cidr6', null, [negative]) || this.apply('ipnet6') || this.apply('ip6addr'),
|
||||
_('valid IPv6 network'));
|
||||
},
|
||||
|
||||
port: function() {
|
||||
var p = this.factory.parseInteger(this.value);
|
||||
return this.assert(p >= 0 && p <= 65535, _('valid port value'));
|
||||
},
|
||||
|
||||
portrange: function() {
|
||||
if (this.value.match(/^(\d+)-(\d+)$/)) {
|
||||
var p1 = +RegExp.$1;
|
||||
var p2 = +RegExp.$2;
|
||||
return this.assert(p1 <= p2 && p2 <= 65535,
|
||||
_('valid port or port range (port1-port2)'));
|
||||
}
|
||||
|
||||
return this.assert(this.apply('port'), _('valid port or port range (port1-port2)'));
|
||||
},
|
||||
|
||||
macaddr: function(multicast) {
|
||||
var m = this.value.match(/^([a-fA-F0-9]{2}):([a-fA-F0-9]{2}:){4}[a-fA-F0-9]{2}$/);
|
||||
return this.assert(m != null && !(+m[1] & 1) == !multicast,
|
||||
multicast ? _('valid multicast MAC address') : _('valid MAC address'));
|
||||
},
|
||||
|
||||
host: function(ipv4only) {
|
||||
return this.assert(this.apply('hostname') || this.apply(ipv4only == 1 ? 'ip4addr' : 'ipaddr', null, ['nomask']),
|
||||
_('valid hostname or IP address'));
|
||||
},
|
||||
|
||||
hostname: function(strict) {
|
||||
if (this.value.length <= 253)
|
||||
return this.assert(
|
||||
(this.value.match(/^[a-zA-Z0-9_]+$/) != null ||
|
||||
(this.value.match(/^[a-zA-Z0-9_][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$/) &&
|
||||
this.value.match(/[^0-9.]/))) &&
|
||||
(!strict || !this.value.match(/^_/)),
|
||||
_('valid hostname'));
|
||||
|
||||
return this.assert(false, _('valid hostname'));
|
||||
},
|
||||
|
||||
network: function() {
|
||||
return this.assert(this.apply('uciname') || this.apply('hostname') || this.apply('ip4addr') || this.apply('ip6addr'),
|
||||
_('valid UCI identifier, hostname or IP address range'));
|
||||
},
|
||||
|
||||
hostport: function(ipv4only) {
|
||||
var hp = this.value.split(/:/);
|
||||
return this.assert(hp.length == 2 && this.apply('host', hp[0], [ipv4only]) && this.apply('port', hp[1]),
|
||||
_('valid host:port'));
|
||||
},
|
||||
|
||||
ip4addrport: function() {
|
||||
var hp = this.value.split(/:/);
|
||||
return this.assert(hp.length == 2 && this.apply('ip4addr', hp[0], [true]) && this.apply('port', hp[1]),
|
||||
_('valid IPv4 address:port'));
|
||||
},
|
||||
|
||||
ipaddrport: function(bracket) {
|
||||
var m4 = this.value.match(/^([^\[\]:]+):(\d+)$/),
|
||||
m6 = this.value.match((bracket == 1) ? /^\[(.+)\]:(\d+)$/ : /^([^\[\]]+):(\d+)$/);
|
||||
|
||||
if (m4)
|
||||
return this.assert(this.apply('ip4addr', m4[1], [true]) && this.apply('port', m4[2]),
|
||||
_('valid address:port'));
|
||||
|
||||
return this.assert(m6 && this.apply('ip6addr', m6[1], [true]) && this.apply('port', m6[2]),
|
||||
_('valid address:port'));
|
||||
},
|
||||
|
||||
wpakey: function() {
|
||||
var v = this.value;
|
||||
|
||||
if (v.length == 64)
|
||||
return this.assert(v.match(/^[a-fA-F0-9]{64}$/), _('valid hexadecimal WPA key'));
|
||||
|
||||
return this.assert((v.length >= 8) && (v.length <= 63), _('key between 8 and 63 characters'));
|
||||
},
|
||||
|
||||
wepkey: function() {
|
||||
var v = this.value;
|
||||
|
||||
if (v.substr(0, 2) === 's:')
|
||||
v = v.substr(2);
|
||||
|
||||
if ((v.length == 10) || (v.length == 26))
|
||||
return this.assert(v.match(/^[a-fA-F0-9]{10,26}$/), _('valid hexadecimal WEP key'));
|
||||
|
||||
return this.assert((v.length === 5) || (v.length === 13), _('key with either 5 or 13 characters'));
|
||||
},
|
||||
|
||||
uciname: function() {
|
||||
return this.assert(this.value.match(/^[a-zA-Z0-9_]+$/), _('valid UCI identifier'));
|
||||
},
|
||||
|
||||
netdevname: function() {
|
||||
var v = this.value;
|
||||
|
||||
if (v == '.' || v == '..')
|
||||
return this.assert(false, _('valid network device name, not "." or ".."'));
|
||||
|
||||
return this.assert(v.match(/^[^:\/%\s]{1,15}$/), _('valid network device name between 1 and 15 characters not containing ":", "/", "%" or spaces'));
|
||||
},
|
||||
|
||||
range: function(min, max) {
|
||||
var val = this.factory.parseDecimal(this.value);
|
||||
return this.assert(val >= +min && val <= +max, _('value between %f and %f').format(min, max));
|
||||
},
|
||||
|
||||
min: function(min) {
|
||||
return this.assert(this.factory.parseDecimal(this.value) >= +min, _('value greater or equal to %f').format(min));
|
||||
},
|
||||
|
||||
max: function(max) {
|
||||
return this.assert(this.factory.parseDecimal(this.value) <= +max, _('value smaller or equal to %f').format(max));
|
||||
},
|
||||
|
||||
length: function(len) {
|
||||
return this.assert(bytelen(this.value) == +len,
|
||||
_('value with %d characters').format(len));
|
||||
},
|
||||
|
||||
rangelength: function(min, max) {
|
||||
var len = bytelen(this.value);
|
||||
return this.assert((len >= +min) && (len <= +max),
|
||||
_('value between %d and %d characters').format(min, max));
|
||||
},
|
||||
|
||||
minlength: function(min) {
|
||||
return this.assert(bytelen(this.value) >= +min,
|
||||
_('value with at least %d characters').format(min));
|
||||
},
|
||||
|
||||
maxlength: function(max) {
|
||||
return this.assert(bytelen(this.value) <= +max,
|
||||
_('value with at most %d characters').format(max));
|
||||
},
|
||||
|
||||
or: function() {
|
||||
var errors = [];
|
||||
|
||||
for (var i = 0; i < arguments.length; i += 2) {
|
||||
if (typeof arguments[i] != 'function') {
|
||||
if (arguments[i] == this.value)
|
||||
return this.assert(true);
|
||||
errors.push('"%s"'.format(arguments[i]));
|
||||
i--;
|
||||
}
|
||||
else if (arguments[i].apply(this, arguments[i+1])) {
|
||||
return this.assert(true);
|
||||
}
|
||||
else {
|
||||
errors.push(this.error);
|
||||
}
|
||||
}
|
||||
|
||||
var t = _('One of the following: %s');
|
||||
|
||||
return this.assert(false, t.format('\n - ' + errors.join('\n - ')));
|
||||
},
|
||||
|
||||
and: function() {
|
||||
for (var i = 0; i < arguments.length; i += 2) {
|
||||
if (typeof arguments[i] != 'function') {
|
||||
if (arguments[i] != this.value)
|
||||
return this.assert(false, '"%s"'.format(arguments[i]));
|
||||
i--;
|
||||
}
|
||||
else if (!arguments[i].apply(this, arguments[i+1])) {
|
||||
return this.assert(false, this.error);
|
||||
}
|
||||
}
|
||||
|
||||
return this.assert(true);
|
||||
},
|
||||
|
||||
neg: function() {
|
||||
this.value = this.value.replace(/^[ \t]*![ \t]*/, '');
|
||||
|
||||
if (arguments[0].apply(this, arguments[1]))
|
||||
return this.assert(true);
|
||||
|
||||
return this.assert(false, _('Potential negation of: %s').format(this.error));
|
||||
},
|
||||
|
||||
list: function(subvalidator, subargs) {
|
||||
this.field.setAttribute('data-is-list', 'true');
|
||||
|
||||
var tokens = this.value.match(/[^ \t]+/g);
|
||||
for (var i = 0; i < tokens.length; i++)
|
||||
if (!this.apply(subvalidator, tokens[i], subargs))
|
||||
return this.assert(false, this.error);
|
||||
|
||||
return this.assert(true);
|
||||
},
|
||||
|
||||
phonedigit: function() {
|
||||
return this.assert(this.value.match(/^[0-9\*#!\.]+$/),
|
||||
_('valid phone digit (0-9, "*", "#", "!" or ".")'));
|
||||
},
|
||||
|
||||
timehhmmss: function() {
|
||||
return this.assert(this.value.match(/^[0-6][0-9]:[0-6][0-9]:[0-6][0-9]$/),
|
||||
_('valid time (HH:MM:SS)'));
|
||||
},
|
||||
|
||||
dateyyyymmdd: function() {
|
||||
if (this.value.match(/^(\d\d\d\d)-(\d\d)-(\d\d)/)) {
|
||||
var year = +RegExp.$1,
|
||||
month = +RegExp.$2,
|
||||
day = +RegExp.$3,
|
||||
days_in_month = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
|
||||
|
||||
var is_leap_year = function(year) {
|
||||
return ((!(year % 4) && (year % 100)) || !(year % 400));
|
||||
}
|
||||
|
||||
var get_days_in_month = function(month, year) {
|
||||
return (month === 2 && is_leap_year(year)) ? 29 : days_in_month[month - 1];
|
||||
}
|
||||
|
||||
/* Firewall rules in the past don't make sense */
|
||||
return this.assert(year >= 2015 && month && month <= 12 && day && day <= get_days_in_month(month, year),
|
||||
_('valid date (YYYY-MM-DD)'));
|
||||
|
||||
}
|
||||
|
||||
return this.assert(false, _('valid date (YYYY-MM-DD)'));
|
||||
},
|
||||
|
||||
unique: function(subvalidator, subargs) {
|
||||
var ctx = this,
|
||||
option = findParent(ctx.field, '[data-widget][data-name]'),
|
||||
section = findParent(option, '.cbi-section'),
|
||||
query = '[data-widget="%s"][data-name="%s"]'.format(option.getAttribute('data-widget'), option.getAttribute('data-name')),
|
||||
unique = true;
|
||||
|
||||
section.querySelectorAll(query).forEach(function(sibling) {
|
||||
if (sibling === option)
|
||||
return;
|
||||
|
||||
var input = sibling.querySelector('[data-type]'),
|
||||
values = input ? (input.getAttribute('data-is-list') ? input.value.match(/[^ \t]+/g) : [ input.value ]) : null;
|
||||
|
||||
if (values !== null && values.indexOf(ctx.value) !== -1)
|
||||
unique = false;
|
||||
});
|
||||
|
||||
if (!unique)
|
||||
return this.assert(false, _('unique value'));
|
||||
|
||||
if (typeof(subvalidator) === 'function')
|
||||
return this.apply(subvalidator, null, subargs);
|
||||
|
||||
return this.assert(true);
|
||||
},
|
||||
|
||||
hexstring: function() {
|
||||
return this.assert(this.value.match(/^([a-f0-9][a-f0-9]|[A-F0-9][A-F0-9])+$/),
|
||||
_('hexadecimal encoded value'));
|
||||
},
|
||||
|
||||
string: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
directory: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
file: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
device: function() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ValidatorFactory;
|
@ -0,0 +1 @@
|
||||
/* replaced by luci.js */
|
@ -0,0 +1,12 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local config = require "luci.config"
|
||||
local ccache = require "luci.ccache"
|
||||
|
||||
module "luci.cacheloader"
|
||||
|
||||
if config.ccache and config.ccache.enable == "1" then
|
||||
ccache.cache_ondemand()
|
||||
end
|
@ -0,0 +1,76 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local io = require "io"
|
||||
local fs = require "nixio.fs"
|
||||
local util = require "luci.util"
|
||||
local nixio = require "nixio"
|
||||
local debug = require "debug"
|
||||
local string = require "string"
|
||||
local package = require "package"
|
||||
|
||||
local type, loadfile = type, loadfile
|
||||
|
||||
|
||||
module "luci.ccache"
|
||||
|
||||
function cache_ondemand(...)
|
||||
if debug.getinfo(1, 'S').source ~= "=?" then
|
||||
cache_enable(...)
|
||||
end
|
||||
end
|
||||
|
||||
function cache_enable(cachepath, mode)
|
||||
cachepath = cachepath or "/tmp/luci-modulecache"
|
||||
mode = mode or "r--r--r--"
|
||||
|
||||
local loader = package.loaders[2]
|
||||
local uid = nixio.getuid()
|
||||
|
||||
if not fs.stat(cachepath) then
|
||||
fs.mkdir(cachepath)
|
||||
end
|
||||
|
||||
local function _encode_filename(name)
|
||||
local encoded = ""
|
||||
for i=1, #name do
|
||||
encoded = encoded .. ("%2X" % string.byte(name, i))
|
||||
end
|
||||
return encoded
|
||||
end
|
||||
|
||||
local function _load_sane(file)
|
||||
local stat = fs.stat(file)
|
||||
if stat and stat.uid == uid and stat.modestr == mode then
|
||||
return loadfile(file)
|
||||
end
|
||||
end
|
||||
|
||||
local function _write_sane(file, func)
|
||||
if nixio.getuid() == uid then
|
||||
local fp = io.open(file, "w")
|
||||
if fp then
|
||||
fp:write(util.get_bytecode(func))
|
||||
fp:close()
|
||||
fs.chmod(file, mode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
package.loaders[2] = function(mod)
|
||||
local encoded = cachepath .. "/" .. _encode_filename(mod)
|
||||
local modcons = _load_sane(encoded)
|
||||
|
||||
if modcons then
|
||||
return modcons
|
||||
end
|
||||
|
||||
-- No cachefile
|
||||
modcons = loader(mod)
|
||||
if type(modcons) == "function" then
|
||||
_write_sane(encoded, modcons)
|
||||
end
|
||||
return modcons
|
||||
end
|
||||
end
|
@ -0,0 +1,18 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local util = require "luci.util"
|
||||
module("luci.config",
|
||||
function(m)
|
||||
if pcall(require, "luci.model.uci") then
|
||||
local config = util.threadlocal()
|
||||
setmetatable(m, {
|
||||
__index = function(tbl, key)
|
||||
if not config[key] then
|
||||
config[key] = luci.model.uci.cursor():get_all("luci", key)
|
||||
end
|
||||
return config[key]
|
||||
end
|
||||
})
|
||||
end
|
||||
end)
|
@ -0,0 +1,199 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
module("luci.controller.admin.index", package.seeall)
|
||||
|
||||
function action_logout()
|
||||
local dsp = require "luci.dispatcher"
|
||||
local utl = require "luci.util"
|
||||
local sid = dsp.context.authsession
|
||||
|
||||
if sid then
|
||||
utl.ubus("session", "destroy", { ubus_rpc_session = sid })
|
||||
|
||||
local url = dsp.build_url()
|
||||
|
||||
if luci.http.getenv('HTTPS') == 'on' then
|
||||
luci.http.header("Set-Cookie", "sysauth_https=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=%s" % url)
|
||||
end
|
||||
|
||||
luci.http.header("Set-Cookie", "sysauth_http=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=%s" % url)
|
||||
end
|
||||
|
||||
luci.http.redirect(dsp.build_url())
|
||||
end
|
||||
|
||||
function action_translations(lang)
|
||||
local i18n = require "luci.i18n"
|
||||
local http = require "luci.http"
|
||||
local fs = require "nixio".fs
|
||||
|
||||
if lang and #lang > 0 then
|
||||
lang = i18n.setlanguage(lang)
|
||||
if lang then
|
||||
local s = fs.stat("%s/base.%s.lmo" %{ i18n.i18ndir, lang })
|
||||
if s then
|
||||
http.header("Cache-Control", "public, max-age=31536000")
|
||||
http.header("ETag", "%x-%x-%x" %{ s["ino"], s["size"], s["mtime"] })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
http.prepare_content("application/javascript; charset=utf-8")
|
||||
http.write("window.TR=")
|
||||
http.write_json(i18n.dump())
|
||||
end
|
||||
|
||||
local function ubus_reply(id, data, code, errmsg)
|
||||
local reply = { jsonrpc = "2.0", id = id }
|
||||
if errmsg then
|
||||
reply.error = {
|
||||
code = code,
|
||||
message = errmsg
|
||||
}
|
||||
elseif type(code) == "table" then
|
||||
reply.result = code
|
||||
else
|
||||
reply.result = { code, data }
|
||||
end
|
||||
|
||||
return reply
|
||||
end
|
||||
|
||||
local ubus_types = {
|
||||
nil,
|
||||
"array",
|
||||
"object",
|
||||
"string",
|
||||
nil, -- INT64
|
||||
"number",
|
||||
nil, -- INT16,
|
||||
"boolean",
|
||||
"double"
|
||||
}
|
||||
|
||||
local function ubus_access(sid, obj, fun)
|
||||
local res, code = luci.util.ubus("session", "access", {
|
||||
ubus_rpc_session = sid,
|
||||
scope = "ubus",
|
||||
object = obj,
|
||||
["function"] = fun
|
||||
})
|
||||
|
||||
return (type(res) == "table" and res.access == true)
|
||||
end
|
||||
|
||||
local function ubus_request(req)
|
||||
if type(req) ~= "table" or type(req.method) ~= "string" or req.jsonrpc ~= "2.0" or req.id == nil then
|
||||
return ubus_reply(nil, nil, -32600, "Invalid request")
|
||||
|
||||
elseif req.method == "call" then
|
||||
if type(req.params) ~= "table" or #req.params < 3 then
|
||||
return ubus_reply(nil, nil, -32600, "Invalid parameters")
|
||||
end
|
||||
|
||||
local sid, obj, fun, arg =
|
||||
req.params[1], req.params[2], req.params[3], req.params[4] or {}
|
||||
if type(arg) ~= "table" or arg.ubus_rpc_session ~= nil then
|
||||
return ubus_reply(req.id, nil, -32602, "Invalid parameters")
|
||||
end
|
||||
|
||||
if sid == "00000000000000000000000000000000" and luci.dispatcher.context.authsession then
|
||||
sid = luci.dispatcher.context.authsession
|
||||
end
|
||||
|
||||
if not ubus_access(sid, obj, fun) then
|
||||
return ubus_reply(req.id, nil, -32002, "Access denied")
|
||||
end
|
||||
|
||||
arg.ubus_rpc_session = sid
|
||||
|
||||
local res, code = luci.util.ubus(obj, fun, arg)
|
||||
return ubus_reply(req.id, res, code or 0)
|
||||
|
||||
elseif req.method == "list" then
|
||||
if req.params == nil or (type(req.params) == "table" and #req.params == 0) then
|
||||
local objs = luci.util.ubus()
|
||||
return ubus_reply(req.id, nil, objs)
|
||||
|
||||
elseif type(req.params) == "table" then
|
||||
local n, rv = nil, {}
|
||||
for n = 1, #req.params do
|
||||
if type(req.params[n]) ~= "string" then
|
||||
return ubus_reply(req.id, nil, -32602, "Invalid parameters")
|
||||
end
|
||||
|
||||
local sig = luci.util.ubus(req.params[n])
|
||||
if sig and type(sig) == "table" then
|
||||
rv[req.params[n]] = {}
|
||||
|
||||
local m, p
|
||||
for m, p in pairs(sig) do
|
||||
if type(p) == "table" then
|
||||
rv[req.params[n]][m] = {}
|
||||
|
||||
local pn, pt
|
||||
for pn, pt in pairs(p) do
|
||||
rv[req.params[n]][m][pn] = ubus_types[pt] or "unknown"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return ubus_reply(req.id, nil, rv)
|
||||
|
||||
else
|
||||
return ubus_reply(req.id, nil, -32602, "Invalid parameters")
|
||||
end
|
||||
end
|
||||
|
||||
return ubus_reply(req.id, nil, -32601, "Method not found")
|
||||
end
|
||||
|
||||
function action_ubus()
|
||||
local parser = require "luci.jsonc".new()
|
||||
|
||||
luci.http.context.request:setfilehandler(function(_, s)
|
||||
if not s then
|
||||
return nil
|
||||
end
|
||||
|
||||
local ok, err = parser:parse(s)
|
||||
return (not err or nil)
|
||||
end)
|
||||
|
||||
luci.http.context.request:content()
|
||||
|
||||
local json = parser:get()
|
||||
if json == nil or type(json) ~= "table" then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(ubus_reply(nil, nil, -32700, "Parse error"))
|
||||
return
|
||||
end
|
||||
|
||||
local response
|
||||
if #json == 0 then
|
||||
response = ubus_request(json)
|
||||
else
|
||||
response = {}
|
||||
|
||||
local _, request
|
||||
for _, request in ipairs(json) do
|
||||
response[_] = ubus_request(request)
|
||||
end
|
||||
end
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(response)
|
||||
end
|
||||
|
||||
function action_menu()
|
||||
local dsp = require "luci.dispatcher"
|
||||
local http = require "luci.http"
|
||||
|
||||
local _, _, acls = dsp.is_authenticated({ methods = { "cookie:sysauth_https", "cookie:sysauth_http" } })
|
||||
local menu = dsp.menu_json(acls or {}) or {}
|
||||
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(menu)
|
||||
end
|
@ -0,0 +1,70 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Copyright 2010-2019 Jo-Philipp Wich <jo@mein.io>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
module("luci.controller.admin.uci", package.seeall)
|
||||
|
||||
local function ubus_state_to_http(errstr)
|
||||
local map = {
|
||||
["Invalid command"] = 400,
|
||||
["Invalid argument"] = 400,
|
||||
["Method not found"] = 404,
|
||||
["Entry not found"] = 404,
|
||||
["No data"] = 204,
|
||||
["Permission denied"] = 403,
|
||||
["Timeout"] = 504,
|
||||
["Not supported"] = 500,
|
||||
["Unknown error"] = 500,
|
||||
["Connection failed"] = 503
|
||||
}
|
||||
|
||||
local code = map[errstr] or 200
|
||||
local msg = errstr or "OK"
|
||||
|
||||
luci.http.status(code, msg)
|
||||
|
||||
if code ~= 204 then
|
||||
luci.http.prepare_content("text/plain")
|
||||
luci.http.write(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function action_apply_rollback()
|
||||
local uci = require "luci.model.uci"
|
||||
local token, errstr = uci:apply(true)
|
||||
if token then
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json({ token = token })
|
||||
else
|
||||
ubus_state_to_http(errstr)
|
||||
end
|
||||
end
|
||||
|
||||
function action_apply_unchecked()
|
||||
local uci = require "luci.model.uci"
|
||||
local _, errstr = uci:apply(false)
|
||||
ubus_state_to_http(errstr)
|
||||
end
|
||||
|
||||
function action_confirm()
|
||||
local uci = require "luci.model.uci"
|
||||
local token = luci.http.formvalue("token")
|
||||
local _, errstr = uci:confirm(token)
|
||||
ubus_state_to_http(errstr)
|
||||
end
|
||||
|
||||
function action_revert()
|
||||
local uci = require "luci.model.uci"
|
||||
local changes = uci:changes()
|
||||
|
||||
-- Collect files to be reverted
|
||||
local _, errstr, r, tbl
|
||||
for r, tbl in pairs(changes) do
|
||||
_, errstr = uci:revert(r)
|
||||
if errstr then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
ubus_state_to_http(errstr or "OK")
|
||||
end
|
@ -0,0 +1,220 @@
|
||||
---[[
|
||||
LuCI web dispatcher.
|
||||
]]
|
||||
module "luci.dispatcher"
|
||||
|
||||
---[[
|
||||
Build the URL relative to the server webroot from given virtual path.
|
||||
|
||||
@class function
|
||||
@name build_url
|
||||
@param ... Virtual path
|
||||
@return Relative URL
|
||||
]]
|
||||
|
||||
---[[
|
||||
Check whether a dispatch node shall be visible
|
||||
|
||||
@class function
|
||||
@name node_visible
|
||||
@param node Dispatch node
|
||||
@return Boolean indicating whether the node should be visible
|
||||
]]
|
||||
|
||||
---[[
|
||||
Return a sorted table of visible children within a given node
|
||||
|
||||
@class function
|
||||
@name node_childs
|
||||
@param node Dispatch node
|
||||
@return Ordered table of child node names
|
||||
]]
|
||||
|
||||
---[[
|
||||
Send a 404 error code and render the "error404" template if available.
|
||||
|
||||
@class function
|
||||
@name error404
|
||||
@param message Custom error message (optional)
|
||||
@return false
|
||||
]]
|
||||
|
||||
---[[
|
||||
Send a 500 error code and render the "error500" template if available.
|
||||
|
||||
@class function
|
||||
@name error500
|
||||
@param message Custom error message (optional)#
|
||||
@return false
|
||||
]]
|
||||
|
||||
---[[
|
||||
Dispatch an HTTP request.
|
||||
|
||||
@class function
|
||||
@name httpdispatch
|
||||
@param request LuCI HTTP Request object
|
||||
]]
|
||||
|
||||
---[[
|
||||
Dispatches a LuCI virtual path.
|
||||
|
||||
@class function
|
||||
@name dispatch
|
||||
@param request Virtual path
|
||||
]]
|
||||
|
||||
---[[
|
||||
Generate the dispatching index using the native file-cache based strategy.
|
||||
|
||||
|
||||
@class function
|
||||
@name createindex
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create the dispatching tree from the index.
|
||||
|
||||
Build the index before if it does not exist yet.
|
||||
|
||||
@class function
|
||||
@name createtree
|
||||
]]
|
||||
|
||||
---[[
|
||||
Clone a node of the dispatching tree to another position.
|
||||
|
||||
@class function
|
||||
@name assign
|
||||
@param path Virtual path destination
|
||||
@param clone Virtual path source
|
||||
@param title Destination node title (optional)
|
||||
@param order Destination node order value (optional)
|
||||
@return Dispatching tree node
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a new dispatching node and define common parameters.
|
||||
|
||||
@class function
|
||||
@name entry
|
||||
@param path Virtual path
|
||||
@param target Target function to call when dispatched.
|
||||
@param title Destination node title
|
||||
@param order Destination node order value (optional)
|
||||
@return Dispatching tree node
|
||||
]]
|
||||
|
||||
---[[
|
||||
Fetch or create a dispatching node without setting the target module or
|
||||
enabling the node.
|
||||
|
||||
@class function
|
||||
@name get
|
||||
@param ... Virtual path
|
||||
@return Dispatching tree node
|
||||
]]
|
||||
|
||||
---[[
|
||||
Fetch or create a new dispatching node.
|
||||
|
||||
@class function
|
||||
@name node
|
||||
@param ... Virtual path
|
||||
@return Dispatching tree node
|
||||
]]
|
||||
|
||||
---[[
|
||||
Lookup node in dispatching tree.
|
||||
|
||||
@class function
|
||||
@name lookup
|
||||
@param ... Virtual path
|
||||
@return Node object, canonical url or nil if the path was not found.
|
||||
]]
|
||||
|
||||
---[[
|
||||
Alias the first (lowest order) page automatically
|
||||
|
||||
|
||||
@class function
|
||||
@name firstchild
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a redirect to another dispatching node.
|
||||
|
||||
@class function
|
||||
@name alias
|
||||
@param ... Virtual path destination
|
||||
]]
|
||||
|
||||
---[[
|
||||
Rewrite the first x path values of the request.
|
||||
|
||||
@class function
|
||||
@name rewrite
|
||||
@param n Number of path values to replace
|
||||
@param ... Virtual path to replace removed path values with
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a function-call dispatching target.
|
||||
|
||||
@class function
|
||||
@name call
|
||||
@param name Target function of local controller
|
||||
@param ... Additional parameters passed to the function
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a template render dispatching target.
|
||||
|
||||
@class function
|
||||
@name template
|
||||
@param name Template to be rendered
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a CBI model dispatching target.
|
||||
|
||||
@class function
|
||||
@name cbi
|
||||
@param model CBI model to be rendered
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a combined dispatching target for non argv and argv requests.
|
||||
|
||||
@class function
|
||||
@name arcombine
|
||||
@param trg1 Overview Target
|
||||
@param trg2 Detail Target
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a CBI form model dispatching target.
|
||||
|
||||
@class function
|
||||
@name form
|
||||
@param model CBI form model tpo be rendered
|
||||
]]
|
||||
|
||||
---[[
|
||||
Access the luci.i18n translate() api.
|
||||
|
||||
@class function
|
||||
@name translate
|
||||
@param text Text to translate
|
||||
]]
|
||||
|
||||
---[[
|
||||
No-op function used to mark translation entries for menu labels.
|
||||
|
||||
This function does not actually translate the given argument but
|
||||
is used by build/i18n-scan.pl to find translatable entries.
|
||||
|
||||
@class function
|
||||
@name _
|
||||
]]
|
||||
|
@ -0,0 +1,55 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local tparser = require "luci.template.parser"
|
||||
local util = require "luci.util"
|
||||
local tostring = tostring
|
||||
|
||||
module "luci.i18n"
|
||||
|
||||
i18ndir = util.libpath() .. "/i18n/"
|
||||
context = util.threadlocal()
|
||||
default = "en"
|
||||
|
||||
|
||||
function setlanguage(lang)
|
||||
local code, subcode = lang:match("^([A-Za-z][A-Za-z])[%-_]([A-Za-z][A-Za-z])$")
|
||||
if not (code and subcode) then
|
||||
subcode = lang:match("^([A-Za-z][A-Za-z])$")
|
||||
if not subcode then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
context.parent = code and code:lower()
|
||||
context.lang = context.parent and context.parent.."-"..subcode:lower() or subcode:lower()
|
||||
|
||||
if tparser.load_catalog(context.lang, i18ndir) and
|
||||
tparser.change_catalog(context.lang)
|
||||
then
|
||||
return context.lang
|
||||
|
||||
elseif context.parent then
|
||||
if tparser.load_catalog(context.parent, i18ndir) and
|
||||
tparser.change_catalog(context.parent)
|
||||
then
|
||||
return context.parent
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function translate(key)
|
||||
return tparser.translate(key) or key
|
||||
end
|
||||
|
||||
function translatef(key, ...)
|
||||
return tostring(translate(key)):format(...)
|
||||
end
|
||||
|
||||
function dump()
|
||||
local rv = {}
|
||||
tparser.get_translations(function(k, v) rv[k] = v end)
|
||||
return rv
|
||||
end
|
@ -0,0 +1,42 @@
|
||||
---[[
|
||||
LuCI translation library.
|
||||
]]
|
||||
module "luci.i18n"
|
||||
|
||||
---[[
|
||||
Set the context default translation language.
|
||||
|
||||
@class function
|
||||
@name setlanguage
|
||||
@param lang An IETF/BCP 47 language tag or ISO3166 country code, e.g. "en-US" or "de"
|
||||
@return The effective loaded language, e.g. "en" for "en-US" - or nil on failure
|
||||
]]
|
||||
|
||||
---[[
|
||||
Return the translated value for a specific translation key.
|
||||
|
||||
@class function
|
||||
@name translate
|
||||
@param key Default translation text
|
||||
@return Translated string
|
||||
]]
|
||||
|
||||
---[[
|
||||
Return the translated value for a specific translation key and use it as sprintf pattern.
|
||||
|
||||
@class function
|
||||
@name translatef
|
||||
@param key Default translation text
|
||||
@param ... Format parameters
|
||||
@return Translated and formatted string
|
||||
]]
|
||||
|
||||
---[[
|
||||
Return all currently loaded translation strings as a key-value table. The key is the
|
||||
hexadecimal representation of the translation key while the value is the translated
|
||||
text content.
|
||||
|
||||
@class function
|
||||
@name dump
|
||||
@return Key-value translation string table.
|
||||
]]
|
@ -0,0 +1,508 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local os = require "os"
|
||||
local util = require "luci.util"
|
||||
local table = require "table"
|
||||
|
||||
|
||||
local setmetatable, rawget, rawset = setmetatable, rawget, rawset
|
||||
local require, getmetatable, assert = require, getmetatable, assert
|
||||
local error, pairs, ipairs, select = error, pairs, ipairs, select
|
||||
local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
|
||||
|
||||
-- The typical workflow for UCI is: Get a cursor instance from the
|
||||
-- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
|
||||
-- save the changes to the staging area via Cursor.save and finally
|
||||
-- Cursor.commit the data to the actual config files.
|
||||
-- LuCI then needs to Cursor.apply the changes so daemons etc. are
|
||||
-- reloaded.
|
||||
module "luci.model.uci"
|
||||
|
||||
local ERRSTR = {
|
||||
"Invalid command",
|
||||
"Invalid argument",
|
||||
"Method not found",
|
||||
"Entry not found",
|
||||
"No data",
|
||||
"Permission denied",
|
||||
"Timeout",
|
||||
"Not supported",
|
||||
"Unknown error",
|
||||
"Connection failed"
|
||||
}
|
||||
|
||||
local session_id = nil
|
||||
|
||||
local function call(cmd, args)
|
||||
if type(args) == "table" and session_id then
|
||||
args.ubus_rpc_session = session_id
|
||||
end
|
||||
return util.ubus("uci", cmd, args)
|
||||
end
|
||||
|
||||
|
||||
function cursor()
|
||||
return _M
|
||||
end
|
||||
|
||||
function cursor_state()
|
||||
return _M
|
||||
end
|
||||
|
||||
function substate(self)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function get_confdir(self)
|
||||
return "/etc/config"
|
||||
end
|
||||
|
||||
function get_savedir(self)
|
||||
return "/tmp/.uci"
|
||||
end
|
||||
|
||||
function get_session_id(self)
|
||||
return session_id
|
||||
end
|
||||
|
||||
function set_confdir(self, directory)
|
||||
return false
|
||||
end
|
||||
|
||||
function set_savedir(self, directory)
|
||||
return false
|
||||
end
|
||||
|
||||
function set_session_id(self, id)
|
||||
session_id = id
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function load(self, config)
|
||||
return true
|
||||
end
|
||||
|
||||
function save(self, config)
|
||||
return true
|
||||
end
|
||||
|
||||
function unload(self, config)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function changes(self, config)
|
||||
local rv, err = call("changes", { config = config })
|
||||
|
||||
if type(rv) == "table" and type(rv.changes) == "table" then
|
||||
return rv.changes
|
||||
elseif err then
|
||||
return nil, ERRSTR[err]
|
||||
else
|
||||
return { }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function revert(self, config)
|
||||
local _, err = call("revert", { config = config })
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
function commit(self, config)
|
||||
local _, err = call("commit", { config = config })
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
function apply(self, rollback)
|
||||
local _, err
|
||||
|
||||
if rollback then
|
||||
local sys = require "luci.sys"
|
||||
local conf = require "luci.config"
|
||||
local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 90) or 0
|
||||
|
||||
_, err = call("apply", {
|
||||
timeout = (timeout > 90) and timeout or 90,
|
||||
rollback = true
|
||||
})
|
||||
|
||||
if not err then
|
||||
local now = os.time()
|
||||
local token = sys.uniqueid(16)
|
||||
|
||||
util.ubus("session", "set", {
|
||||
ubus_rpc_session = "00000000000000000000000000000000",
|
||||
values = {
|
||||
rollback = {
|
||||
token = token,
|
||||
session = session_id,
|
||||
timeout = now + timeout
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return token
|
||||
end
|
||||
else
|
||||
_, err = call("changes", {})
|
||||
|
||||
if not err then
|
||||
if type(_) == "table" and type(_.changes) == "table" then
|
||||
local k, v
|
||||
for k, v in pairs(_.changes) do
|
||||
_, err = call("commit", { config = k })
|
||||
if err then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not err then
|
||||
_, err = call("apply", { rollback = false })
|
||||
end
|
||||
end
|
||||
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
function confirm(self, token)
|
||||
local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending()
|
||||
|
||||
if is_pending then
|
||||
if token ~= rollback_token then
|
||||
return false, "Permission denied"
|
||||
end
|
||||
|
||||
local _, err = util.ubus("uci", "confirm", {
|
||||
ubus_rpc_session = rollback_sid
|
||||
})
|
||||
|
||||
if not err then
|
||||
util.ubus("session", "set", {
|
||||
ubus_rpc_session = "00000000000000000000000000000000",
|
||||
values = { rollback = {} }
|
||||
})
|
||||
end
|
||||
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
return false, "No data"
|
||||
end
|
||||
|
||||
function rollback(self)
|
||||
local is_pending, time_remaining, rollback_sid = self:rollback_pending()
|
||||
|
||||
if is_pending then
|
||||
local _, err = util.ubus("uci", "rollback", {
|
||||
ubus_rpc_session = rollback_sid
|
||||
})
|
||||
|
||||
if not err then
|
||||
util.ubus("session", "set", {
|
||||
ubus_rpc_session = "00000000000000000000000000000000",
|
||||
values = { rollback = {} }
|
||||
})
|
||||
end
|
||||
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
return false, "No data"
|
||||
end
|
||||
|
||||
function rollback_pending(self)
|
||||
local rv, err = util.ubus("session", "get", {
|
||||
ubus_rpc_session = "00000000000000000000000000000000",
|
||||
keys = { "rollback" }
|
||||
})
|
||||
|
||||
local now = os.time()
|
||||
|
||||
if type(rv) == "table" and
|
||||
type(rv.values) == "table" and
|
||||
type(rv.values.rollback) == "table" and
|
||||
type(rv.values.rollback.token) == "string" and
|
||||
type(rv.values.rollback.session) == "string" and
|
||||
type(rv.values.rollback.timeout) == "number" and
|
||||
rv.values.rollback.timeout > now
|
||||
then
|
||||
return true,
|
||||
rv.values.rollback.timeout - now,
|
||||
rv.values.rollback.session,
|
||||
rv.values.rollback.token
|
||||
end
|
||||
|
||||
return false, ERRSTR[err]
|
||||
end
|
||||
|
||||
|
||||
function foreach(self, config, stype, callback)
|
||||
if type(callback) == "function" then
|
||||
local rv, err = call("get", {
|
||||
config = config,
|
||||
type = stype
|
||||
})
|
||||
|
||||
if type(rv) == "table" and type(rv.values) == "table" then
|
||||
local sections = { }
|
||||
local res = false
|
||||
local index = 1
|
||||
|
||||
local _, section
|
||||
for _, section in pairs(rv.values) do
|
||||
section[".index"] = section[".index"] or index
|
||||
sections[index] = section
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
table.sort(sections, function(a, b)
|
||||
return a[".index"] < b[".index"]
|
||||
end)
|
||||
|
||||
for _, section in ipairs(sections) do
|
||||
local continue = callback(section)
|
||||
res = true
|
||||
if continue == false then
|
||||
break
|
||||
end
|
||||
end
|
||||
return res
|
||||
else
|
||||
return false, ERRSTR[err] or "No data"
|
||||
end
|
||||
else
|
||||
return false, "Invalid argument"
|
||||
end
|
||||
end
|
||||
|
||||
local function _get(self, operation, config, section, option)
|
||||
if section == nil then
|
||||
return nil
|
||||
elseif type(option) == "string" and option:byte(1) ~= 46 then
|
||||
local rv, err = call(operation, {
|
||||
config = config,
|
||||
section = section,
|
||||
option = option
|
||||
})
|
||||
|
||||
if type(rv) == "table" then
|
||||
return rv.value or nil
|
||||
elseif err then
|
||||
return false, ERRSTR[err]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
elseif option == nil then
|
||||
local values = self:get_all(config, section)
|
||||
if values then
|
||||
return values[".type"], values[".name"]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
else
|
||||
return false, "Invalid argument"
|
||||
end
|
||||
end
|
||||
|
||||
function get(self, ...)
|
||||
return _get(self, "get", ...)
|
||||
end
|
||||
|
||||
function get_state(self, ...)
|
||||
return _get(self, "state", ...)
|
||||
end
|
||||
|
||||
function get_all(self, config, section)
|
||||
local rv, err = call("get", {
|
||||
config = config,
|
||||
section = section
|
||||
})
|
||||
|
||||
if type(rv) == "table" and type(rv.values) == "table" then
|
||||
return rv.values
|
||||
elseif err then
|
||||
return false, ERRSTR[err]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function get_bool(self, ...)
|
||||
local val = self:get(...)
|
||||
return (val == "1" or val == "true" or val == "yes" or val == "on")
|
||||
end
|
||||
|
||||
function get_first(self, config, stype, option, default)
|
||||
local rv = default
|
||||
|
||||
self:foreach(config, stype, function(s)
|
||||
local val = not option and s[".name"] or s[option]
|
||||
|
||||
if type(default) == "number" then
|
||||
val = tonumber(val)
|
||||
elseif type(default) == "boolean" then
|
||||
val = (val == "1" or val == "true" or
|
||||
val == "yes" or val == "on")
|
||||
end
|
||||
|
||||
if val ~= nil then
|
||||
rv = val
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
return rv
|
||||
end
|
||||
|
||||
function get_list(self, config, section, option)
|
||||
if config and section and option then
|
||||
local val = self:get(config, section, option)
|
||||
return (type(val) == "table" and val or { val })
|
||||
end
|
||||
return { }
|
||||
end
|
||||
|
||||
|
||||
function section(self, config, stype, name, values)
|
||||
local rv, err = call("add", {
|
||||
config = config,
|
||||
type = stype,
|
||||
name = name,
|
||||
values = values
|
||||
})
|
||||
|
||||
if type(rv) == "table" then
|
||||
return rv.section
|
||||
elseif err then
|
||||
return false, ERRSTR[err]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function add(self, config, stype)
|
||||
return self:section(config, stype)
|
||||
end
|
||||
|
||||
function set(self, config, section, option, ...)
|
||||
if select('#', ...) == 0 then
|
||||
local sname, err = self:section(config, option, section)
|
||||
return (not not sname), err
|
||||
else
|
||||
local _, err = call("set", {
|
||||
config = config,
|
||||
section = section,
|
||||
values = { [option] = select(1, ...) }
|
||||
})
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
end
|
||||
|
||||
function set_list(self, config, section, option, value)
|
||||
if section == nil or option == nil then
|
||||
return false
|
||||
elseif value == nil or (type(value) == "table" and #value == 0) then
|
||||
return self:delete(config, section, option)
|
||||
elseif type(value) == "table" then
|
||||
return self:set(config, section, option, value)
|
||||
else
|
||||
return self:set(config, section, option, { value })
|
||||
end
|
||||
end
|
||||
|
||||
function tset(self, config, section, values)
|
||||
local _, err = call("set", {
|
||||
config = config,
|
||||
section = section,
|
||||
values = values
|
||||
})
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
function reorder(self, config, section, index)
|
||||
local sections
|
||||
|
||||
if type(section) == "string" and type(index) == "number" then
|
||||
local pos = 0
|
||||
|
||||
sections = { }
|
||||
|
||||
self:foreach(config, nil, function(s)
|
||||
if pos == index then
|
||||
pos = pos + 1
|
||||
end
|
||||
|
||||
if s[".name"] ~= section then
|
||||
pos = pos + 1
|
||||
sections[pos] = s[".name"]
|
||||
else
|
||||
sections[index + 1] = section
|
||||
end
|
||||
end)
|
||||
elseif type(section) == "table" then
|
||||
sections = section
|
||||
else
|
||||
return false, "Invalid argument"
|
||||
end
|
||||
|
||||
local _, err = call("order", {
|
||||
config = config,
|
||||
sections = sections
|
||||
})
|
||||
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
|
||||
function delete(self, config, section, option)
|
||||
local _, err = call("delete", {
|
||||
config = config,
|
||||
section = section,
|
||||
option = option
|
||||
})
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
||||
|
||||
function delete_all(self, config, stype, comparator)
|
||||
local _, err
|
||||
if type(comparator) == "table" then
|
||||
_, err = call("delete", {
|
||||
config = config,
|
||||
type = stype,
|
||||
match = comparator
|
||||
})
|
||||
elseif type(comparator) == "function" then
|
||||
local rv = call("get", {
|
||||
config = config,
|
||||
type = stype
|
||||
})
|
||||
|
||||
if type(rv) == "table" and type(rv.values) == "table" then
|
||||
local sname, section
|
||||
for sname, section in pairs(rv.values) do
|
||||
if comparator(section) then
|
||||
_, err = call("delete", {
|
||||
config = config,
|
||||
section = sname
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif comparator == nil then
|
||||
_, err = call("delete", {
|
||||
config = config,
|
||||
type = stype
|
||||
})
|
||||
else
|
||||
return false, "Invalid argument"
|
||||
end
|
||||
|
||||
return (err == nil), ERRSTR[err]
|
||||
end
|
@ -0,0 +1,369 @@
|
||||
---[[
|
||||
LuCI UCI model library.
|
||||
|
||||
The typical workflow for UCI is: Get a cursor instance from the
|
||||
cursor factory, modify data (via Cursor.add, Cursor.delete, etc.),
|
||||
save the changes to the staging area via Cursor.save and finally
|
||||
Cursor.commit the data to the actual config files.
|
||||
LuCI then needs to Cursor.apply the changes so daemons etc. are
|
||||
reloaded.
|
||||
@cstyle instance
|
||||
]]
|
||||
module "luci.model.uci"
|
||||
|
||||
---[[
|
||||
Create a new UCI-Cursor.
|
||||
|
||||
@class function
|
||||
@name cursor
|
||||
@return UCI-Cursor
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a new Cursor initialized to the state directory.
|
||||
|
||||
@class function
|
||||
@name cursor_state
|
||||
@return UCI cursor
|
||||
]]
|
||||
|
||||
---[[
|
||||
Applies UCI configuration changes.
|
||||
|
||||
If the rollback parameter is set to true, the apply function will invoke the
|
||||
rollback mechanism which causes the configuration to be automatically reverted
|
||||
if no confirm() call occurs within a certain timeout.
|
||||
|
||||
The current default timeout is 30s and can be increased using the
|
||||
"luci.apply.timeout" uci configuration key.
|
||||
|
||||
@class function
|
||||
@name Cursor.apply
|
||||
@param rollback Enable rollback mechanism
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Confirms UCI apply process.
|
||||
|
||||
If a previous UCI apply with rollback has been invoked using apply(true),
|
||||
this function confirms the process and cancels the pending rollback timer.
|
||||
|
||||
If no apply with rollback session is active, the function has no effect and
|
||||
returns with a "No data" error.
|
||||
|
||||
@class function
|
||||
@name Cursor.confirm
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Cancels UCI apply process.
|
||||
|
||||
If a previous UCI apply with rollback has been invoked using apply(true),
|
||||
this function cancels the process and rolls back the configuration to the
|
||||
pre-apply state.
|
||||
|
||||
If no apply with rollback session is active, the function has no effect and
|
||||
returns with a "No data" error.
|
||||
|
||||
@class function
|
||||
@name Cursor.rollback
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Checks whether a pending rollback is scheduled.
|
||||
|
||||
If a previous UCI apply with rollback has been invoked using apply(true),
|
||||
and has not been confirmed or rolled back yet, this function returns true
|
||||
and the remaining time until rollback in seconds. If no rollback is pending,
|
||||
the function returns false. On error, the function returns false and an
|
||||
additional string describing the error.
|
||||
|
||||
@class function
|
||||
@name Cursor.rollback_pending
|
||||
@return Boolean whether rollback is pending
|
||||
@return Remaining time in seconds
|
||||
]]
|
||||
|
||||
---[[
|
||||
Delete all sections of a given type that match certain criteria.
|
||||
|
||||
@class function
|
||||
@name Cursor.delete_all
|
||||
@param config UCI config
|
||||
@param type UCI section type
|
||||
@param comparator Function that will be called for each section and returns
|
||||
a boolean whether to delete the current section (optional)
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a new section and initialize it with data.
|
||||
|
||||
@class function
|
||||
@name Cursor.section
|
||||
@param config UCI config
|
||||
@param type UCI section type
|
||||
@param name UCI section name (optional)
|
||||
@param values Table of key - value pairs to initialize the section with
|
||||
@return Name of created section
|
||||
]]
|
||||
|
||||
---[[
|
||||
Updated the data of a section using data from a table.
|
||||
|
||||
@class function
|
||||
@name Cursor.tset
|
||||
@param config UCI config
|
||||
@param section UCI section name (optional)
|
||||
@param values Table of key - value pairs to update the section with
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get a boolean option and return it's value as true or false.
|
||||
|
||||
@class function
|
||||
@name Cursor.get_bool
|
||||
@param config UCI config
|
||||
@param section UCI section name
|
||||
@param option UCI option
|
||||
@return Boolean
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get an option or list and return values as table.
|
||||
|
||||
@class function
|
||||
@name Cursor.get_list
|
||||
@param config UCI config
|
||||
@param section UCI section name
|
||||
@param option UCI option
|
||||
@return table. If the option was not found, you will simply get an empty
|
||||
table.
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get the given option from the first section with the given type.
|
||||
|
||||
@class function
|
||||
@name Cursor.get_first
|
||||
@param config UCI config
|
||||
@param type UCI section type
|
||||
@param option UCI option (optional)
|
||||
@param default Default value (optional)
|
||||
@return UCI value
|
||||
]]
|
||||
|
||||
---[[
|
||||
Set given values as list. Setting a list option to an empty list
|
||||
has the same effect as deleting the option.
|
||||
|
||||
@class function
|
||||
@name Cursor.set_list
|
||||
@param config UCI config
|
||||
@param section UCI section name
|
||||
@param option UCI option
|
||||
@param value Value or table. Non-table values will be set as single
|
||||
item UCI list.
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Create a sub-state of this cursor.
|
||||
|
||||
The sub-state is tied to the parent cursor, means it the parent unloads or
|
||||
loads configs, the sub state will do so as well.
|
||||
|
||||
@class function
|
||||
@name Cursor.substate
|
||||
@return UCI state cursor tied to the parent cursor
|
||||
]]
|
||||
|
||||
---[[
|
||||
Add an anonymous section.
|
||||
|
||||
@class function
|
||||
@name Cursor.add
|
||||
@param config UCI config
|
||||
@param type UCI section type
|
||||
@return Name of created section
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get a table of saved but uncommitted changes.
|
||||
|
||||
@class function
|
||||
@name Cursor.changes
|
||||
@param config UCI config
|
||||
@return Table of changes
|
||||
@see Cursor.save
|
||||
]]
|
||||
|
||||
---[[
|
||||
Commit saved changes.
|
||||
|
||||
@class function
|
||||
@name Cursor.commit
|
||||
@param config UCI config
|
||||
@return Boolean whether operation succeeded
|
||||
@see Cursor.revert
|
||||
@see Cursor.save
|
||||
]]
|
||||
|
||||
---[[
|
||||
Deletes a section or an option.
|
||||
|
||||
@class function
|
||||
@name Cursor.delete
|
||||
@param config UCI config
|
||||
@param section UCI section name
|
||||
@param option UCI option (optional)
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Call a function for every section of a certain type.
|
||||
|
||||
@class function
|
||||
@name Cursor.foreach
|
||||
@param config UCI config
|
||||
@param type UCI section type
|
||||
@param callback Function to be called
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get a section type or an option
|
||||
|
||||
@class function
|
||||
@name Cursor.get
|
||||
@param config UCI config
|
||||
@param section UCI section name
|
||||
@param option UCI option (optional)
|
||||
@return UCI value
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get all sections of a config or all values of a section.
|
||||
|
||||
@class function
|
||||
@name Cursor.get_all
|
||||
@param config UCI config
|
||||
@param section UCI section name (optional)
|
||||
@return Table of UCI sections or table of UCI values
|
||||
]]
|
||||
|
||||
---[[
|
||||
Manually load a config.
|
||||
|
||||
@class function
|
||||
@name Cursor.load
|
||||
@param config UCI config
|
||||
@return Boolean whether operation succeeded
|
||||
@see Cursor.save
|
||||
@see Cursor.unload
|
||||
]]
|
||||
|
||||
---[[
|
||||
Revert saved but uncommitted changes.
|
||||
|
||||
@class function
|
||||
@name Cursor.revert
|
||||
@param config UCI config
|
||||
@return Boolean whether operation succeeded
|
||||
@see Cursor.commit
|
||||
@see Cursor.save
|
||||
]]
|
||||
|
||||
---[[
|
||||
Saves changes made to a config to make them committable.
|
||||
|
||||
@class function
|
||||
@name Cursor.save
|
||||
@param config UCI config
|
||||
@return Boolean whether operation succeeded
|
||||
@see Cursor.load
|
||||
@see Cursor.unload
|
||||
]]
|
||||
|
||||
---[[
|
||||
Set a value or create a named section.
|
||||
|
||||
When invoked with three arguments `config`, `sectionname`, `sectiontype`,
|
||||
then a named section of the given type is created.
|
||||
|
||||
When invoked with four arguments `config`, `sectionname`, `optionname` and
|
||||
`optionvalue` then the value of the specified option is set to the given value.
|
||||
|
||||
@class function
|
||||
@name Cursor.set
|
||||
@param config UCI config
|
||||
@param section UCI section name
|
||||
@param option UCI option or UCI section type
|
||||
@param value UCI value or nothing if you want to create a section
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get the configuration directory.
|
||||
|
||||
@class function
|
||||
@name Cursor.get_confdir
|
||||
@return Configuration directory
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get the directory for uncomitted changes.
|
||||
|
||||
@class function
|
||||
@name Cursor.get_savedir
|
||||
@return Save directory
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get the effective session ID.
|
||||
|
||||
@class function
|
||||
@name Cursor.get_session_id
|
||||
@return String containing the session ID
|
||||
]]
|
||||
|
||||
---[[
|
||||
Set the configuration directory.
|
||||
|
||||
@class function
|
||||
@name Cursor.set_confdir
|
||||
@param directory UCI configuration directory
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Set the directory for uncommitted changes.
|
||||
|
||||
@class function
|
||||
@name Cursor.set_savedir
|
||||
@param directory UCI changes directory
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Set the effective session ID.
|
||||
|
||||
@class function
|
||||
@name Cursor.set_session_id
|
||||
@param id String containing the session ID to set
|
||||
@return Boolean whether operation succeeded
|
||||
]]
|
||||
|
||||
---[[
|
||||
Discard changes made to a config.
|
||||
|
||||
@class function
|
||||
@name Cursor.unload
|
||||
@param config UCI config
|
||||
@return Boolean whether operation succeeded
|
||||
@see Cursor.load
|
||||
@see Cursor.save
|
||||
]]
|
||||
|
@ -0,0 +1,73 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
exectime = os.clock()
|
||||
module("luci.sgi.cgi", package.seeall)
|
||||
local ltn12 = require("luci.ltn12")
|
||||
require("nixio.util")
|
||||
require("luci.http")
|
||||
require("luci.sys")
|
||||
require("luci.dispatcher")
|
||||
|
||||
-- Limited source to avoid endless blocking
|
||||
local function limitsource(handle, limit)
|
||||
limit = limit or 0
|
||||
local BLOCKSIZE = ltn12.BLOCKSIZE
|
||||
|
||||
return function()
|
||||
if limit < 1 then
|
||||
handle:close()
|
||||
return nil
|
||||
else
|
||||
local read = (limit > BLOCKSIZE) and BLOCKSIZE or limit
|
||||
limit = limit - read
|
||||
|
||||
local chunk = handle:read(read)
|
||||
if not chunk then handle:close() end
|
||||
return chunk
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function run()
|
||||
local r = luci.http.Request(
|
||||
luci.sys.getenv(),
|
||||
limitsource(io.stdin, tonumber(luci.sys.getenv("CONTENT_LENGTH"))),
|
||||
ltn12.sink.file(io.stderr)
|
||||
)
|
||||
|
||||
local x = coroutine.create(luci.dispatcher.httpdispatch)
|
||||
local hcache = ""
|
||||
local active = true
|
||||
|
||||
while coroutine.status(x) ~= "dead" do
|
||||
local res, id, data1, data2 = coroutine.resume(x, r)
|
||||
|
||||
if not res then
|
||||
print("Status: 500 Internal Server Error")
|
||||
print("Content-Type: text/plain\n")
|
||||
print(id)
|
||||
break;
|
||||
end
|
||||
|
||||
if active then
|
||||
if id == 1 then
|
||||
io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n")
|
||||
elseif id == 2 then
|
||||
hcache = hcache .. data1 .. ": " .. data2 .. "\r\n"
|
||||
elseif id == 3 then
|
||||
io.write(hcache)
|
||||
io.write("\r\n")
|
||||
elseif id == 4 then
|
||||
io.write(tostring(data1 or ""))
|
||||
elseif id == 5 then
|
||||
io.flush()
|
||||
io.close()
|
||||
active = false
|
||||
elseif id == 6 then
|
||||
data1:copyz(nixio.stdout, data2)
|
||||
data1:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,99 @@
|
||||
-- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
require "nixio.util"
|
||||
require "luci.http"
|
||||
require "luci.sys"
|
||||
require "luci.dispatcher"
|
||||
require "luci.ltn12"
|
||||
|
||||
function handle_request(env)
|
||||
exectime = os.clock()
|
||||
local renv = {
|
||||
CONTENT_LENGTH = env.CONTENT_LENGTH,
|
||||
CONTENT_TYPE = env.CONTENT_TYPE,
|
||||
REQUEST_METHOD = env.REQUEST_METHOD,
|
||||
REQUEST_URI = env.REQUEST_URI,
|
||||
PATH_INFO = env.PATH_INFO,
|
||||
SCRIPT_NAME = env.SCRIPT_NAME:gsub("/+$", ""),
|
||||
SCRIPT_FILENAME = env.SCRIPT_NAME,
|
||||
SERVER_PROTOCOL = env.SERVER_PROTOCOL,
|
||||
QUERY_STRING = env.QUERY_STRING,
|
||||
DOCUMENT_ROOT = env.DOCUMENT_ROOT,
|
||||
HTTPS = env.HTTPS,
|
||||
REDIRECT_STATUS = env.REDIRECT_STATUS,
|
||||
REMOTE_ADDR = env.REMOTE_ADDR,
|
||||
REMOTE_NAME = env.REMOTE_NAME,
|
||||
REMOTE_PORT = env.REMOTE_PORT,
|
||||
REMOTE_USER = env.REMOTE_USER,
|
||||
SERVER_ADDR = env.SERVER_ADDR,
|
||||
SERVER_NAME = env.SERVER_NAME,
|
||||
SERVER_PORT = env.SERVER_PORT
|
||||
}
|
||||
|
||||
local k, v
|
||||
for k, v in pairs(env.headers) do
|
||||
k = k:upper():gsub("%-", "_")
|
||||
renv["HTTP_" .. k] = v
|
||||
end
|
||||
|
||||
local len = tonumber(env.CONTENT_LENGTH) or 0
|
||||
local function recv()
|
||||
if len > 0 then
|
||||
local rlen, rbuf = uhttpd.recv(4096)
|
||||
if rlen >= 0 then
|
||||
len = len - rlen
|
||||
return rbuf
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local send = uhttpd.send
|
||||
|
||||
local req = luci.http.Request(
|
||||
renv, recv, luci.ltn12.sink.file(io.stderr)
|
||||
)
|
||||
|
||||
|
||||
local x = coroutine.create(luci.dispatcher.httpdispatch)
|
||||
local hcache = { }
|
||||
local active = true
|
||||
|
||||
while coroutine.status(x) ~= "dead" do
|
||||
local res, id, data1, data2 = coroutine.resume(x, req)
|
||||
|
||||
if not res then
|
||||
send("Status: 500 Internal Server Error\r\n")
|
||||
send("Content-Type: text/plain\r\n\r\n")
|
||||
send(tostring(id))
|
||||
break
|
||||
end
|
||||
|
||||
if active then
|
||||
if id == 1 then
|
||||
send("Status: ")
|
||||
send(tostring(data1))
|
||||
send(" ")
|
||||
send(tostring(data2))
|
||||
send("\r\n")
|
||||
elseif id == 2 then
|
||||
hcache[data1] = data2
|
||||
elseif id == 3 then
|
||||
for k, v in pairs(hcache) do
|
||||
send(tostring(k))
|
||||
send(": ")
|
||||
send(tostring(v))
|
||||
send("\r\n")
|
||||
end
|
||||
send("\r\n")
|
||||
elseif id == 4 then
|
||||
send(tostring(data1 or ""))
|
||||
elseif id == 5 then
|
||||
active = false
|
||||
elseif id == 6 then
|
||||
data1:copyz(nixio.stdout, data2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,6 @@
|
||||
-- Copyright 2009 Steven Barth <steven@midlink.org>
|
||||
-- Copyright 2009 Jo-Philipp Wich <jow@openwrt.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local util = require "luci.util"
|
||||
module("luci.store", util.threadlocal)
|
@ -0,0 +1,615 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local io = require "io"
|
||||
local os = require "os"
|
||||
local table = require "table"
|
||||
local nixio = require "nixio"
|
||||
local fs = require "nixio.fs"
|
||||
local uci = require "luci.model.uci"
|
||||
|
||||
local luci = {}
|
||||
luci.util = require "luci.util"
|
||||
luci.ip = require "luci.ip"
|
||||
|
||||
local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack =
|
||||
tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack
|
||||
|
||||
|
||||
module "luci.sys"
|
||||
|
||||
function call(...)
|
||||
return os.execute(...) / 256
|
||||
end
|
||||
|
||||
exec = luci.util.exec
|
||||
|
||||
-- containing the whole environment is returned otherwise this function returns
|
||||
-- the corresponding string value for the given name or nil if no such variable
|
||||
-- exists.
|
||||
getenv = nixio.getenv
|
||||
|
||||
function hostname(newname)
|
||||
if type(newname) == "string" and #newname > 0 then
|
||||
fs.writefile( "/proc/sys/kernel/hostname", newname )
|
||||
return newname
|
||||
else
|
||||
return nixio.uname().nodename
|
||||
end
|
||||
end
|
||||
|
||||
function httpget(url, stream, target)
|
||||
if not target then
|
||||
local source = stream and io.popen or luci.util.exec
|
||||
return source("wget -qO- %s" % luci.util.shellquote(url))
|
||||
else
|
||||
return os.execute("wget -qO %s %s" %
|
||||
{luci.util.shellquote(target), luci.util.shellquote(url)})
|
||||
end
|
||||
end
|
||||
|
||||
function reboot()
|
||||
return os.execute("reboot >/dev/null 2>&1")
|
||||
end
|
||||
|
||||
function syslog()
|
||||
return luci.util.exec("logread")
|
||||
end
|
||||
|
||||
function dmesg()
|
||||
return luci.util.exec("dmesg")
|
||||
end
|
||||
|
||||
function uniqueid(bytes)
|
||||
local rand = fs.readfile("/dev/urandom", bytes)
|
||||
return rand and nixio.bin.hexlify(rand)
|
||||
end
|
||||
|
||||
function uptime()
|
||||
return nixio.sysinfo().uptime
|
||||
end
|
||||
|
||||
|
||||
net = {}
|
||||
|
||||
local function _nethints(what, callback)
|
||||
local _, k, e, mac, ip, name, duid, iaid
|
||||
local cur = uci.cursor()
|
||||
local ifn = { }
|
||||
local hosts = { }
|
||||
local lookup = { }
|
||||
|
||||
local function _add(i, ...)
|
||||
local k = select(i, ...)
|
||||
if k then
|
||||
if not hosts[k] then hosts[k] = { } end
|
||||
hosts[k][1] = select(1, ...) or hosts[k][1]
|
||||
hosts[k][2] = select(2, ...) or hosts[k][2]
|
||||
hosts[k][3] = select(3, ...) or hosts[k][3]
|
||||
hosts[k][4] = select(4, ...) or hosts[k][4]
|
||||
end
|
||||
end
|
||||
|
||||
luci.ip.neighbors(nil, function(neigh)
|
||||
if neigh.mac and neigh.family == 4 then
|
||||
_add(what, neigh.mac:string(), neigh.dest:string(), nil, nil)
|
||||
elseif neigh.mac and neigh.family == 6 then
|
||||
_add(what, neigh.mac:string(), nil, neigh.dest:string(), nil)
|
||||
end
|
||||
end)
|
||||
|
||||
if fs.access("/etc/ethers") then
|
||||
for e in io.lines("/etc/ethers") do
|
||||
mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)")
|
||||
mac = luci.ip.checkmac(mac)
|
||||
if mac and name then
|
||||
if luci.ip.checkip4(name) then
|
||||
_add(what, mac, name, nil, nil)
|
||||
else
|
||||
_add(what, mac, nil, nil, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cur:foreach("dhcp", "dnsmasq",
|
||||
function(s)
|
||||
if s.leasefile and fs.access(s.leasefile) then
|
||||
for e in io.lines(s.leasefile) do
|
||||
mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
|
||||
mac = luci.ip.checkmac(mac)
|
||||
if mac and ip then
|
||||
_add(what, mac, ip, nil, name ~= "*" and name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
cur:foreach("dhcp", "odhcpd",
|
||||
function(s)
|
||||
if type(s.leasefile) == "string" and fs.access(s.leasefile) then
|
||||
for e in io.lines(s.leasefile) do
|
||||
duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+")
|
||||
mac = net.duid_to_mac(duid)
|
||||
if mac then
|
||||
if ip and iaid == "ipv4" then
|
||||
_add(what, mac, ip, nil, name ~= "*" and name)
|
||||
elseif ip then
|
||||
_add(what, mac, nil, ip, name ~= "*" and name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
cur:foreach("dhcp", "host",
|
||||
function(s)
|
||||
for mac in luci.util.imatch(s.mac) do
|
||||
mac = luci.ip.checkmac(mac)
|
||||
if mac then
|
||||
_add(what, mac, s.ip, nil, s.name)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
for _, e in ipairs(nixio.getifaddrs()) do
|
||||
if e.name ~= "lo" then
|
||||
ifn[e.name] = ifn[e.name] or { }
|
||||
if e.family == "packet" and e.addr and #e.addr == 17 then
|
||||
ifn[e.name][1] = e.addr:upper()
|
||||
elseif e.family == "inet" then
|
||||
ifn[e.name][2] = e.addr
|
||||
elseif e.family == "inet6" then
|
||||
ifn[e.name][3] = e.addr
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, e in pairs(ifn) do
|
||||
if e[what] and (e[2] or e[3]) then
|
||||
_add(what, e[1], e[2], e[3], e[4])
|
||||
end
|
||||
end
|
||||
|
||||
for _, e in pairs(hosts) do
|
||||
lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3])
|
||||
end
|
||||
|
||||
if #lookup > 0 then
|
||||
lookup = luci.util.ubus("network.rrdns", "lookup", {
|
||||
addrs = lookup,
|
||||
timeout = 250,
|
||||
limit = 1000
|
||||
}) or { }
|
||||
end
|
||||
|
||||
for _, e in luci.util.kspairs(hosts) do
|
||||
callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4])
|
||||
end
|
||||
end
|
||||
|
||||
-- Each entry contains the values in the following order:
|
||||
-- [ "mac", "name" ]
|
||||
function net.mac_hints(callback)
|
||||
if callback then
|
||||
_nethints(1, function(mac, v4, v6, name)
|
||||
name = name or v4
|
||||
if name and name ~= mac then
|
||||
callback(mac, name or v4)
|
||||
end
|
||||
end)
|
||||
else
|
||||
local rv = { }
|
||||
_nethints(1, function(mac, v4, v6, name)
|
||||
name = name or v4
|
||||
if name and name ~= mac then
|
||||
rv[#rv+1] = { mac, name or v4 }
|
||||
end
|
||||
end)
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
-- Each entry contains the values in the following order:
|
||||
-- [ "ip", "name" ]
|
||||
function net.ipv4_hints(callback)
|
||||
if callback then
|
||||
_nethints(2, function(mac, v4, v6, name)
|
||||
name = name or mac
|
||||
if name and name ~= v4 then
|
||||
callback(v4, name)
|
||||
end
|
||||
end)
|
||||
else
|
||||
local rv = { }
|
||||
_nethints(2, function(mac, v4, v6, name)
|
||||
name = name or mac
|
||||
if name and name ~= v4 then
|
||||
rv[#rv+1] = { v4, name }
|
||||
end
|
||||
end)
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
-- Each entry contains the values in the following order:
|
||||
-- [ "ip", "name" ]
|
||||
function net.ipv6_hints(callback)
|
||||
if callback then
|
||||
_nethints(3, function(mac, v4, v6, name)
|
||||
name = name or mac
|
||||
if name and name ~= v6 then
|
||||
callback(v6, name)
|
||||
end
|
||||
end)
|
||||
else
|
||||
local rv = { }
|
||||
_nethints(3, function(mac, v4, v6, name)
|
||||
name = name or mac
|
||||
if name and name ~= v6 then
|
||||
rv[#rv+1] = { v6, name }
|
||||
end
|
||||
end)
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
function net.host_hints(callback)
|
||||
if callback then
|
||||
_nethints(1, function(mac, v4, v6, name)
|
||||
if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
|
||||
callback(mac, v4, v6, name)
|
||||
end
|
||||
end)
|
||||
else
|
||||
local rv = { }
|
||||
_nethints(1, function(mac, v4, v6, name)
|
||||
if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then
|
||||
local e = { }
|
||||
if v4 then e.ipv4 = v4 end
|
||||
if v6 then e.ipv6 = v6 end
|
||||
if name then e.name = name end
|
||||
rv[mac] = e
|
||||
end
|
||||
end)
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
function net.conntrack(callback)
|
||||
local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack")
|
||||
if not ok or not nfct then
|
||||
return nil
|
||||
end
|
||||
|
||||
local line, connt = nil, (not callback) and { }
|
||||
for line in nfct do
|
||||
local fam, l3, l4, rest =
|
||||
line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(.+)$")
|
||||
|
||||
local timeout, tuples = rest:match("^(%d+) +(.+)$")
|
||||
|
||||
if not tuples then
|
||||
tuples = rest
|
||||
end
|
||||
|
||||
if fam and l3 and l4 and not tuples:match("^TIME_WAIT ") then
|
||||
l4 = nixio.getprotobynumber(l4)
|
||||
|
||||
local entry = {
|
||||
bytes = 0,
|
||||
packets = 0,
|
||||
layer3 = fam,
|
||||
layer4 = l4 and l4.name or "unknown",
|
||||
timeout = tonumber(timeout, 10)
|
||||
}
|
||||
|
||||
local key, val
|
||||
for key, val in tuples:gmatch("(%w+)=(%S+)") do
|
||||
if key == "bytes" or key == "packets" then
|
||||
entry[key] = entry[key] + tonumber(val, 10)
|
||||
elseif key == "src" or key == "dst" then
|
||||
if entry[key] == nil then
|
||||
entry[key] = luci.ip.new(val):string()
|
||||
end
|
||||
elseif key == "sport" or key == "dport" then
|
||||
if entry[key] == nil then
|
||||
entry[key] = val
|
||||
end
|
||||
elseif val then
|
||||
entry[key] = val
|
||||
end
|
||||
end
|
||||
|
||||
if callback then
|
||||
callback(entry)
|
||||
else
|
||||
connt[#connt+1] = entry
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return callback and true or connt
|
||||
end
|
||||
|
||||
function net.devices()
|
||||
local devs = {}
|
||||
local seen = {}
|
||||
for k, v in ipairs(nixio.getifaddrs()) do
|
||||
if v.name and not seen[v.name] then
|
||||
seen[v.name] = true
|
||||
devs[#devs+1] = v.name
|
||||
end
|
||||
end
|
||||
return devs
|
||||
end
|
||||
|
||||
function net.duid_to_mac(duid)
|
||||
local b1, b2, b3, b4, b5, b6
|
||||
|
||||
if type(duid) == "string" then
|
||||
-- DUID-LLT / Ethernet
|
||||
if #duid == 28 then
|
||||
b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$")
|
||||
|
||||
-- DUID-LL / Ethernet
|
||||
elseif #duid == 20 then
|
||||
b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
|
||||
|
||||
-- DUID-LL / Ethernet (Without Header)
|
||||
elseif #duid == 12 then
|
||||
b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
|
||||
end
|
||||
end
|
||||
|
||||
return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":"))
|
||||
end
|
||||
|
||||
process = {}
|
||||
|
||||
function process.info(key)
|
||||
local s = {uid = nixio.getuid(), gid = nixio.getgid()}
|
||||
return not key and s or s[key]
|
||||
end
|
||||
|
||||
function process.list()
|
||||
local data = {}
|
||||
local k
|
||||
local ps = luci.util.execi("/bin/busybox top -bn1")
|
||||
|
||||
if not ps then
|
||||
return
|
||||
end
|
||||
|
||||
for line in ps do
|
||||
local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match(
|
||||
"^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][<NW ][<N ]) +(%d+m?) +(%d+%%) +(%d+%%) +(.+)"
|
||||
)
|
||||
|
||||
local idx = tonumber(pid)
|
||||
if idx and not cmd:match("top %-bn1") then
|
||||
data[idx] = {
|
||||
['PID'] = pid,
|
||||
['PPID'] = ppid,
|
||||
['USER'] = user,
|
||||
['STAT'] = stat,
|
||||
['VSZ'] = vsz,
|
||||
['%MEM'] = mem,
|
||||
['%CPU'] = cpu,
|
||||
['COMMAND'] = cmd
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function process.setgroup(gid)
|
||||
return nixio.setgid(gid)
|
||||
end
|
||||
|
||||
function process.setuser(uid)
|
||||
return nixio.setuid(uid)
|
||||
end
|
||||
|
||||
process.signal = nixio.kill
|
||||
|
||||
local function xclose(fd)
|
||||
if fd and fd:fileno() > 2 then
|
||||
fd:close()
|
||||
end
|
||||
end
|
||||
|
||||
function process.exec(command, stdout, stderr, nowait)
|
||||
local out_r, out_w, err_r, err_w
|
||||
if stdout then out_r, out_w = nixio.pipe() end
|
||||
if stderr then err_r, err_w = nixio.pipe() end
|
||||
|
||||
local pid = nixio.fork()
|
||||
if pid == 0 then
|
||||
nixio.chdir("/")
|
||||
|
||||
local null = nixio.open("/dev/null", "w+")
|
||||
if null then
|
||||
nixio.dup(out_w or null, nixio.stdout)
|
||||
nixio.dup(err_w or null, nixio.stderr)
|
||||
nixio.dup(null, nixio.stdin)
|
||||
xclose(out_w)
|
||||
xclose(out_r)
|
||||
xclose(err_w)
|
||||
xclose(err_r)
|
||||
xclose(null)
|
||||
end
|
||||
|
||||
nixio.exec(unpack(command))
|
||||
os.exit(-1)
|
||||
end
|
||||
|
||||
local _, pfds, rv = nil, {}, { code = -1, pid = pid }
|
||||
|
||||
xclose(out_w)
|
||||
xclose(err_w)
|
||||
|
||||
if out_r then
|
||||
pfds[#pfds+1] = {
|
||||
fd = out_r,
|
||||
cb = type(stdout) == "function" and stdout,
|
||||
name = "stdout",
|
||||
events = nixio.poll_flags("in", "err", "hup")
|
||||
}
|
||||
end
|
||||
|
||||
if err_r then
|
||||
pfds[#pfds+1] = {
|
||||
fd = err_r,
|
||||
cb = type(stderr) == "function" and stderr,
|
||||
name = "stderr",
|
||||
events = nixio.poll_flags("in", "err", "hup")
|
||||
}
|
||||
end
|
||||
|
||||
while #pfds > 0 do
|
||||
local nfds, err = nixio.poll(pfds, -1)
|
||||
if not nfds and err ~= nixio.const.EINTR then
|
||||
break
|
||||
end
|
||||
|
||||
local i
|
||||
for i = #pfds, 1, -1 do
|
||||
local rfd = pfds[i]
|
||||
if rfd.revents > 0 then
|
||||
local chunk, err = rfd.fd:read(4096)
|
||||
if chunk and #chunk > 0 then
|
||||
if rfd.cb then
|
||||
rfd.cb(chunk)
|
||||
else
|
||||
rfd.buf = rfd.buf or {}
|
||||
rfd.buf[#rfd.buf + 1] = chunk
|
||||
end
|
||||
else
|
||||
table.remove(pfds, i)
|
||||
if rfd.buf then
|
||||
rv[rfd.name] = table.concat(rfd.buf, "")
|
||||
end
|
||||
rfd.fd:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not nowait then
|
||||
_, _, rv.code = nixio.waitpid(pid)
|
||||
end
|
||||
|
||||
return rv
|
||||
end
|
||||
|
||||
|
||||
user = {}
|
||||
|
||||
-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
|
||||
user.getuser = nixio.getpw
|
||||
|
||||
function user.getpasswd(username)
|
||||
local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username)
|
||||
local pwh = pwe and (pwe.pwdp or pwe.passwd)
|
||||
if not pwh or #pwh < 1 then
|
||||
return nil, pwe
|
||||
else
|
||||
return pwh, pwe
|
||||
end
|
||||
end
|
||||
|
||||
function user.checkpasswd(username, pass)
|
||||
local pwh, pwe = user.getpasswd(username)
|
||||
if pwe then
|
||||
return (pwh == nil or nixio.crypt(pass, pwh) == pwh)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function user.setpasswd(username, password)
|
||||
return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
|
||||
luci.util.shellquote(password),
|
||||
luci.util.shellquote(password),
|
||||
luci.util.shellquote(username)
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
wifi = {}
|
||||
|
||||
function wifi.getiwinfo(ifname)
|
||||
local ntm = require "luci.model.network"
|
||||
|
||||
ntm.init()
|
||||
|
||||
local wnet = ntm:get_wifinet(ifname)
|
||||
if wnet and wnet.iwinfo then
|
||||
return wnet.iwinfo
|
||||
end
|
||||
|
||||
local wdev = ntm:get_wifidev(ifname)
|
||||
if wdev and wdev.iwinfo then
|
||||
return wdev.iwinfo
|
||||
end
|
||||
|
||||
return { ifname = ifname }
|
||||
end
|
||||
|
||||
|
||||
init = {}
|
||||
init.dir = "/etc/init.d/"
|
||||
|
||||
function init.names()
|
||||
local names = { }
|
||||
for name in fs.glob(init.dir.."*") do
|
||||
names[#names+1] = fs.basename(name)
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
function init.index(name)
|
||||
name = fs.basename(name)
|
||||
if fs.access(init.dir..name) then
|
||||
return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null"
|
||||
%{ init.dir, name })
|
||||
end
|
||||
end
|
||||
|
||||
local function init_action(action, name)
|
||||
name = fs.basename(name)
|
||||
if fs.access(init.dir..name) then
|
||||
return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action })
|
||||
end
|
||||
end
|
||||
|
||||
function init.enabled(name)
|
||||
return (init_action("enabled", name) == 0)
|
||||
end
|
||||
|
||||
function init.enable(name)
|
||||
return (init_action("enable", name) == 0)
|
||||
end
|
||||
|
||||
function init.disable(name)
|
||||
return (init_action("disable", name) == 0)
|
||||
end
|
||||
|
||||
function init.start(name)
|
||||
return (init_action("start", name) == 0)
|
||||
end
|
||||
|
||||
function init.stop(name)
|
||||
return (init_action("stop", name) == 0)
|
||||
end
|
||||
|
||||
function init.restart(name)
|
||||
return (init_action("restart", name) == 0)
|
||||
end
|
||||
|
||||
function init.reload(name)
|
||||
return (init_action("reload", name) == 0)
|
||||
end
|
@ -0,0 +1,392 @@
|
||||
---[[
|
||||
LuCI Linux and POSIX system utilities.
|
||||
]]
|
||||
module "luci.sys"
|
||||
|
||||
---[[
|
||||
Execute a given shell command and return the error code
|
||||
|
||||
@class function
|
||||
@name call
|
||||
@param ... Command to call
|
||||
@return Error code of the command
|
||||
]]
|
||||
|
||||
---[[
|
||||
Execute a given shell command and capture its standard output
|
||||
|
||||
@class function
|
||||
@name exec
|
||||
@param command Command to call
|
||||
@return String containing the return the output of the command
|
||||
]]
|
||||
|
||||
---[[
|
||||
Retrieve information about currently mounted file systems.
|
||||
|
||||
@class function
|
||||
@name mounts
|
||||
@return Table containing mount information
|
||||
]]
|
||||
|
||||
---[[
|
||||
Retrieve environment variables. If no variable is given then a table
|
||||
|
||||
containing the whole environment is returned otherwise this function returns
|
||||
the corresponding string value for the given name or nil if no such variable
|
||||
exists.
|
||||
@class function
|
||||
@name getenv
|
||||
@param var Name of the environment variable to retrieve (optional)
|
||||
@return String containing the value of the specified variable
|
||||
@return Table containing all variables if no variable name is given
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get or set the current hostname.
|
||||
|
||||
@class function
|
||||
@name hostname
|
||||
@param String containing a new hostname to set (optional)
|
||||
@return String containing the system hostname
|
||||
]]
|
||||
|
||||
---[[
|
||||
Returns the contents of a documented referred by an URL.
|
||||
|
||||
@class function
|
||||
@name httpget
|
||||
@param url The URL to retrieve
|
||||
@param stream Return a stream instead of a buffer
|
||||
@param target Directly write to target file name
|
||||
@return String containing the contents of given the URL
|
||||
]]
|
||||
|
||||
---[[
|
||||
Initiate a system reboot.
|
||||
|
||||
@class function
|
||||
@name reboot
|
||||
@return Return value of os.execute()
|
||||
]]
|
||||
|
||||
---[[
|
||||
Retrieves the output of the "logread" command.
|
||||
|
||||
@class function
|
||||
@name syslog
|
||||
@return String containing the current log buffer
|
||||
]]
|
||||
|
||||
---[[
|
||||
Retrieves the output of the "dmesg" command.
|
||||
|
||||
@class function
|
||||
@name dmesg
|
||||
@return String containing the current log buffer
|
||||
]]
|
||||
|
||||
---[[
|
||||
Generates a random id with specified length.
|
||||
|
||||
@class function
|
||||
@name uniqueid
|
||||
@param bytes Number of bytes for the unique id
|
||||
@return String containing hex encoded id
|
||||
]]
|
||||
|
||||
---[[
|
||||
Returns the current system uptime stats.
|
||||
|
||||
@class function
|
||||
@name uptime
|
||||
@return String containing total uptime in seconds
|
||||
]]
|
||||
|
||||
---[[
|
||||
LuCI system utilities / network related functions.
|
||||
|
||||
@class module
|
||||
@name luci.sys.net
|
||||
]]
|
||||
|
||||
---[[
|
||||
Returns a two-dimensional table of mac address hints.
|
||||
|
||||
@class function
|
||||
@name net.mac_hints
|
||||
@return Table of table containing known hosts from various sources.
|
||||
Each entry contains the values in the following order:
|
||||
[ "mac", "name" ]
|
||||
]]
|
||||
|
||||
---[[
|
||||
Returns a two-dimensional table of IPv4 address hints.
|
||||
|
||||
@class function
|
||||
@name net.ipv4_hints
|
||||
@return Table of table containing known hosts from various sources.
|
||||
Each entry contains the values in the following order:
|
||||
[ "ip", "name" ]
|
||||
]]
|
||||
|
||||
---[[
|
||||
Returns a two-dimensional table of IPv6 address hints.
|
||||
|
||||
@class function
|
||||
@name net.ipv6_hints
|
||||
@return Table of table containing known hosts from various sources.
|
||||
Each entry contains the values in the following order:
|
||||
[ "ip", "name" ]
|
||||
]]
|
||||
|
||||
---[[
|
||||
Returns a two-dimensional table of host hints.
|
||||
|
||||
@class function
|
||||
@name net.host_hints
|
||||
@return Table of table containing known hosts from various sources,
|
||||
indexed by mac address. Each subtable contains at least one
|
||||
of the fields "name", "ipv4" or "ipv6".
|
||||
]]
|
||||
|
||||
---[[
|
||||
Returns conntrack information
|
||||
|
||||
@class function
|
||||
@name net.conntrack
|
||||
@return Table with the currently tracked IP connections
|
||||
]]
|
||||
|
||||
---[[
|
||||
Determine the names of available network interfaces.
|
||||
|
||||
@class function
|
||||
@name net.devices
|
||||
@return Table containing all current interface names
|
||||
]]
|
||||
|
||||
---[[
|
||||
LuCI system utilities / process related functions.
|
||||
|
||||
@class module
|
||||
@name luci.sys.process
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get the current process id.
|
||||
|
||||
@class function
|
||||
@name process.info
|
||||
@return Number containing the current pid
|
||||
]]
|
||||
|
||||
---[[
|
||||
Retrieve information about currently running processes.
|
||||
|
||||
@class function
|
||||
@name process.list
|
||||
@return Table containing process information
|
||||
]]
|
||||
|
||||
---[[
|
||||
Set the gid of a process identified by given pid.
|
||||
|
||||
@class function
|
||||
@name process.setgroup
|
||||
@param gid Number containing the Unix group id
|
||||
@return Boolean indicating successful operation
|
||||
@return String containing the error message if failed
|
||||
@return Number containing the error code if failed
|
||||
]]
|
||||
|
||||
---[[
|
||||
Set the uid of a process identified by given pid.
|
||||
|
||||
@class function
|
||||
@name process.setuser
|
||||
@param uid Number containing the Unix user id
|
||||
@return Boolean indicating successful operation
|
||||
@return String containing the error message if failed
|
||||
@return Number containing the error code if failed
|
||||
]]
|
||||
|
||||
---[[
|
||||
Send a signal to a process identified by given pid.
|
||||
|
||||
@class function
|
||||
@name process.signal
|
||||
@param pid Number containing the process id
|
||||
@param sig Signal to send (default: 15 [SIGTERM])
|
||||
@return Boolean indicating successful operation
|
||||
@return Number containing the error code if failed
|
||||
]]
|
||||
|
||||
---[[
|
||||
Execute a process, optionally capturing stdio.
|
||||
|
||||
Executes the process specified by the given argv vector, e.g.
|
||||
`{ "/bin/sh", "-c", "echo 1" }` and waits for it to terminate unless a true
|
||||
value has been passed for the "nowait" parameter.
|
||||
|
||||
When a function value is passed for the stdout or stderr arguments, the passed
|
||||
function is repeatedly called for each chunk read from the corresponding stdio
|
||||
stream. The read data is passed as string containing at most 4096 bytes at a
|
||||
time.
|
||||
|
||||
When a true, non-function value is passed for the stdout or stderr arguments,
|
||||
the data of the corresponding stdio stream is read into an internal string
|
||||
buffer and returned as "stdout" or "stderr" field respectively in the result
|
||||
table.
|
||||
|
||||
When a true value is passed to the nowait parameter, the function does not
|
||||
await process termination but returns as soon as all captured stdio streams
|
||||
have been closed or - if no streams are captured - immediately after launching
|
||||
the process.
|
||||
|
||||
@class function
|
||||
@name process.exec
|
||||
@param commend Table containing the argv vector to execute
|
||||
@param stdout Callback function or boolean to indicate capturing (optional)
|
||||
@param stderr Callback function or boolean to indicate capturing (optional)
|
||||
@param nowait Don't wait for process termination when true (optional)
|
||||
@return Table containing at least the fields "code" which holds the exit
|
||||
status of the invoked process or "-1" on error and "pid", which
|
||||
contains the process id assigned to the spawned process. When
|
||||
stdout and/or stderr capturing has been requested, it additionally
|
||||
contains "stdout" and "stderr" fields respectively, holding the
|
||||
captured stdio data as string.
|
||||
]]
|
||||
|
||||
---[[
|
||||
LuCI system utilities / user related functions.
|
||||
|
||||
@class module
|
||||
@name luci.sys.user
|
||||
]]
|
||||
|
||||
---[[
|
||||
Retrieve user information for given uid.
|
||||
|
||||
@class function
|
||||
@name getuser
|
||||
@param uid Number containing the Unix user id
|
||||
@return Table containing the following fields:
|
||||
-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" }
|
||||
]]
|
||||
|
||||
---[[
|
||||
Retrieve the current user password hash.
|
||||
|
||||
@class function
|
||||
@name user.getpasswd
|
||||
@param username String containing the username to retrieve the password for
|
||||
@return String containing the hash or nil if no password is set.
|
||||
@return Password database entry
|
||||
]]
|
||||
|
||||
---[[
|
||||
Test whether given string matches the password of a given system user.
|
||||
|
||||
@class function
|
||||
@name user.checkpasswd
|
||||
@param username String containing the Unix user name
|
||||
@param pass String containing the password to compare
|
||||
@return Boolean indicating whether the passwords are equal
|
||||
]]
|
||||
|
||||
---[[
|
||||
Change the password of given user.
|
||||
|
||||
@class function
|
||||
@name user.setpasswd
|
||||
@param username String containing the Unix user name
|
||||
@param password String containing the password to compare
|
||||
@return Number containing 0 on success and >= 1 on error
|
||||
]]
|
||||
|
||||
---[[
|
||||
LuCI system utilities / wifi related functions.
|
||||
|
||||
@class module
|
||||
@name luci.sys.wifi
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get wireless information for given interface.
|
||||
|
||||
@class function
|
||||
@name wifi.getiwinfo
|
||||
@param ifname String containing the interface name
|
||||
@return A wrapped iwinfo object instance
|
||||
]]
|
||||
|
||||
---[[
|
||||
LuCI system utilities / init related functions.
|
||||
|
||||
@class module
|
||||
@name luci.sys.init
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get the names of all installed init scripts
|
||||
|
||||
@class function
|
||||
@name init.names
|
||||
@return Table containing the names of all inistalled init scripts
|
||||
]]
|
||||
|
||||
---[[
|
||||
Get the index of he given init script
|
||||
|
||||
@class function
|
||||
@name init.index
|
||||
@param name Name of the init script
|
||||
@return Numeric index value
|
||||
]]
|
||||
|
||||
---[[
|
||||
Test whether the given init script is enabled
|
||||
|
||||
@class function
|
||||
@name init.enabled
|
||||
@param name Name of the init script
|
||||
@return Boolean indicating whether init is enabled
|
||||
]]
|
||||
|
||||
---[[
|
||||
Enable the given init script
|
||||
|
||||
@class function
|
||||
@name init.enable
|
||||
@param name Name of the init script
|
||||
@return Boolean indicating success
|
||||
]]
|
||||
|
||||
---[[
|
||||
Disable the given init script
|
||||
|
||||
@class function
|
||||
@name init.disable
|
||||
@param name Name of the init script
|
||||
@return Boolean indicating success
|
||||
]]
|
||||
|
||||
---[[
|
||||
Start the given init script
|
||||
|
||||
@class function
|
||||
@name init.start
|
||||
@param name Name of the init script
|
||||
@return Boolean indicating success
|
||||
]]
|
||||
|
||||
---[[
|
||||
Stop the given init script
|
||||
|
||||
@class function
|
||||
@name init.stop
|
||||
@param name Name of the init script
|
||||
@return Boolean indicating success
|
||||
]]
|
||||
|
@ -0,0 +1,19 @@
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local setmetatable, require, rawget, rawset = setmetatable, require, rawget, rawset
|
||||
|
||||
module "luci.sys.zoneinfo"
|
||||
|
||||
setmetatable(_M, {
|
||||
__index = function(t, k)
|
||||
if k == "TZ" and not rawget(t, k) then
|
||||
local m = require "luci.sys.zoneinfo.tzdata"
|
||||
rawset(t, k, rawget(m, k))
|
||||
elseif k == "OFFSET" and not rawget(t, k) then
|
||||
local m = require "luci.sys.zoneinfo.tzoffset"
|
||||
rawset(t, k, rawget(m, k))
|
||||
end
|
||||
|
||||
return rawget(t, k)
|
||||
end
|
||||
})
|
@ -0,0 +1,451 @@
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
module "luci.sys.zoneinfo.tzdata"
|
||||
|
||||
TZ = {
|
||||
{ 'Africa/Abidjan', 'GMT0' },
|
||||
{ 'Africa/Accra', 'GMT0' },
|
||||
{ 'Africa/Addis Ababa', 'EAT-3' },
|
||||
{ 'Africa/Algiers', 'CET-1' },
|
||||
{ 'Africa/Asmara', 'EAT-3' },
|
||||
{ 'Africa/Bamako', 'GMT0' },
|
||||
{ 'Africa/Bangui', 'WAT-1' },
|
||||
{ 'Africa/Banjul', 'GMT0' },
|
||||
{ 'Africa/Bissau', 'GMT0' },
|
||||
{ 'Africa/Blantyre', 'CAT-2' },
|
||||
{ 'Africa/Brazzaville', 'WAT-1' },
|
||||
{ 'Africa/Bujumbura', 'CAT-2' },
|
||||
{ 'Africa/Cairo', 'EET-2EEST,M4.5.5/0,M10.5.4/24' },
|
||||
{ 'Africa/Casablanca', '<+01>-1' },
|
||||
{ 'Africa/Ceuta', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Africa/Conakry', 'GMT0' },
|
||||
{ 'Africa/Dakar', 'GMT0' },
|
||||
{ 'Africa/Dar es Salaam', 'EAT-3' },
|
||||
{ 'Africa/Djibouti', 'EAT-3' },
|
||||
{ 'Africa/Douala', 'WAT-1' },
|
||||
{ 'Africa/El Aaiun', '<+01>-1' },
|
||||
{ 'Africa/Freetown', 'GMT0' },
|
||||
{ 'Africa/Gaborone', 'CAT-2' },
|
||||
{ 'Africa/Harare', 'CAT-2' },
|
||||
{ 'Africa/Johannesburg', 'SAST-2' },
|
||||
{ 'Africa/Juba', 'CAT-2' },
|
||||
{ 'Africa/Kampala', 'EAT-3' },
|
||||
{ 'Africa/Khartoum', 'CAT-2' },
|
||||
{ 'Africa/Kigali', 'CAT-2' },
|
||||
{ 'Africa/Kinshasa', 'WAT-1' },
|
||||
{ 'Africa/Lagos', 'WAT-1' },
|
||||
{ 'Africa/Libreville', 'WAT-1' },
|
||||
{ 'Africa/Lome', 'GMT0' },
|
||||
{ 'Africa/Luanda', 'WAT-1' },
|
||||
{ 'Africa/Lubumbashi', 'CAT-2' },
|
||||
{ 'Africa/Lusaka', 'CAT-2' },
|
||||
{ 'Africa/Malabo', 'WAT-1' },
|
||||
{ 'Africa/Maputo', 'CAT-2' },
|
||||
{ 'Africa/Maseru', 'SAST-2' },
|
||||
{ 'Africa/Mbabane', 'SAST-2' },
|
||||
{ 'Africa/Mogadishu', 'EAT-3' },
|
||||
{ 'Africa/Monrovia', 'GMT0' },
|
||||
{ 'Africa/Nairobi', 'EAT-3' },
|
||||
{ 'Africa/Ndjamena', 'WAT-1' },
|
||||
{ 'Africa/Niamey', 'WAT-1' },
|
||||
{ 'Africa/Nouakchott', 'GMT0' },
|
||||
{ 'Africa/Ouagadougou', 'GMT0' },
|
||||
{ 'Africa/Porto-Novo', 'WAT-1' },
|
||||
{ 'Africa/Sao Tome', 'GMT0' },
|
||||
{ 'Africa/Tripoli', 'EET-2' },
|
||||
{ 'Africa/Tunis', 'CET-1' },
|
||||
{ 'Africa/Windhoek', 'CAT-2' },
|
||||
{ 'America/Adak', 'HST10HDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Anchorage', 'AKST9AKDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Anguilla', 'AST4' },
|
||||
{ 'America/Antigua', 'AST4' },
|
||||
{ 'America/Araguaina', '<-03>3' },
|
||||
{ 'America/Argentina/Buenos Aires', '<-03>3' },
|
||||
{ 'America/Argentina/Catamarca', '<-03>3' },
|
||||
{ 'America/Argentina/Cordoba', '<-03>3' },
|
||||
{ 'America/Argentina/Jujuy', '<-03>3' },
|
||||
{ 'America/Argentina/La Rioja', '<-03>3' },
|
||||
{ 'America/Argentina/Mendoza', '<-03>3' },
|
||||
{ 'America/Argentina/Rio Gallegos', '<-03>3' },
|
||||
{ 'America/Argentina/Salta', '<-03>3' },
|
||||
{ 'America/Argentina/San Juan', '<-03>3' },
|
||||
{ 'America/Argentina/San Luis', '<-03>3' },
|
||||
{ 'America/Argentina/Tucuman', '<-03>3' },
|
||||
{ 'America/Argentina/Ushuaia', '<-03>3' },
|
||||
{ 'America/Aruba', 'AST4' },
|
||||
{ 'America/Asuncion', '<-04>4<-03>,M10.1.0/0,M3.4.0/0' },
|
||||
{ 'America/Atikokan', 'EST5' },
|
||||
{ 'America/Bahia', '<-03>3' },
|
||||
{ 'America/Bahia Banderas', 'CST6' },
|
||||
{ 'America/Barbados', 'AST4' },
|
||||
{ 'America/Belem', '<-03>3' },
|
||||
{ 'America/Belize', 'CST6' },
|
||||
{ 'America/Blanc-Sablon', 'AST4' },
|
||||
{ 'America/Boa Vista', '<-04>4' },
|
||||
{ 'America/Bogota', '<-05>5' },
|
||||
{ 'America/Boise', 'MST7MDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Cambridge Bay', 'MST7MDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Campo Grande', '<-04>4' },
|
||||
{ 'America/Cancun', 'EST5' },
|
||||
{ 'America/Caracas', '<-04>4' },
|
||||
{ 'America/Cayenne', '<-03>3' },
|
||||
{ 'America/Cayman', 'EST5' },
|
||||
{ 'America/Chicago', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Chihuahua', 'CST6' },
|
||||
{ 'America/Ciudad Juarez', 'MST7MDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Costa Rica', 'CST6' },
|
||||
{ 'America/Creston', 'MST7' },
|
||||
{ 'America/Cuiaba', '<-04>4' },
|
||||
{ 'America/Curacao', 'AST4' },
|
||||
{ 'America/Danmarkshavn', 'GMT0' },
|
||||
{ 'America/Dawson', 'MST7' },
|
||||
{ 'America/Dawson Creek', 'MST7' },
|
||||
{ 'America/Denver', 'MST7MDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Detroit', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Dominica', 'AST4' },
|
||||
{ 'America/Edmonton', 'MST7MDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Eirunepe', '<-05>5' },
|
||||
{ 'America/El Salvador', 'CST6' },
|
||||
{ 'America/Fort Nelson', 'MST7' },
|
||||
{ 'America/Fortaleza', '<-03>3' },
|
||||
{ 'America/Glace Bay', 'AST4ADT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Goose Bay', 'AST4ADT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Grand Turk', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Grenada', 'AST4' },
|
||||
{ 'America/Guadeloupe', 'AST4' },
|
||||
{ 'America/Guatemala', 'CST6' },
|
||||
{ 'America/Guayaquil', '<-05>5' },
|
||||
{ 'America/Guyana', '<-04>4' },
|
||||
{ 'America/Halifax', 'AST4ADT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Havana', 'CST5CDT,M3.2.0/0,M11.1.0/1' },
|
||||
{ 'America/Hermosillo', 'MST7' },
|
||||
{ 'America/Indiana/Indianapolis', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Indiana/Knox', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Indiana/Marengo', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Indiana/Petersburg', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Indiana/Tell City', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Indiana/Vevay', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Indiana/Vincennes', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Indiana/Winamac', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Inuvik', 'MST7MDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Iqaluit', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Jamaica', 'EST5' },
|
||||
{ 'America/Juneau', 'AKST9AKDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Kentucky/Louisville', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Kentucky/Monticello', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Kralendijk', 'AST4' },
|
||||
{ 'America/La Paz', '<-04>4' },
|
||||
{ 'America/Lima', '<-05>5' },
|
||||
{ 'America/Los Angeles', 'PST8PDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Lower Princes', 'AST4' },
|
||||
{ 'America/Maceio', '<-03>3' },
|
||||
{ 'America/Managua', 'CST6' },
|
||||
{ 'America/Manaus', '<-04>4' },
|
||||
{ 'America/Marigot', 'AST4' },
|
||||
{ 'America/Martinique', 'AST4' },
|
||||
{ 'America/Matamoros', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Mazatlan', 'MST7' },
|
||||
{ 'America/Menominee', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Merida', 'CST6' },
|
||||
{ 'America/Metlakatla', 'AKST9AKDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Mexico City', 'CST6' },
|
||||
{ 'America/Miquelon', '<-03>3<-02>,M3.2.0,M11.1.0' },
|
||||
{ 'America/Moncton', 'AST4ADT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Monterrey', 'CST6' },
|
||||
{ 'America/Montevideo', '<-03>3' },
|
||||
{ 'America/Montserrat', 'AST4' },
|
||||
{ 'America/Nassau', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/New York', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Nome', 'AKST9AKDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Noronha', '<-02>2' },
|
||||
{ 'America/North Dakota/Beulah', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/North Dakota/Center', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/North Dakota/New Salem', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Nuuk', '<-02>2<-01>,M3.5.0/-1,M10.5.0/0' },
|
||||
{ 'America/Ojinaga', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Panama', 'EST5' },
|
||||
{ 'America/Paramaribo', '<-03>3' },
|
||||
{ 'America/Phoenix', 'MST7' },
|
||||
{ 'America/Port of Spain', 'AST4' },
|
||||
{ 'America/Port-au-Prince', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Porto Velho', '<-04>4' },
|
||||
{ 'America/Puerto Rico', 'AST4' },
|
||||
{ 'America/Punta Arenas', '<-03>3' },
|
||||
{ 'America/Rankin Inlet', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Recife', '<-03>3' },
|
||||
{ 'America/Regina', 'CST6' },
|
||||
{ 'America/Resolute', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Rio Branco', '<-05>5' },
|
||||
{ 'America/Santarem', '<-03>3' },
|
||||
{ 'America/Santiago', '<-04>4<-03>,M9.1.6/24,M4.1.6/24' },
|
||||
{ 'America/Santo Domingo', 'AST4' },
|
||||
{ 'America/Sao Paulo', '<-03>3' },
|
||||
{ 'America/Scoresbysund', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' },
|
||||
{ 'America/Sitka', 'AKST9AKDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/St Barthelemy', 'AST4' },
|
||||
{ 'America/St Johns', 'NST3:30NDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/St Kitts', 'AST4' },
|
||||
{ 'America/St Lucia', 'AST4' },
|
||||
{ 'America/St Thomas', 'AST4' },
|
||||
{ 'America/St Vincent', 'AST4' },
|
||||
{ 'America/Swift Current', 'CST6' },
|
||||
{ 'America/Tegucigalpa', 'CST6' },
|
||||
{ 'America/Thule', 'AST4ADT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Tijuana', 'PST8PDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Toronto', 'EST5EDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Tortola', 'AST4' },
|
||||
{ 'America/Vancouver', 'PST8PDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Whitehorse', 'MST7' },
|
||||
{ 'America/Winnipeg', 'CST6CDT,M3.2.0,M11.1.0' },
|
||||
{ 'America/Yakutat', 'AKST9AKDT,M3.2.0,M11.1.0' },
|
||||
{ 'Antarctica/Casey', '<+11>-11' },
|
||||
{ 'Antarctica/Davis', '<+07>-7' },
|
||||
{ 'Antarctica/DumontDUrville', '<+10>-10' },
|
||||
{ 'Antarctica/Macquarie', 'AEST-10AEDT,M10.1.0,M4.1.0/3' },
|
||||
{ 'Antarctica/Mawson', '<+05>-5' },
|
||||
{ 'Antarctica/McMurdo', 'NZST-12NZDT,M9.5.0,M4.1.0/3' },
|
||||
{ 'Antarctica/Palmer', '<-03>3' },
|
||||
{ 'Antarctica/Rothera', '<-03>3' },
|
||||
{ 'Antarctica/Syowa', '<+03>-3' },
|
||||
{ 'Antarctica/Troll', '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3' },
|
||||
{ 'Antarctica/Vostok', '<+06>-6' },
|
||||
{ 'Arctic/Longyearbyen', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Asia/Aden', '<+03>-3' },
|
||||
{ 'Asia/Almaty', '<+06>-6' },
|
||||
{ 'Asia/Amman', '<+03>-3' },
|
||||
{ 'Asia/Anadyr', '<+12>-12' },
|
||||
{ 'Asia/Aqtau', '<+05>-5' },
|
||||
{ 'Asia/Aqtobe', '<+05>-5' },
|
||||
{ 'Asia/Ashgabat', '<+05>-5' },
|
||||
{ 'Asia/Atyrau', '<+05>-5' },
|
||||
{ 'Asia/Baghdad', '<+03>-3' },
|
||||
{ 'Asia/Bahrain', '<+03>-3' },
|
||||
{ 'Asia/Baku', '<+04>-4' },
|
||||
{ 'Asia/Bangkok', '<+07>-7' },
|
||||
{ 'Asia/Barnaul', '<+07>-7' },
|
||||
{ 'Asia/Beirut', 'EET-2EEST,M3.5.0/0,M10.5.0/0' },
|
||||
{ 'Asia/Bishkek', '<+06>-6' },
|
||||
{ 'Asia/Brunei', '<+08>-8' },
|
||||
{ 'Asia/Chita', '<+09>-9' },
|
||||
{ 'Asia/Choibalsan', '<+08>-8' },
|
||||
{ 'Asia/Colombo', '<+0530>-5:30' },
|
||||
{ 'Asia/Damascus', '<+03>-3' },
|
||||
{ 'Asia/Dhaka', '<+06>-6' },
|
||||
{ 'Asia/Dili', '<+09>-9' },
|
||||
{ 'Asia/Dubai', '<+04>-4' },
|
||||
{ 'Asia/Dushanbe', '<+05>-5' },
|
||||
{ 'Asia/Famagusta', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Asia/Gaza', 'EET-2EEST,M3.4.4/50,M10.4.4/50' },
|
||||
{ 'Asia/Hebron', 'EET-2EEST,M3.4.4/50,M10.4.4/50' },
|
||||
{ 'Asia/Ho Chi Minh', '<+07>-7' },
|
||||
{ 'Asia/Hong Kong', 'HKT-8' },
|
||||
{ 'Asia/Hovd', '<+07>-7' },
|
||||
{ 'Asia/Irkutsk', '<+08>-8' },
|
||||
{ 'Asia/Jakarta', 'WIB-7' },
|
||||
{ 'Asia/Jayapura', 'WIT-9' },
|
||||
{ 'Asia/Jerusalem', 'IST-2IDT,M3.4.4/26,M10.5.0' },
|
||||
{ 'Asia/Kabul', '<+0430>-4:30' },
|
||||
{ 'Asia/Kamchatka', '<+12>-12' },
|
||||
{ 'Asia/Karachi', 'PKT-5' },
|
||||
{ 'Asia/Kathmandu', '<+0545>-5:45' },
|
||||
{ 'Asia/Khandyga', '<+09>-9' },
|
||||
{ 'Asia/Kolkata', 'IST-5:30' },
|
||||
{ 'Asia/Krasnoyarsk', '<+07>-7' },
|
||||
{ 'Asia/Kuala Lumpur', '<+08>-8' },
|
||||
{ 'Asia/Kuching', '<+08>-8' },
|
||||
{ 'Asia/Kuwait', '<+03>-3' },
|
||||
{ 'Asia/Macau', 'CST-8' },
|
||||
{ 'Asia/Magadan', '<+11>-11' },
|
||||
{ 'Asia/Makassar', 'WITA-8' },
|
||||
{ 'Asia/Manila', 'PST-8' },
|
||||
{ 'Asia/Muscat', '<+04>-4' },
|
||||
{ 'Asia/Nicosia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Asia/Novokuznetsk', '<+07>-7' },
|
||||
{ 'Asia/Novosibirsk', '<+07>-7' },
|
||||
{ 'Asia/Omsk', '<+06>-6' },
|
||||
{ 'Asia/Oral', '<+05>-5' },
|
||||
{ 'Asia/Phnom Penh', '<+07>-7' },
|
||||
{ 'Asia/Pontianak', 'WIB-7' },
|
||||
{ 'Asia/Pyongyang', 'KST-9' },
|
||||
{ 'Asia/Qatar', '<+03>-3' },
|
||||
{ 'Asia/Qostanay', '<+06>-6' },
|
||||
{ 'Asia/Qyzylorda', '<+05>-5' },
|
||||
{ 'Asia/Riyadh', '<+03>-3' },
|
||||
{ 'Asia/Sakhalin', '<+11>-11' },
|
||||
{ 'Asia/Samarkand', '<+05>-5' },
|
||||
{ 'Asia/Seoul', 'KST-9' },
|
||||
{ 'Asia/Shanghai', 'CST-8' },
|
||||
{ 'Asia/Singapore', '<+08>-8' },
|
||||
{ 'Asia/Srednekolymsk', '<+11>-11' },
|
||||
{ 'Asia/Taipei', 'CST-8' },
|
||||
{ 'Asia/Tashkent', '<+05>-5' },
|
||||
{ 'Asia/Tbilisi', '<+04>-4' },
|
||||
{ 'Asia/Tehran', '<+0330>-3:30' },
|
||||
{ 'Asia/Thimphu', '<+06>-6' },
|
||||
{ 'Asia/Tokyo', 'JST-9' },
|
||||
{ 'Asia/Tomsk', '<+07>-7' },
|
||||
{ 'Asia/Ulaanbaatar', '<+08>-8' },
|
||||
{ 'Asia/Urumqi', '<+06>-6' },
|
||||
{ 'Asia/Ust-Nera', '<+10>-10' },
|
||||
{ 'Asia/Vientiane', '<+07>-7' },
|
||||
{ 'Asia/Vladivostok', '<+10>-10' },
|
||||
{ 'Asia/Yakutsk', '<+09>-9' },
|
||||
{ 'Asia/Yangon', '<+0630>-6:30' },
|
||||
{ 'Asia/Yekaterinburg', '<+05>-5' },
|
||||
{ 'Asia/Yerevan', '<+04>-4' },
|
||||
{ 'Atlantic/Azores', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' },
|
||||
{ 'Atlantic/Bermuda', 'AST4ADT,M3.2.0,M11.1.0' },
|
||||
{ 'Atlantic/Canary', 'WET0WEST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Atlantic/Cape Verde', '<-01>1' },
|
||||
{ 'Atlantic/Faroe', 'WET0WEST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Atlantic/Madeira', 'WET0WEST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Atlantic/Reykjavik', 'GMT0' },
|
||||
{ 'Atlantic/South Georgia', '<-02>2' },
|
||||
{ 'Atlantic/St Helena', 'GMT0' },
|
||||
{ 'Atlantic/Stanley', '<-03>3' },
|
||||
{ 'Australia/Adelaide', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' },
|
||||
{ 'Australia/Brisbane', 'AEST-10' },
|
||||
{ 'Australia/Broken Hill', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' },
|
||||
{ 'Australia/Darwin', 'ACST-9:30' },
|
||||
{ 'Australia/Eucla', '<+0845>-8:45' },
|
||||
{ 'Australia/Hobart', 'AEST-10AEDT,M10.1.0,M4.1.0/3' },
|
||||
{ 'Australia/Lindeman', 'AEST-10' },
|
||||
{ 'Australia/Lord Howe', '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0' },
|
||||
{ 'Australia/Melbourne', 'AEST-10AEDT,M10.1.0,M4.1.0/3' },
|
||||
{ 'Australia/Perth', 'AWST-8' },
|
||||
{ 'Australia/Sydney', 'AEST-10AEDT,M10.1.0,M4.1.0/3' },
|
||||
{ 'Etc/GMT', 'GMT0' },
|
||||
{ 'Etc/GMT+1', '<-01>1' },
|
||||
{ 'Etc/GMT+10', '<-10>10' },
|
||||
{ 'Etc/GMT+11', '<-11>11' },
|
||||
{ 'Etc/GMT+12', '<-12>12' },
|
||||
{ 'Etc/GMT+2', '<-02>2' },
|
||||
{ 'Etc/GMT+3', '<-03>3' },
|
||||
{ 'Etc/GMT+4', '<-04>4' },
|
||||
{ 'Etc/GMT+5', '<-05>5' },
|
||||
{ 'Etc/GMT+6', '<-06>6' },
|
||||
{ 'Etc/GMT+7', '<-07>7' },
|
||||
{ 'Etc/GMT+8', '<-08>8' },
|
||||
{ 'Etc/GMT+9', '<-09>9' },
|
||||
{ 'Etc/GMT-1', '<+01>-1' },
|
||||
{ 'Etc/GMT-10', '<+10>-10' },
|
||||
{ 'Etc/GMT-11', '<+11>-11' },
|
||||
{ 'Etc/GMT-12', '<+12>-12' },
|
||||
{ 'Etc/GMT-13', '<+13>-13' },
|
||||
{ 'Etc/GMT-14', '<+14>-14' },
|
||||
{ 'Etc/GMT-2', '<+02>-2' },
|
||||
{ 'Etc/GMT-3', '<+03>-3' },
|
||||
{ 'Etc/GMT-4', '<+04>-4' },
|
||||
{ 'Etc/GMT-5', '<+05>-5' },
|
||||
{ 'Etc/GMT-6', '<+06>-6' },
|
||||
{ 'Etc/GMT-7', '<+07>-7' },
|
||||
{ 'Etc/GMT-8', '<+08>-8' },
|
||||
{ 'Etc/GMT-9', '<+09>-9' },
|
||||
{ 'Europe/Amsterdam', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Andorra', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Astrakhan', '<+04>-4' },
|
||||
{ 'Europe/Athens', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Belgrade', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Berlin', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Bratislava', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Brussels', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Bucharest', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Budapest', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Busingen', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Chisinau', 'EET-2EEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Copenhagen', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Dublin', 'IST-1GMT0,M10.5.0,M3.5.0/1' },
|
||||
{ 'Europe/Gibraltar', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Guernsey', 'GMT0BST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Europe/Helsinki', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Isle of Man', 'GMT0BST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Europe/Istanbul', '<+03>-3' },
|
||||
{ 'Europe/Jersey', 'GMT0BST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Europe/Kaliningrad', 'EET-2' },
|
||||
{ 'Europe/Kirov', 'MSK-3' },
|
||||
{ 'Europe/Kyiv', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Lisbon', 'WET0WEST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Europe/Ljubljana', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/London', 'GMT0BST,M3.5.0/1,M10.5.0' },
|
||||
{ 'Europe/Luxembourg', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Madrid', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Malta', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Mariehamn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Minsk', '<+03>-3' },
|
||||
{ 'Europe/Monaco', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Moscow', 'MSK-3' },
|
||||
{ 'Europe/Oslo', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Paris', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Podgorica', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Prague', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Riga', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Rome', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Samara', '<+04>-4' },
|
||||
{ 'Europe/San Marino', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Sarajevo', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Saratov', '<+04>-4' },
|
||||
{ 'Europe/Simferopol', 'MSK-3' },
|
||||
{ 'Europe/Skopje', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Sofia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Stockholm', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Tallinn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Tirane', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Ulyanovsk', '<+04>-4' },
|
||||
{ 'Europe/Vaduz', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Vatican', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Vienna', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Vilnius', 'EET-2EEST,M3.5.0/3,M10.5.0/4' },
|
||||
{ 'Europe/Volgograd', 'MSK-3' },
|
||||
{ 'Europe/Warsaw', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Zagreb', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Europe/Zurich', 'CET-1CEST,M3.5.0,M10.5.0/3' },
|
||||
{ 'Indian/Antananarivo', 'EAT-3' },
|
||||
{ 'Indian/Chagos', '<+06>-6' },
|
||||
{ 'Indian/Christmas', '<+07>-7' },
|
||||
{ 'Indian/Cocos', '<+0630>-6:30' },
|
||||
{ 'Indian/Comoro', 'EAT-3' },
|
||||
{ 'Indian/Kerguelen', '<+05>-5' },
|
||||
{ 'Indian/Mahe', '<+04>-4' },
|
||||
{ 'Indian/Maldives', '<+05>-5' },
|
||||
{ 'Indian/Mauritius', '<+04>-4' },
|
||||
{ 'Indian/Mayotte', 'EAT-3' },
|
||||
{ 'Indian/Reunion', '<+04>-4' },
|
||||
{ 'Pacific/Apia', '<+13>-13' },
|
||||
{ 'Pacific/Auckland', 'NZST-12NZDT,M9.5.0,M4.1.0/3' },
|
||||
{ 'Pacific/Bougainville', '<+11>-11' },
|
||||
{ 'Pacific/Chatham', '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45' },
|
||||
{ 'Pacific/Chuuk', '<+10>-10' },
|
||||
{ 'Pacific/Easter', '<-06>6<-05>,M9.1.6/22,M4.1.6/22' },
|
||||
{ 'Pacific/Efate', '<+11>-11' },
|
||||
{ 'Pacific/Fakaofo', '<+13>-13' },
|
||||
{ 'Pacific/Fiji', '<+12>-12' },
|
||||
{ 'Pacific/Funafuti', '<+12>-12' },
|
||||
{ 'Pacific/Galapagos', '<-06>6' },
|
||||
{ 'Pacific/Gambier', '<-09>9' },
|
||||
{ 'Pacific/Guadalcanal', '<+11>-11' },
|
||||
{ 'Pacific/Guam', 'ChST-10' },
|
||||
{ 'Pacific/Honolulu', 'HST10' },
|
||||
{ 'Pacific/Kanton', '<+13>-13' },
|
||||
{ 'Pacific/Kiritimati', '<+14>-14' },
|
||||
{ 'Pacific/Kosrae', '<+11>-11' },
|
||||
{ 'Pacific/Kwajalein', '<+12>-12' },
|
||||
{ 'Pacific/Majuro', '<+12>-12' },
|
||||
{ 'Pacific/Marquesas', '<-0930>9:30' },
|
||||
{ 'Pacific/Midway', 'SST11' },
|
||||
{ 'Pacific/Nauru', '<+12>-12' },
|
||||
{ 'Pacific/Niue', '<-11>11' },
|
||||
{ 'Pacific/Norfolk', '<+11>-11<+12>,M10.1.0,M4.1.0/3' },
|
||||
{ 'Pacific/Noumea', '<+11>-11' },
|
||||
{ 'Pacific/Pago Pago', 'SST11' },
|
||||
{ 'Pacific/Palau', '<+09>-9' },
|
||||
{ 'Pacific/Pitcairn', '<-08>8' },
|
||||
{ 'Pacific/Pohnpei', '<+11>-11' },
|
||||
{ 'Pacific/Port Moresby', '<+10>-10' },
|
||||
{ 'Pacific/Rarotonga', '<-10>10' },
|
||||
{ 'Pacific/Saipan', 'ChST-10' },
|
||||
{ 'Pacific/Tahiti', '<-10>10' },
|
||||
{ 'Pacific/Tarawa', '<+12>-12' },
|
||||
{ 'Pacific/Tongatapu', '<+13>-13' },
|
||||
{ 'Pacific/Wake', '<+12>-12' },
|
||||
{ 'Pacific/Wallis', '<+12>-12' },
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
module "luci.sys.zoneinfo.tzoffset"
|
||||
|
||||
OFFSET = {
|
||||
gmt = 0, -- GMT
|
||||
eat = 10800, -- EAT
|
||||
cet = 3600, -- CET
|
||||
wat = 3600, -- WAT
|
||||
cat = 7200, -- CAT
|
||||
eet = 7200, -- EET
|
||||
eest = 10800, -- EEST
|
||||
sast = 7200, -- SAST
|
||||
hst = -36000, -- HST
|
||||
hdt = -32400, -- HDT
|
||||
akst = -32400, -- AKST
|
||||
akdt = -28800, -- AKDT
|
||||
ast = -14400, -- AST
|
||||
est = -18000, -- EST
|
||||
cst = -21600, -- CST
|
||||
mst = -25200, -- MST
|
||||
mdt = -21600, -- MDT
|
||||
pst = -28800, -- PST
|
||||
pdt = -25200, -- PDT
|
||||
nst = -12600, -- NST
|
||||
ndt = -9000, -- NDT
|
||||
aest = 36000, -- AEST
|
||||
aedt = 39600, -- AEDT
|
||||
nzst = 43200, -- NZST
|
||||
nzdt = 46800, -- NZDT
|
||||
hkt = 28800, -- HKT
|
||||
wib = 25200, -- WIB
|
||||
wit = 32400, -- WIT
|
||||
ist = 7200, -- IST
|
||||
idt = 10800, -- IDT
|
||||
pkt = 18000, -- PKT
|
||||
wita = 28800, -- WITA
|
||||
kst = 32400, -- KST
|
||||
jst = 32400, -- JST
|
||||
wet = 0, -- WET
|
||||
acst = 34200, -- ACST
|
||||
acdt = 37800, -- ACDT
|
||||
awst = 28800, -- AWST
|
||||
msk = 10800, -- MSK
|
||||
sst = -39600, -- SST
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local util = require "luci.util"
|
||||
local config = require "luci.config"
|
||||
local tparser = require "luci.template.parser"
|
||||
|
||||
local tostring, pairs, loadstring = tostring, pairs, loadstring
|
||||
local setmetatable, loadfile = setmetatable, loadfile
|
||||
local getfenv, setfenv, rawget = getfenv, setfenv, rawget
|
||||
local assert, type, error = assert, type, error
|
||||
|
||||
--- LuCI template library.
|
||||
module "luci.template"
|
||||
|
||||
config.template = config.template or {}
|
||||
viewdir = config.template.viewdir or util.libpath() .. "/view"
|
||||
|
||||
|
||||
-- Define the namespace for template modules
|
||||
context = util.threadlocal()
|
||||
|
||||
--- Render a certain template.
|
||||
-- @param name Template name
|
||||
-- @param scope Scope to assign to template (optional)
|
||||
function render(name, scope)
|
||||
return Template(name):render(scope or getfenv(2))
|
||||
end
|
||||
|
||||
--- Render a template from a string.
|
||||
-- @param template Template string
|
||||
-- @param scope Scope to assign to template (optional)
|
||||
function render_string(template, scope)
|
||||
return Template(nil, template):render(scope or getfenv(2))
|
||||
end
|
||||
|
||||
|
||||
-- Template class
|
||||
Template = util.class()
|
||||
|
||||
-- Shared template cache to store templates in to avoid unnecessary reloading
|
||||
Template.cache = setmetatable({}, {__mode = "v"})
|
||||
|
||||
|
||||
-- Constructor - Reads and compiles the template on-demand
|
||||
function Template.__init__(self, name, template)
|
||||
if name then
|
||||
self.template = self.cache[name]
|
||||
self.name = name
|
||||
else
|
||||
self.name = "[string]"
|
||||
end
|
||||
|
||||
-- Create a new namespace for this template
|
||||
self.viewns = context.viewns
|
||||
|
||||
-- If we have a cached template, skip compiling and loading
|
||||
if not self.template then
|
||||
|
||||
-- Compile template
|
||||
local err
|
||||
local sourcefile
|
||||
|
||||
if name then
|
||||
sourcefile = viewdir .. "/" .. name .. ".htm"
|
||||
self.template, _, err = tparser.parse(sourcefile)
|
||||
else
|
||||
sourcefile = "[string]"
|
||||
self.template, _, err = tparser.parse_string(template)
|
||||
end
|
||||
|
||||
-- If we have no valid template throw error, otherwise cache the template
|
||||
if not self.template then
|
||||
error("Failed to load template '" .. self.name .. "'.\n" ..
|
||||
"Error while parsing template '" .. sourcefile .. "':\n" ..
|
||||
(err or "Unknown syntax error"))
|
||||
elseif name then
|
||||
self.cache[name] = self.template
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Renders a template
|
||||
function Template.render(self, scope)
|
||||
scope = scope or getfenv(2)
|
||||
|
||||
-- Put our predefined objects in the scope of the template
|
||||
setfenv(self.template, setmetatable({}, {__index =
|
||||
function(tbl, key)
|
||||
return rawget(tbl, key) or self.viewns[key] or scope[key]
|
||||
end}))
|
||||
|
||||
-- Now finally render the thing
|
||||
local stat, err = util.copcall(self.template)
|
||||
if not stat then
|
||||
error("Failed to execute template '" .. self.name .. "'.\n" ..
|
||||
"A runtime error occurred: " .. tostring(err or "(nil)"))
|
||||
end
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
module "luci.version"
|
||||
|
||||
distname = "Host System"
|
||||
distversion = "SDK"
|
||||
|
||||
luciname = "LuCI"
|
||||
luciversion = "SVN"
|
@ -0,0 +1,24 @@
|
||||
<%#
|
||||
Copyright 2015 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<h2 name="content"><%:Form token mismatch%></h2>
|
||||
<br />
|
||||
|
||||
<p class="alert-message"><%:The submitted security token is invalid or already expired!%></p>
|
||||
|
||||
<p><%:
|
||||
In order to prevent unauthorized access to the system, your request has
|
||||
been blocked. Click "Continue »" below to return to the previous page.
|
||||
%></p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p class="right">
|
||||
<strong><a href="#" onclick="window.history.back();">Continue »</a></strong>
|
||||
</p>
|
||||
|
||||
<%+footer%>
|
@ -0,0 +1,11 @@
|
||||
<%#
|
||||
Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Copyright 2018 Daniel F. Dickinson <cshored@thecshore.com>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<p>Component not present.</p>
|
||||
|
||||
<%+footer%>
|
@ -0,0 +1,12 @@
|
||||
<%#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
<h2 name="content">404 <%:Not Found%></h2>
|
||||
<p><%:Sorry, the object you requested was not found.%></p>
|
||||
<p><%=message%></p>
|
||||
<tt><%:Unable to dispatch%>: <%=url(unpack(luci.dispatcher.context.request))%></tt>
|
||||
<%+footer%>
|
@ -0,0 +1,11 @@
|
||||
<%#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
<h2 name="content">500 <%:Internal Server Error%></h2>
|
||||
<p><%:Sorry, the server encountered an unexpected error.%></p>
|
||||
<pre class="error500"><%=message%></pre>
|
||||
<%+footer%>
|
@ -0,0 +1,27 @@
|
||||
<%#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008-2019 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%
|
||||
local is_rollback_pending, rollback_time_remaining, rollback_session, rollback_token = luci.model.uci:rollback_pending()
|
||||
|
||||
if is_rollback_pending or trigger_apply or trigger_revert then
|
||||
%>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("luci-loaded", function() {
|
||||
<% if trigger_apply then -%>
|
||||
L.ui.changes.apply(true);
|
||||
<%- elseif trigger_revert then -%>
|
||||
L.ui.changes.revert();
|
||||
<%- else -%>
|
||||
L.ui.changes.confirm(true, Date.now() + <%=rollback_time_remaining%> * 1000, <%=luci.http.write_json(rollback_token)%>);
|
||||
<%- end %>
|
||||
});
|
||||
</script>
|
||||
<%
|
||||
end
|
||||
|
||||
include("themes/" .. theme .. "/footer")
|
||||
%>
|
@ -0,0 +1,38 @@
|
||||
<%#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008-2019 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%
|
||||
if not luci.dispatcher.context.template_header_sent then
|
||||
include("themes/" .. theme .. "/header")
|
||||
luci.dispatcher.context.template_header_sent = true
|
||||
end
|
||||
|
||||
local applyconf = luci.config and luci.config.apply
|
||||
%>
|
||||
|
||||
<script type="text/javascript" src="<%=resource%>/promis.min.js"></script>
|
||||
<script type="text/javascript" src="<%=resource%>/luci.js"></script>
|
||||
<script type="text/javascript">
|
||||
L = new LuCI(<%= luci.http.write_json({
|
||||
token = token,
|
||||
media = media,
|
||||
resource = resource,
|
||||
scriptname = luci.http.getenv("SCRIPT_NAME"),
|
||||
pathinfo = luci.http.getenv("PATH_INFO"),
|
||||
documentroot = luci.http.getenv("DOCUMENT_ROOT"),
|
||||
requestpath = luci.dispatcher.context.requestpath,
|
||||
dispatchpath = luci.dispatcher.context.path,
|
||||
pollinterval = luci.config.main.pollinterval or 5,
|
||||
ubuspath = luci.config.main.ubuspath or '/ubus/',
|
||||
sessionid = luci.dispatcher.context.authsession,
|
||||
nodespec = luci.dispatcher.context.dispatched,
|
||||
apply_rollback = math.max(applyconf and applyconf.rollback or 90, 90),
|
||||
apply_holdoff = math.max(applyconf and applyconf.holdoff or 4, 1),
|
||||
apply_timeout = math.max(applyconf and applyconf.timeout or 5, 1),
|
||||
apply_display = math.max(applyconf and applyconf.display or 1.5, 1),
|
||||
rollback_token = rollback_token
|
||||
}) %>);
|
||||
</script>
|
@ -0,0 +1,7 @@
|
||||
<%#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<% include("themes/" .. theme .. "/indexer") %>
|
@ -0,0 +1,75 @@
|
||||
<%#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008-2012 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-%>
|
||||
|
||||
<%+header%>
|
||||
|
||||
<form method="post" action="<%=pcdata(FULL_REQUEST_URI)%>">
|
||||
<%- if fuser then %>
|
||||
<div class="alert-message warning">
|
||||
<p><%:Invalid username and/or password! Please try again.%></p>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<div class="cbi-map">
|
||||
<h2 name="content"><%:Authorization Required%></h2>
|
||||
<div class="cbi-map-descr">
|
||||
<%:Please enter your username and password.%>
|
||||
</div>
|
||||
<div class="cbi-section"><div class="cbi-section-node">
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title" for="luci_username"><%:Username%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="cbi-input-text" type="text" name="luci_username" id="luci_username" autocomplete="username" value="<%=duser%>" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="cbi-value cbi-value-last">
|
||||
<label class="cbi-value-title" for="luci_password"><%:Password%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="cbi-input-text" type="password" name="luci_password" id="luci_password" autocomplete="current-password"/>
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-page-actions">
|
||||
<input type="submit" value="<%:Login%>" class="btn cbi-button cbi-button-apply" />
|
||||
<input type="reset" value="<%:Reset%>" class="btn cbi-button cbi-button-reset" />
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var input = document.getElementsByName('luci_password')[0];
|
||||
if (input)
|
||||
input.focus();
|
||||
//]]></script>
|
||||
|
||||
<%
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
local fs = require "nixio.fs"
|
||||
local https_key = uci:get("uhttpd", "main", "key")
|
||||
local https_port = uci:get("uhttpd", "main", "listen_https")
|
||||
if type(https_port) == "table" then
|
||||
https_port = https_port[1]
|
||||
end
|
||||
|
||||
if https_port and fs.access(https_key) then
|
||||
https_port = https_port:match("(%d+)$")
|
||||
%>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
if (document.location.protocol != 'https:') {
|
||||
var url = 'https://' + window.location.hostname + ':' + '<%=https_port%>' + window.location.pathname;
|
||||
var img=new Image;
|
||||
img.onload=function(){window.location = url};
|
||||
img.src='https://' + window.location.hostname + ':' + '<%=https_port%>' + '<%=resource%>/icons/loading.gif?' + Math.random();
|
||||
setTimeout(function(){
|
||||
img.src=''
|
||||
}, 5000);
|
||||
}
|
||||
//]]></script>
|
||||
|
||||
<% end %>
|
||||
|
||||
<%+footer%>
|
@ -0,0 +1,12 @@
|
||||
<%+header%>
|
||||
|
||||
<div id="view">
|
||||
<div class="spinning"><%:Loading view…%></div>
|
||||
<script type="text/javascript">
|
||||
L.require('ui').then(function(ui) {
|
||||
ui.instantiateView('<%=view%>');
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<%+footer%>
|
@ -0,0 +1,26 @@
|
||||
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
|
||||
local tparser = require "luci.template.parser"
|
||||
local string = require "string"
|
||||
|
||||
local tostring = tostring
|
||||
|
||||
module "luci.xml"
|
||||
|
||||
--
|
||||
-- String and data manipulation routines
|
||||
--
|
||||
|
||||
function pcdata(value)
|
||||
return value and tparser.pcdata(tostring(value))
|
||||
end
|
||||
|
||||
function striptags(value)
|
||||
return value and tparser.striptags(tostring(value))
|
||||
end
|
||||
|
||||
|
||||
-- also register functions above in the central string class for convenience
|
||||
string.pcdata = pcdata
|
||||
string.striptags = striptags
|
@ -0,0 +1,23 @@
|
||||
---[[
|
||||
LuCI utility functions.
|
||||
]]
|
||||
module "luci.xml"
|
||||
|
||||
---[[
|
||||
Create valid XML PCDATA from given string.
|
||||
|
||||
@class function
|
||||
@name pcdata
|
||||
@param value String value containing the data to escape
|
||||
@return String value containing the escaped data
|
||||
]]
|
||||
|
||||
---[[
|
||||
Strip HTML tags from given string.
|
||||
|
||||
@class function
|
||||
@name striptags
|
||||
@param value String containing the HTML text
|
||||
@return String with HTML tags stripped of
|
||||
]]
|
||||
|