mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 19:55:13 +08:00
⛄ Sync 2026-04-23 20:32:35
This commit is contained in:
1 parent
ac97aa196d
commit
618f49f6ca
206 files changed
+725671
No files matched your search
@@ -0,0 +1,160 @@
|
||||
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
// Licensed to the public under the Apache License 2.0.
|
||||
|
||||
import { load_catalog, change_catalog, get_translations } from 'luci.core';
|
||||
|
||||
const ubus_types = [
|
||||
null,
|
||||
'array',
|
||||
'object',
|
||||
'string',
|
||||
null, // INT64
|
||||
'number',
|
||||
null, // INT16,
|
||||
'boolean',
|
||||
'double'
|
||||
];
|
||||
|
||||
|
||||
function ubus_reply(id, data, code, errmsg) {
|
||||
const reply = { jsonrpc: '2.0', id };
|
||||
|
||||
if (errmsg)
|
||||
reply.error = { code, message: errmsg };
|
||||
else if (type(code) == 'object')
|
||||
reply.result = code;
|
||||
else if (data != null)
|
||||
reply.result = [ code, data ];
|
||||
else
|
||||
reply.result = [ code ];
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
function ubus_access(sid, obj, fun) {
|
||||
return (ubus.call('session', 'access', {
|
||||
ubus_rpc_session: sid,
|
||||
scope: 'ubus',
|
||||
object: obj,
|
||||
function: fun
|
||||
})?.access == true);
|
||||
}
|
||||
|
||||
function ubus_request(req) {
|
||||
if (type(req?.method) != 'string' || req?.jsonrpc != '2.0' || req?.id == null)
|
||||
return ubus_reply(null, null, -32600, 'Invalid request');
|
||||
|
||||
if (req.method == 'call') {
|
||||
if (type(req?.params) != 'array' || length(req.params) < 3)
|
||||
return ubus_reply(null, null, -32600, 'Invalid parameters');
|
||||
|
||||
let sid = req.params[0],
|
||||
obj = req.params[1],
|
||||
fun = req.params[2],
|
||||
arg = req.params[3] ?? {};
|
||||
|
||||
if (type(arg) != 'object' || exists(arg, 'ubus_rpc_session'))
|
||||
return ubus_reply(req.id, null, -32602, 'Invalid parameters');
|
||||
|
||||
if (sid == '00000000000000000000000000000000' && ctx.authsession)
|
||||
sid = ctx.authsession;
|
||||
|
||||
if (!ubus_access(sid, obj, fun))
|
||||
return ubus_reply(req.id, null, -32002, 'Access denied');
|
||||
|
||||
arg.ubus_rpc_session = sid;
|
||||
|
||||
|
||||
// clear error
|
||||
ubus.error();
|
||||
|
||||
const res = ubus.call(obj, fun, arg);
|
||||
|
||||
return ubus_reply(req.id, res, ubus.error(true) ?? 0);
|
||||
}
|
||||
|
||||
if (req.method == 'list') {
|
||||
if (req?.params == null || (type(req.params) == 'array' && length(req.params) == 0)) {
|
||||
return ubus_reply(req.id, null, ubus.list());
|
||||
}
|
||||
else if (type(req.params) == 'array') {
|
||||
const rv = {};
|
||||
|
||||
for (let param in req.params) {
|
||||
if (type(param) != 'string')
|
||||
return ubus_reply(req.id, null, -32602, 'Invalid parameters');
|
||||
|
||||
for (let m, p in ubus.list(param)?.[0]) {
|
||||
for (let pn, pt in p) {
|
||||
rv[param] ??= {};
|
||||
rv[param][m] ??= {};
|
||||
rv[param][m][pn] = ubus_types[pt] ?? 'unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ubus_reply(req.id, null, rv);
|
||||
}
|
||||
else {
|
||||
return ubus_reply(req.id, null, -32602, 'Invalid parameters')
|
||||
}
|
||||
}
|
||||
|
||||
return ubus_reply(req.id, null, -32601, 'Method not found')
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
action_ubus: function() {
|
||||
let request;
|
||||
|
||||
try { request = json(http.content()); }
|
||||
catch { request = null; }
|
||||
|
||||
http.prepare_content('application/json; charset=UTF-8');
|
||||
|
||||
if (type(request) == 'object')
|
||||
http.write_json(ubus_request(request));
|
||||
else if (type(request) == 'array')
|
||||
http.write_json(map(request, ubus_request));
|
||||
else
|
||||
http.write_json(ubus_reply(null, null, -32700, 'Parse error'))
|
||||
},
|
||||
|
||||
action_translations: function(reqlang) {
|
||||
if (reqlang != null && reqlang != dispatcher.lang) {
|
||||
load_catalog(reqlang, '/usr/lib/lua/luci/i18n');
|
||||
change_catalog(reqlang);
|
||||
}
|
||||
|
||||
http.prepare_content('application/javascript; charset=UTF-8');
|
||||
http.write('window.TR={');
|
||||
|
||||
get_translations((key, val) => http.write(sprintf('"%08x":%J,', key, val)));
|
||||
|
||||
http.write('};');
|
||||
},
|
||||
|
||||
action_logout: function() {
|
||||
const url = dispatcher.build_url();
|
||||
|
||||
if (ctx.authsession) {
|
||||
ubus.call('session', 'destroy', { ubus_rpc_session: ctx.authsession });
|
||||
|
||||
if (http.getenv('HTTPS') == 'on')
|
||||
http.header('Set-Cookie', `sysauth_https=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`);
|
||||
else
|
||||
http.header('Set-Cookie', `sysauth_http=; expires=Thu, 01 Jan 1970 01:00:00 GMT; path=${url}`);
|
||||
}
|
||||
|
||||
http.redirect(url);
|
||||
},
|
||||
|
||||
action_menu: function() {
|
||||
const session = dispatcher.is_authenticated({ methods: [ 'cookie:sysauth_https', 'cookie:sysauth_http' ] });
|
||||
const menu = dispatcher.menu_json(session?.acls ?? {}) ?? {};
|
||||
|
||||
http.prepare_content('application/json; charset=UTF-8');
|
||||
http.write_json(menu);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
// Licensed to the public under the Apache License 2.0.
|
||||
|
||||
import { STATUS_NO_DATA, STATUS_PERMISSION_DENIED } from 'ubus';
|
||||
|
||||
let last_ubus_error;
|
||||
|
||||
const ubus_error_map = [
|
||||
200, 'OK',
|
||||
400, 'Invalid command',
|
||||
400, 'Invalid argument',
|
||||
404, 'Method not found',
|
||||
404, 'Not found',
|
||||
204, 'No data',
|
||||
403, 'Permission denied',
|
||||
504, 'Timeout',
|
||||
500, 'Not supported',
|
||||
500, 'Unknown error',
|
||||
503, 'Connection failed',
|
||||
500, 'Out of memory',
|
||||
400, 'Parse error',
|
||||
500, 'System error',
|
||||
];
|
||||
|
||||
function ubus_call(object, method, args) {
|
||||
ubus.error(); // clear previous error
|
||||
|
||||
let res = ubus.call(object, method, args);
|
||||
|
||||
last_ubus_error = ubus.error(true);
|
||||
|
||||
return res ?? !last_ubus_error;
|
||||
}
|
||||
|
||||
function ubus_state_to_http(err) {
|
||||
let code = ubus_error_map[(err << 1) + 0] ?? 200;
|
||||
let msg = ubus_error_map[(err << 1) + 1] ?? 'OK';
|
||||
|
||||
http.status(code, msg);
|
||||
|
||||
if (code != 204) {
|
||||
http.prepare_content('text/plain');
|
||||
http.write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function uci_apply(rollback) {
|
||||
if (rollback) {
|
||||
const timeout = +(config?.apply?.rollback ?? 90) || 0;
|
||||
const success = ubus_call('uci', 'apply', {
|
||||
ubus_rpc_session: ctx.authsession,
|
||||
timeout: max(timeout, 90),
|
||||
rollback: true
|
||||
});
|
||||
|
||||
if (success) {
|
||||
const token = dispatcher.randomid(16);
|
||||
|
||||
ubus.call('session', 'set', {
|
||||
ubus_rpc_session: '00000000000000000000000000000000',
|
||||
values: {
|
||||
rollback: {
|
||||
token,
|
||||
session: ctx.authsession,
|
||||
timeout: time() + timeout
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
let changes = ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes;
|
||||
|
||||
for (let config in changes)
|
||||
if (!ubus_call('uci', 'commit', { ubus_rpc_session: ctx.authsession, config }))
|
||||
return false;
|
||||
|
||||
return ubus_call('uci', 'apply', {
|
||||
ubus_rpc_session: ctx.authsession,
|
||||
rollback: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function uci_confirm(token) {
|
||||
const data = ubus.call('session', 'get', {
|
||||
ubus_rpc_session: '00000000000000000000000000000000',
|
||||
keys: [ 'rollback' ]
|
||||
})?.values?.rollback;
|
||||
|
||||
if (type(data?.token) != 'string' || type(data?.session) != 'string' ||
|
||||
type(data?.timeout) != 'int' || data.timeout < time()) {
|
||||
last_ubus_error = STATUS_NO_DATA;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (token != data.token) {
|
||||
last_ubus_error = STATUS_PERMISSION_DENIED;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ubus_call('uci', 'confirm', { ubus_rpc_session: data.session }))
|
||||
return false;
|
||||
|
||||
ubus_call('session', 'set', {
|
||||
ubus_rpc_session: '00000000000000000000000000000000',
|
||||
values: { rollback: {} }
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
action_apply_rollback: function() {
|
||||
const token = uci_apply(true);
|
||||
|
||||
if (token) {
|
||||
http.prepare_content('application/json; charset=UTF-8');
|
||||
http.write_json({ token });
|
||||
}
|
||||
else {
|
||||
ubus_state_to_http(last_ubus_error);
|
||||
}
|
||||
},
|
||||
|
||||
action_apply_unchecked: function() {
|
||||
uci_apply(false);
|
||||
ubus_state_to_http(last_ubus_error);
|
||||
},
|
||||
|
||||
action_confirm: function() {
|
||||
uci_confirm(http.formvalue('token'));
|
||||
ubus_state_to_http(last_ubus_error);
|
||||
},
|
||||
|
||||
action_revert: function() {
|
||||
for (let config in ubus_call('uci', 'changes', { ubus_rpc_session: ctx.authsession })?.changes)
|
||||
if (!ubus_call('uci', 'revert', { ubus_rpc_session: ctx.authsession, config }))
|
||||
break;
|
||||
|
||||
ubus_state_to_http(last_ubus_error);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,574 @@
|
||||
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
// Licensed to the public under the Apache License 2.0.
|
||||
|
||||
import {
|
||||
urlencode as _urlencode,
|
||||
urldecode as _urldecode,
|
||||
urlencoded_parser, multipart_parser, header_attribute,
|
||||
ENCODE_IF_NEEDED, ENCODE_FULL, DECODE_IF_NEEDED, DECODE_PLUS
|
||||
} from 'lucihttp';
|
||||
|
||||
import {
|
||||
error as fserror,
|
||||
stdin, stdout, mkstemp
|
||||
} from 'fs';
|
||||
|
||||
// luci.http module scope
|
||||
export let HTTP_MAX_CONTENT = 1024*100; // 100 kB maximum content size
|
||||
|
||||
// Decode a mime encoded http message body with multipart/form-data
|
||||
// Content-Type. Stores all extracted data associated with its parameter name
|
||||
// in the params table within the given message object. Multiple parameter
|
||||
// values are stored as tables, ordinary ones as strings.
|
||||
// If an optional file callback function is given then it is fed with the
|
||||
// file contents chunk by chunk and only the extracted file name is stored
|
||||
// within the params table. The callback function will be called subsequently
|
||||
// with three arguments:
|
||||
// o Table containing decoded (name, file) and raw (headers) mime header data
|
||||
// o String value containing a chunk of the file data
|
||||
// o Boolean which indicates whether the current chunk is the last one (eof)
|
||||
export function mimedecode_message_body(src, msg, file_cb) {
|
||||
let len = 0, maxlen = +msg.env.CONTENT_LENGTH;
|
||||
let err, header, field, parser;
|
||||
|
||||
parser = multipart_parser(msg.env.CONTENT_TYPE, function(what, buffer, length) {
|
||||
if (what == parser.PART_INIT) {
|
||||
field = {};
|
||||
}
|
||||
else if (what == parser.HEADER_NAME) {
|
||||
header = lc(buffer);
|
||||
}
|
||||
else if (what == parser.HEADER_VALUE && header) {
|
||||
if (lc(header) == 'content-disposition' &&
|
||||
header_attribute(buffer, null) == 'form-data') {
|
||||
field.name = header_attribute(buffer, 'name');
|
||||
field.file = header_attribute(buffer, 'filename');
|
||||
field[1] = field.file;
|
||||
}
|
||||
|
||||
field.headers = field.headers || {};
|
||||
field.headers[header] = buffer;
|
||||
}
|
||||
else if (what == parser.PART_BEGIN) {
|
||||
return !field.file;
|
||||
}
|
||||
else if (what == parser.PART_DATA && field.name && length > 0) {
|
||||
if (field.file) {
|
||||
if (file_cb) {
|
||||
file_cb(field, buffer, false);
|
||||
|
||||
msg.params[field.name] = msg.params[field.name] || field;
|
||||
}
|
||||
else {
|
||||
if (!field.fd)
|
||||
field.fd = mkstemp(field.name);
|
||||
|
||||
if (field.fd) {
|
||||
field.fd.write(buffer);
|
||||
msg.params[field.name] = msg.params[field.name] || field;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
field.value = buffer;
|
||||
}
|
||||
}
|
||||
else if (what == parser.PART_END && field.name) {
|
||||
if (field.file && msg.params[field.name]) {
|
||||
if (file_cb)
|
||||
file_cb(field, '', true);
|
||||
else if (field.fd)
|
||||
field.fd.seek(0);
|
||||
}
|
||||
else {
|
||||
let val = msg.params[field.name];
|
||||
|
||||
if (type(val) == 'array')
|
||||
push(val, field.value || '');
|
||||
else if (val != null)
|
||||
msg.params[field.name] = [ val, field.value || '' ];
|
||||
else
|
||||
msg.params[field.name] = field.value || '';
|
||||
}
|
||||
|
||||
field = null;
|
||||
}
|
||||
else if (what == parser.ERROR) {
|
||||
err = buffer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, HTTP_MAX_CONTENT);
|
||||
|
||||
while (true) {
|
||||
let chunk = src();
|
||||
|
||||
len += length(chunk);
|
||||
|
||||
if (maxlen && len > maxlen + 2)
|
||||
die('Message body size exceeds Content-Length');
|
||||
|
||||
if (!parser.parse(chunk))
|
||||
die(err);
|
||||
|
||||
if (chunk == null)
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Decode an urlencoded http message body with application/x-www-urlencoded
|
||||
// Content-Type. Stores all extracted data associated with its parameter name
|
||||
// in the params table within the given message object. Multiple parameter
|
||||
// values are stored as tables, ordinary ones as strings.
|
||||
export function urldecode_message_body(src, msg) {
|
||||
let len = 0, maxlen = +msg.env.CONTENT_LENGTH;
|
||||
let err, name, value, parser;
|
||||
|
||||
parser = urlencoded_parser(function (what, buffer, length) {
|
||||
if (what == parser.TUPLE) {
|
||||
name = null;
|
||||
value = null;
|
||||
}
|
||||
else if (what == parser.NAME) {
|
||||
name = _urldecode(buffer, DECODE_PLUS);
|
||||
}
|
||||
else if (what == parser.VALUE && name) {
|
||||
let val = msg.params[name];
|
||||
|
||||
if (type(val) == 'array')
|
||||
push(val, _urldecode(buffer, DECODE_PLUS) || '');
|
||||
else if (val != null)
|
||||
msg.params[name] = [ val, _urldecode(buffer, DECODE_PLUS) || '' ];
|
||||
else
|
||||
msg.params[name] = _urldecode(buffer, DECODE_PLUS) || '';
|
||||
}
|
||||
else if (what == parser.ERROR) {
|
||||
err = buffer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, HTTP_MAX_CONTENT);
|
||||
|
||||
while (true) {
|
||||
let chunk = src();
|
||||
|
||||
len += length(chunk);
|
||||
|
||||
if (maxlen && len > maxlen + 2)
|
||||
die('Message body size exceeds Content-Length');
|
||||
|
||||
if (!parser.parse(chunk))
|
||||
die(err);
|
||||
|
||||
if (chunk == null)
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// This function will examine the Content-Type within the given message object
|
||||
// to select the appropriate content decoder.
|
||||
// Currently the application/x-www-urlencoded and application/form-data
|
||||
// mime types are supported. If the encountered content encoding can't be
|
||||
// handled then the whole message body will be stored unaltered as 'content'
|
||||
// property within the given message object.
|
||||
export function parse_message_body(src, msg, filecb) {
|
||||
if (msg.env.CONTENT_LENGTH || msg.env.REQUEST_METHOD == 'POST') {
|
||||
let ctype = header_attribute(msg.env.CONTENT_TYPE, null);
|
||||
|
||||
// Is it multipart/mime ?
|
||||
if (ctype == 'multipart/form-data')
|
||||
return mimedecode_message_body(src, msg, filecb);
|
||||
|
||||
// Is it application/x-www-form-urlencoded ?
|
||||
else if (ctype == 'application/x-www-form-urlencoded')
|
||||
return urldecode_message_body(src, msg);
|
||||
|
||||
// Unhandled encoding
|
||||
// If a file callback is given then feed it chunk by chunk, else
|
||||
// store whole buffer in message.content
|
||||
let sink;
|
||||
|
||||
// If we have a file callback then feed it
|
||||
if (type(filecb) == 'function') {
|
||||
let meta = {
|
||||
name: 'raw',
|
||||
encoding: msg.env.CONTENT_TYPE
|
||||
};
|
||||
|
||||
sink = (chunk) => {
|
||||
if (chunk != null)
|
||||
return filecb(meta, chunk, false);
|
||||
else
|
||||
return filecb(meta, null, true);
|
||||
};
|
||||
}
|
||||
|
||||
// ... else append to .content
|
||||
else {
|
||||
let chunks = [], len = 0;
|
||||
|
||||
sink = (chunk) => {
|
||||
len += length(chunk);
|
||||
|
||||
if (len > HTTP_MAX_CONTENT)
|
||||
die('POST data exceeds maximum allowed length');
|
||||
|
||||
if (chunk != null) {
|
||||
push(chunks, chunk);
|
||||
}
|
||||
else {
|
||||
msg.content = join('', chunks);
|
||||
msg.content_length = len;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Pump data...
|
||||
while (true) {
|
||||
let chunk = src();
|
||||
|
||||
sink(chunk);
|
||||
|
||||
if (chunk == null)
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export function build_querystring(q) {
|
||||
let s = [];
|
||||
|
||||
for (let k, v in q) {
|
||||
push(s,
|
||||
length(s) ? '&' : '?',
|
||||
_urlencode(k, ENCODE_IF_NEEDED | ENCODE_FULL) || k,
|
||||
'=',
|
||||
_urlencode(v, ENCODE_IF_NEEDED | ENCODE_FULL) || v
|
||||
);
|
||||
}
|
||||
|
||||
return join('', s);
|
||||
};
|
||||
|
||||
export function urlencode(value) {
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
value = '' + value;
|
||||
|
||||
return _urlencode(value, ENCODE_IF_NEEDED | ENCODE_FULL) || value;
|
||||
};
|
||||
|
||||
export function urldecode(value, decode_plus) {
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
value = '' + value;
|
||||
|
||||
return _urldecode(value, DECODE_IF_NEEDED | (decode_plus ? DECODE_PLUS : 0)) || value;
|
||||
};
|
||||
|
||||
// Extract and split urlencoded data pairs, separated bei either "&" or ";"
|
||||
// from given url or string. Returns a table with urldecoded values.
|
||||
// Simple parameters are stored as string values associated with the parameter
|
||||
// name within the table. Parameters with multiple values are stored as array
|
||||
// containing the corresponding values.
|
||||
export function urldecode_params(url, tbl) {
|
||||
let parser, name, value;
|
||||
let params = tbl || {};
|
||||
|
||||
parser = urlencoded_parser(function(what, buffer, length) {
|
||||
if (what == parser.TUPLE) {
|
||||
name = null;
|
||||
value = null;
|
||||
}
|
||||
else if (what == parser.NAME) {
|
||||
name = _urldecode(buffer);
|
||||
}
|
||||
else if (what == parser.VALUE && name) {
|
||||
params[name] = _urldecode(buffer) || '';
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (parser) {
|
||||
let m = match(('' + (url || '')), /[^?]*$/);
|
||||
|
||||
parser.parse(m ? m[0] : '');
|
||||
parser.parse(null);
|
||||
}
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
// Encode each key-value-pair in given table to x-www-urlencoded format,
|
||||
// separated by '&'. Tables are encoded as parameters with multiple values by
|
||||
// repeating the parameter name with each value.
|
||||
export function urlencode_params(tbl) {
|
||||
let enc = [];
|
||||
|
||||
for (let k, v in tbl) {
|
||||
if (type(v) == 'array') {
|
||||
for (let v2 in v) {
|
||||
if (length(enc))
|
||||
push(enc, '&');
|
||||
|
||||
push(enc,
|
||||
_urlencode(k),
|
||||
'=',
|
||||
_urlencode('' + v2));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (length(enc))
|
||||
push(enc, '&');
|
||||
|
||||
push(enc,
|
||||
_urlencode(k),
|
||||
'=',
|
||||
_urlencode('' + v));
|
||||
}
|
||||
}
|
||||
|
||||
return join(enc, '');
|
||||
};
|
||||
|
||||
|
||||
// Default IO routines suitable for CGI invocation
|
||||
let avail_len = +getenv('CONTENT_LENGTH');
|
||||
|
||||
const default_source = () => {
|
||||
let rlen = min(avail_len, 4096);
|
||||
|
||||
if (rlen == 0) {
|
||||
stdin.close();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let chunk = stdin.read(rlen);
|
||||
|
||||
if (chunk == null)
|
||||
die(`Input read error: ${fserror()}`);
|
||||
|
||||
avail_len -= length(chunk);
|
||||
|
||||
return chunk;
|
||||
};
|
||||
|
||||
const default_sink = (...chunks) => {
|
||||
for (let chunk in chunks)
|
||||
stdout.write(chunk);
|
||||
|
||||
stdout.flush();
|
||||
};
|
||||
|
||||
const Class = {
|
||||
formvalue: function(name, noparse) {
|
||||
if (!noparse && !this.parsed_input)
|
||||
this._parse_input();
|
||||
|
||||
if (name != null)
|
||||
return this.message.params[name];
|
||||
else
|
||||
return this.message.params;
|
||||
},
|
||||
|
||||
formvaluetable: function(prefix) {
|
||||
let vals = {};
|
||||
|
||||
prefix = (prefix || '') + '.';
|
||||
|
||||
if (!this.parsed_input)
|
||||
this._parse_input();
|
||||
|
||||
for (let k, v in this.message.params)
|
||||
if (index(k, prefix) == 0)
|
||||
vals[substr(k, length(prefix))] = '' + v;
|
||||
|
||||
return vals;
|
||||
},
|
||||
|
||||
content: function() {
|
||||
if (!this.parsed_input)
|
||||
this._parse_input();
|
||||
|
||||
return this.message.content;
|
||||
},
|
||||
|
||||
getcookie: function(name) {
|
||||
return header_attribute(`cookie; ${this.getenv('HTTP_COOKIE') ?? ''}`, name);
|
||||
},
|
||||
|
||||
getenv: function(name) {
|
||||
if (name != null)
|
||||
return this.message.env[name];
|
||||
else
|
||||
return this.message.env;
|
||||
},
|
||||
|
||||
setfilehandler: function(callback) {
|
||||
if (type(callback) == 'resource' && type(callback.call) == 'function')
|
||||
this.filehandler = (...args) => callback.call(...args);
|
||||
else if (type(callback) == 'function')
|
||||
this.filehandler = callback;
|
||||
else
|
||||
die('Invalid callback argument for setfilehandler()');
|
||||
|
||||
if (!this.parsed_input)
|
||||
return;
|
||||
|
||||
// If input has already been parsed then uploads are stored as unlinked
|
||||
// temporary files pointed to by open file handles in the parameter
|
||||
// value table. Loop all params, and invoke the file callback for any
|
||||
// param with an open file handle.
|
||||
for (let name, value in this.message.params) {
|
||||
while (value?.fd) {
|
||||
let data = value.fd.read(1024);
|
||||
let eof = (length(data) == 0);
|
||||
|
||||
this.filehandler(value, data, eof);
|
||||
|
||||
if (eof) {
|
||||
value.fd.close();
|
||||
value.fd = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_parse_input: function() {
|
||||
parse_message_body(
|
||||
this.input,
|
||||
this.message,
|
||||
this.filehandler
|
||||
);
|
||||
|
||||
this.parsed_input = true;
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.write_headers();
|
||||
this.closed = true;
|
||||
},
|
||||
|
||||
header: function(key, value) {
|
||||
this.headers ??= {};
|
||||
this.headers[lc(key)] = value;
|
||||
},
|
||||
|
||||
prepare_content: function(mime) {
|
||||
if (!this.headers?.['content-type']) {
|
||||
if (mime == 'application/xhtml+xml') {
|
||||
if (index(this.getenv('HTTP_ACCEPT'), mime) == -1) {
|
||||
mime = 'text/html; charset=UTF-8';
|
||||
this.header('Vary', 'Accept');
|
||||
}
|
||||
}
|
||||
|
||||
this.header('Content-Type', mime);
|
||||
}
|
||||
},
|
||||
|
||||
status: function(code, message) {
|
||||
this.status_code = code ?? 200;
|
||||
this.status_message = message ?? 'OK';
|
||||
},
|
||||
|
||||
write_headers: function() {
|
||||
if (this.eoh)
|
||||
return;
|
||||
|
||||
if (!this.status_code)
|
||||
this.status();
|
||||
|
||||
if (!this.headers?.['content-type'])
|
||||
this.header('Content-Type', 'text/html; charset=UTF-8');
|
||||
|
||||
if (!this.headers?.['cache-control']) {
|
||||
this.header('Cache-Control', 'no-cache');
|
||||
this.header('Expires', '0');
|
||||
}
|
||||
|
||||
if (!this.headers?.['x-frame-options'])
|
||||
this.header('X-Frame-Options', 'SAMEORIGIN');
|
||||
|
||||
if (!this.headers?.['x-xss-protection'])
|
||||
this.header('X-XSS-Protection', '1; mode=block');
|
||||
|
||||
if (!this.headers?.['x-content-type-options'])
|
||||
this.header('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
this.output('Status: ');
|
||||
this.output(this.status_code);
|
||||
this.output(' ');
|
||||
this.output(this.status_message);
|
||||
this.output('\r\n');
|
||||
|
||||
for (let k, v in this.headers) {
|
||||
this.output(k);
|
||||
this.output(': ');
|
||||
this.output(v);
|
||||
this.output('\r\n');
|
||||
}
|
||||
|
||||
this.output('\r\n');
|
||||
|
||||
this.eoh = true;
|
||||
},
|
||||
|
||||
// If the content chunk is nil this function will automatically invoke close.
|
||||
write: function(content) {
|
||||
if (content != null && !this.closed) {
|
||||
this.write_headers();
|
||||
this.output(content);
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
|
||||
redirect: function(url) {
|
||||
this.status(302, 'Found');
|
||||
this.header('Location', url ?? '/');
|
||||
this.close();
|
||||
},
|
||||
|
||||
write_json: function(value) {
|
||||
this.write(sprintf('%.J', value));
|
||||
},
|
||||
|
||||
urlencode,
|
||||
urlencode_params,
|
||||
|
||||
urldecode,
|
||||
urldecode_params,
|
||||
|
||||
build_querystring
|
||||
};
|
||||
|
||||
export default function(env, sourcein, sinkout) {
|
||||
return proto({
|
||||
input: sourcein ?? default_source,
|
||||
output: sinkout ?? default_sink,
|
||||
|
||||
// File handler nil by default to let .content() work
|
||||
file: null,
|
||||
|
||||
// HTTP-Message table
|
||||
message: {
|
||||
env,
|
||||
headers: {},
|
||||
params: urldecode_params(env?.QUERY_STRING ?? '')
|
||||
},
|
||||
|
||||
parsed_input: false
|
||||
}, Class);
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
// Licensed to the public under the Apache License 2.0.
|
||||
|
||||
import { access, basename, stat } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
|
||||
const template_directory = '/usr/share/ucode/luci/template';
|
||||
|
||||
function cut_message(msg) {
|
||||
return trim(replace(msg, /\n--\n.*$/, ''));
|
||||
}
|
||||
|
||||
function format_nested_exception(ex) {
|
||||
let msg = replace(cut_message(ex.message), /(\n+( \|[^\n]*(\n|$))+)/, (m, m1) => {
|
||||
m1 = replace(m1, /(^|\n) \| ?/g, '$1');
|
||||
m = match(m1, /^(.+?)\n(In.*line \d+, byte \d+:.+)$/);
|
||||
|
||||
return `
|
||||
<div class="exception">
|
||||
<div class="message">${cut_message(m ? m[1] : m1)}</div>
|
||||
${m ? `<pre class="context">${trim(m[2])}</pre>` : ''}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
return `
|
||||
<div class="exception">
|
||||
<div class="message">${cut_message(msg)}</div>
|
||||
<pre class="context">${trim(ex.stacktrace[0].context)}</pre>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function format_lua_exception(ex) {
|
||||
let m = match(ex.message, /^(.+)\nstack traceback:\n(.+)$/);
|
||||
|
||||
return `
|
||||
<div class="exception">
|
||||
<div class="message">${cut_message(m ? m[1] : ex.message)}</div>
|
||||
<pre class="context">${m ? trim(replace(m[2], /(^|\n)\t/g, '$1')) : ex.stacktrace[0].context}</pre>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const Class = {
|
||||
init_lua: function(optional) {
|
||||
if (!this.L) {
|
||||
let bridge = this.env.dispatcher.load_luabridge(optional);
|
||||
|
||||
if (bridge) {
|
||||
let http = this.env.http;
|
||||
|
||||
this.L = bridge.create();
|
||||
this.L.set('L', proto({ write: (...args) => http.closed || print(...args) }, this.env));
|
||||
this.L.invoke('require', 'luci.ucodebridge');
|
||||
|
||||
this.env.lua_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
return this.L;
|
||||
},
|
||||
|
||||
is_ucode_template: function(path) {
|
||||
return access(`${template_directory}/${path}.ut`);
|
||||
},
|
||||
|
||||
is_lua_template: function(path) {
|
||||
let vm = this.init_lua(true);
|
||||
|
||||
return vm && access(`${vm.get('_G', 'luci', 'template', 'viewdir')}/${path}.htm`);
|
||||
},
|
||||
|
||||
render_ucode: function(path, scope) {
|
||||
let tmplfunc = loadfile(path, { raw_mode: false });
|
||||
|
||||
if (this.env.http.closed)
|
||||
render(call, tmplfunc, null, scope ?? {});
|
||||
else
|
||||
call(tmplfunc, null, scope ?? {});
|
||||
},
|
||||
|
||||
render_lua: function(path, scope) {
|
||||
let vm = this.init_lua();
|
||||
let render = vm.get('_G', 'luci', 'ucodebridge', 'render');
|
||||
|
||||
render.call(path, scope ?? {});
|
||||
},
|
||||
|
||||
trycompile: function(path) {
|
||||
let ucode_path = `${template_directory}/${path}.ut`;
|
||||
|
||||
if (access(ucode_path)) {
|
||||
try {
|
||||
loadfile(ucode_path, { raw_mode: false });
|
||||
}
|
||||
catch (ucode_err) {
|
||||
return `Unable to compile '${path}' as ucode template: ${format_nested_exception(ucode_err)}`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
let vm = this.init_lua(true);
|
||||
|
||||
if (vm)
|
||||
vm.get('_G', 'luci', 'ucodebridge', 'compile').call(path);
|
||||
else
|
||||
return `Unable to compile '${path}' as Lua template: Unable to load Lua runtime`;
|
||||
}
|
||||
catch (lua_err) {
|
||||
return `Unable to compile '${path}' as Lua template: ${format_lua_exception(lua_err)}`;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
render_any: function(path, scope) {
|
||||
let ucode_path = `${template_directory}/${path}.ut`;
|
||||
|
||||
scope = proto(scope ?? {}, this.scopes[-1]);
|
||||
|
||||
push(this.scopes, scope);
|
||||
|
||||
try {
|
||||
if (access(ucode_path))
|
||||
this.render_ucode(ucode_path, scope);
|
||||
else
|
||||
this.render_lua(path, scope);
|
||||
}
|
||||
catch (ex) {
|
||||
pop(this.scopes);
|
||||
die(ex);
|
||||
}
|
||||
|
||||
pop(this.scopes);
|
||||
},
|
||||
|
||||
render: function(path, scope) {
|
||||
let self = this;
|
||||
this.env.http.write(render(() => self.render_any(path, scope)));
|
||||
},
|
||||
|
||||
call: function(modname, method, ...args) {
|
||||
let vm = this.init_lua();
|
||||
let lcall = vm.get('_G', 'luci', 'ucodebridge', 'call');
|
||||
|
||||
return lcall.call(modname, method, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
export default function(env) {
|
||||
const self = proto({ env: env ??= {}, scopes: [ proto(env, global) ], global }, Class);
|
||||
const uci = cursor();
|
||||
|
||||
// determine theme
|
||||
let media = uci.get('luci', 'main', 'mediaurlbase');
|
||||
let status = self.trycompile(`themes/${basename(media)}/header`);
|
||||
|
||||
if (status !== true) {
|
||||
media = null;
|
||||
self.env.media_error = status;
|
||||
|
||||
for (let k, v in uci.get_all('luci', 'themes')) {
|
||||
if (substr(k, 0, 1) != '.') {
|
||||
status = self.trycompile(`themes/${basename(v)}/header`);
|
||||
|
||||
if (status === true) {
|
||||
media = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!media)
|
||||
env.dispatcher.error500(`Unable to render any theme header template, last error was:\n${status}`);
|
||||
}
|
||||
|
||||
self.env.media = media;
|
||||
self.env.theme = basename(media);
|
||||
self.env.resource = uci.get('luci', 'main', 'resourcebase');
|
||||
self.env.pkgs_update_time = stat('/lib/apk/db/installed')?.mtime ?? stat('/usr/lib/opkg/status')?.mtime ?? 0;
|
||||
self.env.include = (...args) => self.render_any(...args);
|
||||
|
||||
return self;
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
// Licensed to the public under the Apache License 2.0.
|
||||
|
||||
import { basename, readlink, readfile, open, popen, stat, glob } from 'fs';
|
||||
|
||||
export function process_list() {
|
||||
const top = popen('/bin/busybox top -bn1');
|
||||
let line, list = [];
|
||||
|
||||
for (let line = top.read('line'); length(line); line = top.read('line')) {
|
||||
let m = match(trim(line), /^([0-9]+) +([0-9]+) +(.+) +([RSDZTWI][<NW ][<N ]) +([0-9]+m?) +([0-9]+%) +([0-9]+%) +(.+)$/);
|
||||
|
||||
if (m && m[8] != '/bin/busybox top -bn1') {
|
||||
push(list, {
|
||||
PID: m[1],
|
||||
PPID: m[2],
|
||||
USER: trim(m[3]),
|
||||
STAT: m[4],
|
||||
VSZ: m[5],
|
||||
'%MEM': m[6],
|
||||
'%CPU': m[7],
|
||||
COMMAND: m[8]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
top.close();
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
export function conntrack_list(callback) {
|
||||
const etcpr = open('/etc/protocols');
|
||||
const protos = {};
|
||||
|
||||
if (etcpr) {
|
||||
for (let line = etcpr.read('line'); length(line); line = etcpr.read('line')) {
|
||||
const m = match(line, /^([^# \t\n]+)\s+([0-9]+)\s+/);
|
||||
|
||||
if (m)
|
||||
protos[m[2]] = m[1];
|
||||
}
|
||||
|
||||
etcpr.close();
|
||||
}
|
||||
|
||||
let nfct = open('/proc/net/nf_conntrack', 'r');
|
||||
if (! nfct) {
|
||||
nfct = popen('/usr/sbin/conntrack -L -o extended', 'r');
|
||||
}
|
||||
let connt;
|
||||
|
||||
if (nfct) {
|
||||
for (let line = nfct.read('line'); length(line); line = nfct.read('line')) {
|
||||
let m = match(line, /^(ipv[46]) +([0-9]+) +\S+ +([0-9]+)( +.+)\n$/);
|
||||
|
||||
if (!m)
|
||||
continue;
|
||||
|
||||
let fam = m[1];
|
||||
let l3 = m[2];
|
||||
let l4 = m[3];
|
||||
let tuples = m[4];
|
||||
let timeout = null;
|
||||
|
||||
m = match(tuples, /^ +([0-9]+)( .+)$/);
|
||||
|
||||
if (m) {
|
||||
timeout = m[1];
|
||||
tuples = m[2];
|
||||
}
|
||||
|
||||
if (index(tuples, 'TIME_WAIT') !== -1)
|
||||
continue;
|
||||
|
||||
let e = {
|
||||
bytes: 0,
|
||||
packets: 0,
|
||||
layer3: fam,
|
||||
layer4: protos[l4] ?? 'unknown',
|
||||
timeout: +timeout
|
||||
};
|
||||
|
||||
for (let kv in match(tuples, / (\w+)=(\S+)/g)) {
|
||||
switch (kv[1]) {
|
||||
case 'bytes':
|
||||
case 'packets':
|
||||
e[kv[1]] += +kv[2];
|
||||
break;
|
||||
|
||||
case 'src':
|
||||
case 'dst':
|
||||
e[kv[1]] ??= arrtoip(iptoarr(kv[2]));
|
||||
break;
|
||||
|
||||
case 'sport':
|
||||
case 'dport':
|
||||
e[kv[1]] ??= +kv[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
e[kv[1]] = kv[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (callback)
|
||||
callback(e);
|
||||
else
|
||||
push(connt ??= [], e);
|
||||
}
|
||||
|
||||
nfct.close();
|
||||
}
|
||||
|
||||
return callback ? true : (connt ?? []);
|
||||
};
|
||||
|
||||
export function init_list() {
|
||||
return map(filter(glob('/etc/init.d/*'), path => {
|
||||
const s = stat(path);
|
||||
|
||||
return s?.type == 'file' && s?.perm?.user_exec;
|
||||
}), basename);
|
||||
};
|
||||
|
||||
export function init_index(name) {
|
||||
const src = readfile(`/etc/init.d/${basename(name)}`, 2048);
|
||||
const idx = [];
|
||||
|
||||
for (let m in match(src, /^[[:space:]]*(START|STOP)=('[0-9][0-9]'|"[0-9][0-9]"|[0-9][0-9])[[:space:]]*$/gs)) {
|
||||
switch (m[1]) {
|
||||
case 'START': idx[0] = +trim(m[2], '"\''); break;
|
||||
case 'STOP': idx[1] = +trim(m[2], '"\''); break;
|
||||
}
|
||||
}
|
||||
|
||||
return length(idx) ? idx : null;
|
||||
};
|
||||
|
||||
export function init_enabled(name) {
|
||||
for (let path in glob(`/etc/rc.d/[SK][0-9][0-9]${basename(name)}`)) {
|
||||
const ln = readlink(path);
|
||||
const s1 = stat(index(ln, '/') == 0 ? ln : `/etc/rc.d/${ln}`);
|
||||
const s2 = stat(`/etc/init.d/${basename(name)}`);
|
||||
|
||||
if (s1?.inode == s2?.inode && s1?.type == 'file' && s1?.perm?.user_exec)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export function init_action(name, action) {
|
||||
const s = stat(`/etc/init.d/${basename(name)}`);
|
||||
|
||||
if (s?.type != 'file' || s?.user_exec == false)
|
||||
return false;
|
||||
|
||||
return system(`env -i /etc/init.d/${basename(name)} ${action} >/dev/null`);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
{#
|
||||
Copyright 2015-2022 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{% include('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>
|
||||
|
||||
{% include('footer') %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{#
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008-2022 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{% include('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') }}: {{ dispatcher.build_url(...ctx.request_path) }}</tt>
|
||||
|
||||
{% include('footer') %}
|
||||
@@ -0,0 +1,67 @@
|
||||
{#
|
||||
Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
<!--]]>--><!--'>--><!--">-->
|
||||
<style>
|
||||
body {
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.error500 * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.error500 {
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
background: #ffe;
|
||||
color: #f00 !important;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.error500 h1 {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.error500 .exception {
|
||||
font-weight: normal;
|
||||
white-space: normal;
|
||||
margin: .25em;
|
||||
padding: .5em;
|
||||
border: 1px solid #f00;
|
||||
background: rgba(204, 204, 204, .2);
|
||||
}
|
||||
|
||||
.error500 .message {
|
||||
font-weight: bold;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.error500 .context {
|
||||
margin-top: 2em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="error500">
|
||||
<h1>{{ title }}</h1>
|
||||
<div class="message">{{ message }}</div>
|
||||
|
||||
{% if (exception): %}
|
||||
<div class="exception">
|
||||
<div class="message">{{ exception.message }}</div>
|
||||
<pre class="context">{{ exception.stacktrace[0].context }}</pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,42 @@
|
||||
{#
|
||||
Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{% const rollback = dispatcher.rollback_pending() %}
|
||||
{% if (rollback || trigger_apply || trigger_revert): %}
|
||||
<script>
|
||||
document.addEventListener("luci-loaded", function() {
|
||||
{% if (trigger_apply): %}
|
||||
L.ui.changes.apply(true);
|
||||
{% elif (trigger_revert): %}
|
||||
L.ui.changes.revert();
|
||||
{% else %}
|
||||
L.ui.changes.confirm(true, Date.now() + {{rollback.remaining * 1000}}, {{sprintf('%J', rollback.token)}});
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if (media_error): %}
|
||||
<script>
|
||||
L.require('ui').then(function(ui) {
|
||||
ui.showIndicator('media_error', _('Theme fallback'), function(ev) {
|
||||
ui.showModal(_('Error loading theme'), [
|
||||
E('p', [
|
||||
_('A fallback is used since the configured theme failed to load with the error below.')
|
||||
]),
|
||||
E('hr'),
|
||||
E('div', { 'style': 'white-space:pre-line' }, {{ sprintf('%J', trim(media_error)) }}),
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', { 'class': 'btn cbi-button', 'click': ui.hideModal }, _('Dismiss'))
|
||||
])
|
||||
]);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% include(`themes/${theme}/footer`) %}
|
||||
|
||||
<!-- Lua compatibility mode active: {{ lua_active ? 'yes' : 'no' }} -->
|
||||
@@ -0,0 +1,31 @@
|
||||
{#
|
||||
Copyright 2022 Jo-Philipp Wich <jo@mein.io>
|
||||
Licensed to the public under the Apache License 2.0.
|
||||
-#}
|
||||
|
||||
{%
|
||||
include(`themes/${theme}/header`);
|
||||
-%}
|
||||
|
||||
<script src="{{ resource }}/luci.js?v={# PKG_VERSION #}-{{ pkgs_update_time }}"></script>
|
||||
<script>
|
||||
L = new LuCI({{ replace(`${ {
|
||||
media : media,
|
||||
resource : resource,
|
||||
scriptname : http.getenv("SCRIPT_NAME"),
|
||||
pathinfo : http.getenv("PATH_INFO"),
|
||||
documentroot : http.getenv("DOCUMENT_ROOT"),
|
||||
requestpath : ctx.request_path,
|
||||
dispatchpath : ctx.path,
|
||||
pollinterval : +config.main.pollinterval || 5,
|
||||
ubuspath : config.main.ubuspath || '/ubus/',
|
||||
sessionid : ctx.authsession,
|
||||
token : ctx.authtoken,
|
||||
nodespec : dispatched,
|
||||
apply_rollback : max(+config.apply.rollback || 90, 90),
|
||||
apply_holdoff : max(+config.apply.holdoff || 4, 1),
|
||||
apply_timeout : max(+config.apply.timeout || 5, 1),
|
||||
apply_display : max(+config.apply.display || 1.5, 1),
|
||||
rollback_token : rollback_token
|
||||
} }`, '/', '\\/') }});
|
||||
</script>
|
||||
@@ -0,0 +1,73 @@
|
||||
{#
|
||||
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.
|
||||
-#}
|
||||
|
||||
{% include('header') %}
|
||||
|
||||
<form method="post">
|
||||
{% if (fuser): %}
|
||||
<div class="alert-message warning">
|
||||
<p>{{ _('Invalid username and/or password! Please try again.') }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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">{{ _('Username') }}</label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="cbi-input-text" type="text" name="luci_username" value="{{ entityencode(duser, true) }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="cbi-value cbi-value-last">
|
||||
<label class="cbi-value-title">{{ _('Password') }}</label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="cbi-input-text" type="password" name="luci_password" />
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-page-actions">
|
||||
<input type="submit" value="{{ _('Log in') }}" class="btn cbi-button cbi-button-apply" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{%
|
||||
let https_ports = uci.get('uhttpd', 'main', 'listen_https') ?? [];
|
||||
|
||||
https_ports = uniq(filter(
|
||||
map(
|
||||
(type(https_ports) == 'string') ? split(https_port, /\s+/) : https_ports,
|
||||
e => +match(e, /\d+$/)?.[0]
|
||||
),
|
||||
p => (p >= 0 && p <= 65535)
|
||||
));
|
||||
%}
|
||||
|
||||
<script>
|
||||
var input = document.getElementsByName('luci_password')[0];
|
||||
|
||||
if (input)
|
||||
input.focus();
|
||||
|
||||
if (document.location.protocol != 'https:') {
|
||||
{{ https_ports }}.forEach(function(port) {
|
||||
var url = 'https://' + window.location.hostname + ':' + port + window.location.pathname;
|
||||
var img = new Image();
|
||||
|
||||
img.onload = function() { window.location = url };
|
||||
img.src = 'https://' + window.location.hostname + ':' + port + '{{ resource }}/icons/loading.svg?' + Math.random();
|
||||
|
||||
setTimeout(function() { img.src = '' }, 5000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{% include('footer') %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% include('header') %}
|
||||
|
||||
<div id="view">
|
||||
<div class="spinning">{{ _('Loading view…') }}</div>
|
||||
<script>
|
||||
L.require('ui').then(function(ui) {
|
||||
ui.instantiateView('{{ view }}');
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
{% include('footer') %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{%
|
||||
|
||||
import dispatch from 'luci.dispatcher';
|
||||
import request from 'luci.http';
|
||||
|
||||
global.handle_request = function(env) {
|
||||
let req = request(env, uhttpd.recv, uhttpd.send);
|
||||
|
||||
dispatch(req);
|
||||
|
||||
req.close();
|
||||
};
|
||||
@@ -0,0 +1,449 @@
|
||||
// Autogenerated by zoneinfo2ucode.pl
|
||||
|
||||
export default {
|
||||
'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': '<-03>3',
|
||||
'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/Coyhaique': '<-03>3',
|
||||
'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-au-Prince': 'EST5EDT,M3.2.0,M11.1.0',
|
||||
'America/Port_of_Spain': 'AST4',
|
||||
'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': '<-02>2<-01>,M3.5.0/-1,M10.5.0/0',
|
||||
'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': '<+08>-8',
|
||||
'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': '<+05>-5',
|
||||
'Arctic/Longyearbyen': 'CET-1CEST,M3.5.0,M10.5.0/3',
|
||||
'Asia/Aden': '<+03>-3',
|
||||
'Asia/Almaty': '<+05>-5',
|
||||
'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/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': '<+05>-5',
|
||||
'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/3,M10.5.0/4',
|
||||
'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',
|
||||
};
|
||||
Reference in new issue
Block a user