blob: 7f7af20f8b2ac5b6783fee86b1327acfb2573ba1 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// THIS IS AN AUTOMATICALLY COMBINED FILE. PLEASE EDIT source/*.js!!
/******************************************
Fetched from source/activity.js
******************************************/
function list_activity(state, json) {
let obj = document.getElementById('activity');
obj.innerHTML = ''; // clear object
if (json.activity && json.activity.length > 0) {
json.activity = json.activity.sort((a,b) => b.epoch - a.epoch);
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Activity log entries (%u):".format(json.activity.length));
div.inject(wheader);
let tbl = new HTML('table', { style: {fontSize: '0.8rem'}});
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Type'),
new HTML('th', {style: {width: '110px'}},'Added'),
new HTML('th', 'Text'),
]));
tbl.inject(tbh);
div.inject(tbl);
for (var i = 0; i < json.activity.length; i++) {
let res = json.activity[i];
let when = 'Never';
if (res.epoch) {
when = moment(res.epoch*1000.0).fromNow();
}
res.text = res.text.replace(/\b([a-f0-9]{8})[a-f0-9]{32}\b/g, (a, b) => b + "..");
let tr = new HTML('tr', {}, [
new HTML('td', {}, _kbd(res.ntype)),
new HTML('td', moment(res.epoch*1000.0).fromNow()),
new HTML('td', res.text)
]);
tbody.inject(tr);
}
tbl.inject(tbody);
obj.inject(div);
}
}
function init_activity(source) {
let obj = document.getElementById('activity');
obj.innerText = "Fetching activity, hang on...";
GET('./api/activity', list_activity, manage_error, {});
}
/******************************************
Fetched from source/banlist.js
******************************************/
function manage_error(state, json) {
if (!json) json = state;
modal("An error occured", json.message || json.reason, "error");
}
function list_bans(state, json) {
let obj = document.getElementById('banlist');
obj.innerHTML = ''; // clear object
if (json.bans && json.bans.length > 0) {
json.bans = json.bans.sort((a,b) => b.epoch - a.epoch);
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Currently banned IP addresses/networks (%u):".format(json.bans.length));
div.inject(wheader);
let tbl = new HTML('table', { style: {fontSize: '0.8rem'}});
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Source'),
new HTML('th', {style: {width: '120px'}},'Last updated'),
new HTML('th', 'Reason for ban'),
new HTML('th', {style: {width: '100px'}},'Actions')
]));
tbl.inject(tbh);
div.inject(tbl);
tbl.inject(tbody);
for (var i = 0; i < json.bans.length; i++) {
if (i > 50) {
div.inject(_hr());
div.inject(_i("Only the 50 latest results are shown here. Use the search feature to find more IPs."));
break
}
let res = json.bans[i];
let timeout = 'unknown?';
if (res.epoch) {
timeout = moment(res.epoch*1000.0).fromNow();
}
let actions = [
new HTML('a', {href:'javascript:void();', onclick: "remove_banlist('%s');".format(res.rid)}, "Remove ban")
];
let name = res.ip;
if (res.dns && res.dns.length > 0 && res.dns != res.ip && res.dns.match) {
let m = res.dns.match(/(([^.]+\.((com?|net|org|edu|)\.)?)[^.]+)$/);
lastbit = m ? m[1] : '';
name = "%s (%s)".format(res.ip, lastbit);
}
res.reason = res.reason.replace(/\b(\d{4,16})\b/g, (a,b) => parseInt(b).pretty());
let tr = new HTML('tr', {}, [
new HTML('td', {}, _kbd(name)),
new HTML('td', timeout),
new HTML('td', res.reason),
new HTML('td', {}, actions)
]);
tbody.inject(tr);
}
obj.inject(div);
}
}
function init_banlist(source) {
let obj = document.getElementById('banlist');
obj.innerText = "Fetching ban list, hang on...";
GET('./api/bans', list_bans, manage_error, {});
}
function banlist_added(state, json) {
alert("Banlist entry added!");
location.reload();
}
function add_banlist() {
let source = document.getElementById('source').value;
let target = document.getElementById('target').value;
let reason = document.getElementById('reason').value;
let force = document.getElementById('force').checked;
let m = source.match(/([a-f0-9:.\/]+)/);
if (m) {
source = m[0];
PUT('./api/bans', banlist_added, {}, manage_error, {
source: source,
target: target,
reason: reason,
force: force
});
} else {
alert("Invalid source address entered!");
}
return false
}
/******************************************
Fetched from source/base-http-extensions.js
******************************************/
// URL calls currently 'in escrow'. This controls the spinny wheel animation
var async_escrow = {}
var async_maxwait = 250; // ms to wait before displaying spinner
var async_status = 'clear';
var async_cache = {}
// Escrow spinner check
async function escrow_check() {
let now = new Date();
let show_spinner = false;
for (var k in async_escrow) {
if ( (now - async_escrow[k]) > async_maxwait ) {
show_spinner = true;
break;
}
}
// Fetch or create the spinner
let spinner = document.getElementById('spinner');
if (!spinner) {
spinner = new HTML('div', { id: 'spinner', class: 'spinner'});
spinwheel = new HTML('div', {id: 'spinwheel', class: 'spinwheel'});
spinner.inject(spinwheel);
spinner.inject(new HTML('h2', {}, "Loading, please wait.."));
document.body.appendChild(spinner);
}
// Show or don't show spinner?
if (show_spinner) {
spinner.style.display = 'block';
if (async_status === 'clear') {
console.log("Waiting for JSON resource, deploying spinner");
async_status = 'waiting';
}
} else {
spinner.style.display = 'none';
if (async_status === 'waiting') {
console.log("All URLs out of escrow, dropping spinner");
async_status = 'clear';
}
}
}
async function async_snap(error) {
msg = await error.text();
msg = (msg||"An unknown error occured, possibly an internal browser issue").replace(/<.*?>/g, ""); // strip HTML tags
modal("An error occured", "An error code %u occured while trying to fetch %s:\n%s".format(error.status, error.url, msg), "error");
}
// Asynchronous GET call
async function GET(url, callback, state, snap, method, body) {
method = method || 'get'
console.log("Fetching JSON resource at %s".format(url))
let pkey = "GET-%s-%s".format(callback, url);
let res = undefined;
let res_json = undefined;
state = state || {};
state.url = url;
if (state && state.cached === true && async_cache[url]) {
console.log("Fetching %s from cache".format(url));
res_json = async_cache[url];
}
else {
try {
let meta = {method: method, credentials: 'include', referrerPolicy: 'unsafe-url', headers: {'x-original-referral': document.referrer}};
if (body) {
meta.body = body;
}
console.log("putting %s in escrow...".format(url));
async_escrow[pkey] = new Date(); // Log start of request in escrow dict
const rv = await fetch(url, meta); // Wait for resource...
// Since this is an async request, the request may have been canceled
// by the time we get a response. Only do callback if not.
if (async_escrow[pkey] !== undefined) {
delete async_escrow[pkey]; // move out of escrow when fetched
res = rv;
}
}
catch (e) {
delete async_escrow[pkey]; // move out of escrow if failed
console.log("The URL %s could not be fetched: %s".format(url, e));
if (snap) snap({}, {reason: e});
else {
modal("An error occured", "An error occured while trying to fetch %s:\n%s".format(url, e), "error");
}
}
}
if (res !== undefined || res_json !== undefined) {
// We expect a 2xx return code (usually 200 or 201), snap otherwise
if ((res_json) || (res.status >= 200 && res.status < 300)) {
console.log("Successfully fetched %s".format(url))
if (res_json) {
js = res_json;
} else {
js = await res.json();
async_cache[url] = js;
}
if (callback) {
callback(state, js);
} else {
console.log("No callback function was registered for %s, ignoring result.".format(url));
}
} else {
console.log("URL %s returned HTTP code %u, snapping!".format(url, res.status));
try {
js = await res.json();
snap(state, js);
return;
} catch (e) {}
if (snap) snap(res);
else async_snap(res);
}
}
}
// DELETE wrapper
async function DELETE(url, callback, state, snap, json) {
return GET(url, callback, state, snap, 'delete', JSON.stringify(json));
}
// POST wrapper
async function POST(url, callback, state, snap, json) {
return GET(url, callback, state, snap, 'post', JSON.stringify(json));
}
// PUT wrapper
async function PUT(url, callback, state, snap, json) {
return GET(url, callback, state, snap, 'put', JSON.stringify(json));
}
// PATCH wrapper
async function PATCH(url, callback, state, snap, json) {
return GET(url, callback, state, snap, 'PATCH', JSON.stringify(json));
}
// whatwg fetch for IE
(function(self) {
'use strict';
if (self.fetch) {
return
}
var support = {
searchParams: 'URLSearchParams' in self,
iterable: 'Symbol' in self && 'iterator' in Symbol,
blob: 'FileReader' in self && 'Blob' in self && (function() {
try {
new Blob()
return true
} catch(e) {
return false
}
})(),
formData: 'FormData' in self,
arrayBuffer: 'ArrayBuffer' in self
}
if (support.arrayBuffer) {
var viewClasses = [
'[object Int8Array]',
'[object Uint8Array]',
'[object Uint8ClampedArray]',
'[object Int16Array]',
'[object Uint16Array]',
'[object Int32Array]',
'[object Uint32Array]',
'[object Float32Array]',
'[object Float64Array]'
]
var isDataView = function(obj) {
return obj && DataView.prototype.isPrototypeOf(obj)
}
var isArrayBufferView = ArrayBuffer.isView || function(obj) {
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
}
}
function normalizeName(name) {
if (typeof name !== 'string') {
name = String(name)
}
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
throw new TypeError('Invalid character in header field name')
}
return name.toLowerCase()
}
function normalizeValue(value) {
if (typeof value !== 'string') {
value = String(value)
}
return value
}
// Build a destructive iterator for the value list
function iteratorFor(items) {
var iterator = {
next: function() {
var value = items.shift()
return {done: value === undefined, value: value}
}
}
if (support.iterable) {
iterator[Symbol.iterator] = function() {
return iterator
}
}
return iterator
}
function Headers(headers) {
this.map = {}
if (headers instanceof Headers) {
headers.forEach(function(value, name) {
this.append(name, value)
}, this)
} else if (Array.isArray(headers)) {
headers.forEach(function(header) {
this.append(header[0], header[1])
}, this)
} else if (headers) {
Object.getOwnPropertyNames(headers).forEach(function(name) {
this.append(name, headers[name])
}, this)
}
}
Headers.prototype.append = function(name, value) {
name = normalizeName(name)
value = normalizeValue(value)
var oldValue = this.map[name]
this.map[name] = oldValue ? oldValue+','+value : value
}
Headers.prototype['delete'] = function(name) {
delete this.map[normalizeName(name)]
}
Headers.prototype.get = function(name) {
name = normalizeName(name)
return this.has(name) ? this.map[name] : null
}
Headers.prototype.has = function(name) {
return this.map.hasOwnProperty(normalizeName(name))
}
Headers.prototype.set = function(name, value) {
this.map[normalizeName(name)] = normalizeValue(value)
}
Headers.prototype.forEach = function(callback, thisArg) {
for (var name in this.map) {
if (this.map.hasOwnProperty(name)) {
callback.call(thisArg, this.map[name], name, this)
}
}
}
Headers.prototype.keys = function() {
var items = []
this.forEach(function(value, name) { items.push(name) })
return iteratorFor(items)
}
Headers.prototype.values = function() {
var items = []
this.forEach(function(value) { items.push(value) })
return iteratorFor(items)
}
Headers.prototype.entries = function() {
var items = []
this.forEach(function(value, name) { items.push([name, value]) })
return iteratorFor(items)
}
if (support.iterable) {
Headers.prototype[Symbol.iterator] = Headers.prototype.entries
}
function consumed(body) {
if (body.bodyUsed) {
return Promise.reject(new TypeError('Already read'))
}
body.bodyUsed = true
}
function fileReaderReady(reader) {
return new Promise(function(resolve, reject) {
reader.onload = function() {
resolve(reader.result)
}
reader.onerror = function() {
reject(reader.error)
}
})
}
function readBlobAsArrayBuffer(blob) {
var reader = new FileReader()
var promise = fileReaderReady(reader)
reader.readAsArrayBuffer(blob)
return promise
}
function readBlobAsText(blob) {
var reader = new FileReader()
var promise = fileReaderReady(reader)
reader.readAsText(blob)
return promise
}
function readArrayBufferAsText(buf) {
var view = new Uint8Array(buf)
var chars = new Array(view.length)
for (var i = 0; i < view.length; i++) {
chars[i] = String.fromCharCode(view[i])
}
return chars.join('')
}
function bufferClone(buf) {
if (buf.slice) {
return buf.slice(0)
} else {
var view = new Uint8Array(buf.byteLength)
view.set(new Uint8Array(buf))
return view.buffer
}
}
function Body() {
this.bodyUsed = false
this._initBody = function(body) {
this._bodyInit = body
if (!body) {
this._bodyText = ''
} else if (typeof body === 'string') {
this._bodyText = body
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
this._bodyBlob = body
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
this._bodyFormData = body
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this._bodyText = body.toString()
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
this._bodyArrayBuffer = bufferClone(body.buffer)
// IE 10-11 can't handle a DataView body.
this._bodyInit = new Blob([this._bodyArrayBuffer])
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
this._bodyArrayBuffer = bufferClone(body)
} else {
throw new Error('unsupported BodyInit type')
}
if (!this.headers.get('content-type')) {
if (typeof body === 'string') {
this.headers.set('content-type', 'text/plain;charset=UTF-8')
} else if (this._bodyBlob && this._bodyBlob.type) {
this.headers.set('content-type', this._bodyBlob.type)
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8')
}
}
}
if (support.blob) {
this.blob = function() {
var rejected = consumed(this)
if (rejected) {
return rejected
}
if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob)
} else if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([this._bodyArrayBuffer]))
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as blob')
} else {
return Promise.resolve(new Blob([this._bodyText]))
}
}
this.arrayBuffer = function() {
if (this._bodyArrayBuffer) {
return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
} else {
return this.blob().then(readBlobAsArrayBuffer)
}
}
}
this.text = function() {
var rejected = consumed(this)
if (rejected) {
return rejected
}
if (this._bodyBlob) {
return readBlobAsText(this._bodyBlob)
} else if (this._bodyArrayBuffer) {
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
} else if (this._bodyFormData) {
throw new Error('could not read FormData body as text')
} else {
return Promise.resolve(this._bodyText)
}
}
if (support.formData) {
this.formData = function() {
return this.text().then(decode)
}
}
this.json = function() {
return this.text().then(JSON.parse)
}
return this
}
// HTTP methods whose capitalization should be normalized
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
function normalizeMethod(method) {
var upcased = method.toUpperCase()
return (methods.indexOf(upcased) > -1) ? upcased : method
}
function Request(input, options) {
options = options || {}
var body = options.body
if (input instanceof Request) {
if (input.bodyUsed) {
throw new TypeError('Already read')
}
this.url = input.url
this.credentials = input.credentials
if (!options.headers) {
this.headers = new Headers(input.headers)
}
this.method = input.method
this.mode = input.mode
if (!body && input._bodyInit != null) {
body = input._bodyInit
input.bodyUsed = true
}
} else {
this.url = String(input)
}
this.credentials = options.credentials || this.credentials || 'omit'
if (options.headers || !this.headers) {
this.headers = new Headers(options.headers)
}
this.method = normalizeMethod(options.method || this.method || 'GET')
this.mode = options.mode || this.mode || null
this.referrer = null
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
throw new TypeError('Body not allowed for GET or HEAD requests')
}
this._initBody(body)
}
Request.prototype.clone = function() {
return new Request(this, { body: this._bodyInit })
}
function decode(body) {
var form = new FormData()
body.trim().split('&').forEach(function(bytes) {
if (bytes) {
var split = bytes.split('=')
var name = split.shift().replace(/\+/g, ' ')
var value = split.join('=').replace(/\+/g, ' ')
form.append(decodeURIComponent(name), decodeURIComponent(value))
}
})
return form
}
function parseHeaders(rawHeaders) {
var headers = new Headers()
// Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
// https://tools.ietf.org/html/rfc7230#section-3.2
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ')
preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
var parts = line.split(':')
var key = parts.shift().trim()
if (key) {
var value = parts.join(':').trim()
headers.append(key, value)
}
})
return headers
}
Body.call(Request.prototype)
function Response(bodyInit, options) {
if (!options) {
options = {}
}
this.type = 'default'
this.status = options.status === undefined ? 200 : options.status
this.ok = this.status >= 200 && this.status < 300
this.statusText = 'statusText' in options ? options.statusText : 'OK'
this.headers = new Headers(options.headers)
this.url = options.url || ''
this._initBody(bodyInit)
}
Body.call(Response.prototype)
Response.prototype.clone = function() {
return new Response(this._bodyInit, {
status: this.status,
statusText: this.statusText,
headers: new Headers(this.headers),
url: this.url
})
}
Response.error = function() {
var response = new Response(null, {status: 0, statusText: ''})
response.type = 'error'
return response
}
var redirectStatuses = [301, 302, 303, 307, 308]
Response.redirect = function(url, status) {
if (redirectStatuses.indexOf(status) === -1) {
throw new RangeError('Invalid status code')
}
return new Response(null, {status: status, headers: {location: url}})
}
self.Headers = Headers
self.Request = Request
self.Response = Response
self.fetch = function(input, init) {
return new Promise(function(resolve, reject) {
var request = new Request(input, init)
var xhr = new XMLHttpRequest()
xhr.onload = function() {
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
}
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
var body = 'response' in xhr ? xhr.response : xhr.responseText
resolve(new Response(body, options))
}
xhr.onerror = function() {
reject(new TypeError('Network request failed'))
}
xhr.ontimeout = function() {
reject(new TypeError('Network request failed'))
}
xhr.open(request.method, request.url, true)
if (request.credentials === 'include') {
xhr.withCredentials = true
} else if (request.credentials === 'omit') {
xhr.withCredentials = false
}
if ('responseType' in xhr && support.blob) {
xhr.responseType = 'blob'
}
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value)
})
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
})
}
self.fetch.polyfill = true
})(typeof self !== 'undefined' ? self : this);
/******************************************
Fetched from source/base-js-extensions.js
******************************************/
/**
* String formatting prototype
* A'la printf
*/
String.prototype.format = function() {
let args = arguments;
let n = 0;
let t = this;
let rtn = this.replace(/(?!%)?%([-+]*)([0-9.]*)([a-zA-Z])/g, function(m, pm, len, fmt) {
len = parseInt(len || '1');
// We need the correct number of args, balk otherwise, using ourselves to format the error!
if (args.length <= n) {
let err = "Error interpolating string '%s': Expected at least %u argments, only got %u!".format(t, n+1, args.length);
console.log(err);
throw err;
}
let varg = args[n];
n++;
switch (fmt) {
case 's':
if (typeof(varg) == 'function') {
varg = '(function)';
}
return varg;
// For now, let u, d and i do the same thing
case 'd':
case 'i':
case 'u':
varg = parseInt(varg).pad(len); // truncate to Integer, pad if needed
return varg;
}
});
return rtn;
}
/**
* Number prettification prototype:
* Converts 1234567 into 1,234,567 etc
*/
Number.prototype.pretty = function(fix) {
if (fix) {
return String(this.toFixed(fix)).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
}
return String(this.toFixed(0)).replace(/(\d)(?=(\d{3})+$)/g, '$1,');
};
/**
* Number padding
* usage: 123.pad(6) -> 000123
*/
Number.prototype.pad = function(n) {
var str;
str = String(this);
/* Do we need to pad? if so, do it using String.repeat */
if (str.length < n) {
str = "0".repeat(n - str.length) + str;
}
return str;
};
/* Func for converting a date to YYYY-MM-DD HH:MM */
Date.prototype.ISOBare = function() {
var M, d, h, m, y;
y = this.getFullYear();
m = (this.getMonth() + 1).pad(2);
d = this.getDate().pad(2);
h = this.getHours().pad(2);
M = this.getMinutes().pad(2);
return y + "-" + m + "-" + d + " " + h + ":" + M;
};
/* isArray: function to detect if an object is an array */
isArray = function(value) {
return value && typeof value === 'object' && value instanceof Array && typeof value.length === 'number' && typeof value.splice === 'function' && !(value.propertyIsEnumerable('length'));
};
/* isHash: function to detect if an object is a hash */
isHash = function(value) {
return value && typeof value === 'object' && !isArray(value);
};
/* Remove an array element by value */
Array.prototype.remove = function(val) {
var i, item, j, len;
for (i = j = 0, len = this.length; j < len; i = ++j) {
item = this[i];
if (item === val) {
this.splice(i, 1);
return this;
}
}
return this;
};
/* Check if array has value */
Array.prototype.has = function(val) {
var i, item, j, len;
for (i = j = 0, len = this.length; j < len; i = ++j) {
item = this[i];
if (item === val) {
return true;
}
}
return false;
};
/******************************************
Fetched from source/datepicker.js
******************************************/
var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var datepicker_spawner = null
var calendarpicker_spawner = null
var units = {
w: 'week',
d: 'day',
M: 'month',
y: 'year'
}
function fixupPicker(obj) {
obj.addEventListener("focus", function(event){
$('html').on('hide.bs.dropdown', function (e) {
return false;
});
});
obj.addEventListener("blur", function(event){
$('html').unbind('hide.bs.dropdown')
});
}
// makeSelect: Creates a <select> object with options
function makeSelect(options, id, selval) {
var sel = document.createElement('select')
sel.addEventListener("focus", function(event){
$('html').on('hide.bs.dropdown', function (e) {
return false;
});
});
sel.addEventListener("blur", function(event){
$('html').unbind('hide.bs.dropdown')
});
sel.setAttribute("name", id)
sel.setAttribute("id", id)
// For each options element, create it in the DOM
for (var key in options) {
var opt = document.createElement('option')
// Hash or array?
if (typeof key == "string") {
opt.setAttribute("value", key)
// Option is selected by default?
if (key == selval) {
opt.setAttribute("selected", "selected")
}
} else {
// Option is selected by default?
if (options[key] == selval) {
opt.setAttribute("selected", "selected")
}
}
opt.text = options[key]
sel.appendChild(opt)
}
return sel
}
// splitDiv: Makes a split div with 2 elements,
// and puts div2 into the right column,
// and 'name' as text in the left one.
function splitDiv(id, name, div2) {
var div = document.createElement('div')
var subdiv = document.createElement('div')
var radio = document.createElement('input')
radio.setAttribute("type", "radio")
radio.setAttribute("name", "datepicker_radio")
radio.setAttribute("value", name)
radio.setAttribute("id", "datepicker_radio_" + id)
radio.setAttribute("onclick", "calcTimespan('"+ id + "')")
var label = document.createElement('label')
label.innerHTML = "&nbsp; " + name + ": "
label.setAttribute("for", "datepicker_radio_" + id)
subdiv.appendChild(radio)
subdiv.appendChild(label)
subdiv.style.float = "left"
div2.style.float = "left"
subdiv.style.width = "120px"
subdiv.style.height = "48px"
div2.style.height = "48px"
div2.style.width = "250px"
div.appendChild(subdiv)
div.appendChild(div2)
return div
}
// calcTimespan: Calculates the value and representational text
// for the datepicker choice and puts it in the datepicker's
// spawning input/select element.
function calcTimespan(what) {
var wat = ""
var tval = ""
// Less than N units ago?
if (what == 'lt') {
// Get unit and how many units
var N = document.getElementById('datepicker_lti').value
var unit = document.getElementById('datepicker_lts').value
var unitt = units[unit]
if (parseInt(N) != 1) {
unitt += "s"
}
// If this makes sense, construct a humanly readable and a computer version
// of the timespan
if (N.length > 0) {
wat = "Less than " + N + " " + unitt + " ago"
tval = "lte=" + N + unit
}
}
// More than N units ago?
if (what == 'mt') {
// As above, get unit and no of units.
var N = document.getElementById('datepicker_mti').value
var unit = document.getElementById('datepicker_mts').value
var unitt = units[unit]
if (parseInt(N) != 1) {
unitt += "s"
}
// construct timespan val + description
if (N.length > 0) {
wat = "More than " + N + " " + unitt + " ago"
tval = "gte=" + N + unit
}
}
// Date range?
if (what == 'cd') {
// Get From and To values
var f = document.getElementById('datepicker_cfrom').value
var t = document.getElementById('datepicker_cto').value
// construct timespan val + description if both from and to are valid
if (f.length > 0 && t.length > 0) {
wat = "From " + f + " to " + t
tval = "dfr=" + f + "|dto=" + t
}
}
// If we calc'ed a value and spawner exists, update its key/val
if (datepicker_spawner && what && wat.length > 0) {
document.getElementById('datepicker_radio_' + what).checked = true
if (datepicker_spawner.options) {
datepicker_spawner.options[0].value = tval
datepicker_spawner.options[0].text = wat
} else if (datepicker_spawner.value) {
datepicker_spawner.value = wat
datepicker_spawner.setAttribute("data", tval)
}
}
}
// datePicker: spawns a date picker with various
// timespan options right next to the parent caller.
function datePicker(parent, seedPeriod) {
datepicker_spawner = parent
var div = document.getElementById('datepicker_popup')
// If the datepicker object doesn't exist, spawn it
if (!div) {
div = document.createElement('div')
var id = parseInt(Math.random() * 10000).toString(16)
div.setAttribute("id", "datepicker_popup")
div.setAttribute("class", "datepicker")
}
// Reset the contents of the datepicker object
div.innerHTML = ""
div.style.display = "block"
// Position the datepicker next to whatever called it
var bb = parent.getBoundingClientRect()
div.style.top = (bb.bottom + 8) + "px"
div.style.left = (bb.left + 32) + "px"
// -- Less than N $units ago
var ltdiv = document.createElement('div')
var lti = document.createElement('input')
lti.setAttribute("id", "datepicker_lti")
lti.style.width = "48px"
lti.setAttribute("onkeyup", "calcTimespan('lt')")
lti.setAttribute("onblur", "calcTimespan('lt')")
ltdiv.appendChild(lti)
var lts = makeSelect({
'd': "Day(s)",
'w': 'Week(s)',
'M': "Month(s)",
'y': "Year(s)"
}, 'datepicker_lts', 'm')
lts.setAttribute("onchange", "calcTimespan('lt')")
ltdiv.appendChild(lts)
ltdiv.appendChild(document.createTextNode(' ago'))
div.appendChild(splitDiv('lt', 'Less than', ltdiv))
// -- More than N $units ago
var mtdiv = document.createElement('div')
var mti = document.createElement('input')
mti.style.width = "48px"
mti.setAttribute("id", "datepicker_mti")
mti.setAttribute("onkeyup", "calcTimespan('mt')")
mti.setAttribute("onblur", "calcTimespan('mt')")
mtdiv.appendChild(mti)
var mts = makeSelect({
'd': "Day(s)",
'w': 'Week(s)',
'M': "Month(s)",
'y': "Year(s)"
}, 'datepicker_mts', 'm')
mtdiv.appendChild(mts)
mts.setAttribute("onchange", "calcTimespan('mt')")
mtdiv.appendChild(document.createTextNode(' ago'))
div.appendChild(splitDiv('mt', 'More than', mtdiv))
// -- Calendar timespan
// This is just two text fields, the calendarPicker sub-plugin populates them
var cdiv = document.createElement('div')
var cfrom = document.createElement('input')
cfrom.style.width = "90px"
cfrom.setAttribute("id", "datepicker_cfrom")
cfrom.setAttribute("onfocus", "showCalendarPicker(this)")
cfrom.setAttribute("onchange", "calcTimespan('cd')")
cdiv.appendChild(document.createTextNode('From: '))
cdiv.appendChild(cfrom)
var cto = document.createElement('input')
cto.style.width = "90px"
cto.setAttribute("id", "datepicker_cto")
cto.setAttribute("onfocus", "showCalendarPicker(this)")
cto.setAttribute("onchange", "calcTimespan('cd')")
cdiv.appendChild(document.createTextNode('To: '))
cdiv.appendChild(cto)
div.appendChild(splitDiv('cd', 'Date range', cdiv))
// -- Magic button that sends the timespan back to the caller
var okay = document.createElement('input')
okay.setAttribute("type", "button")
okay.setAttribute("value", "Okay")
okay.setAttribute("onclick", "setDatepickerDate()")
div.appendChild(okay)
parent.parentNode.appendChild(div)
document.body.setAttribute("onclick", "")
window.setTimeout(function() { document.body.setAttribute("onclick", "blurDatePicker(event)") }, 200)
lti.focus()
// This is for recalcing the set options if spawned from a
// select/input box with an existing value derived from an
// earlier call to datePicker
var ptype = ""
var pvalue = parent.hasAttribute("data") ? parent.getAttribute("data") : parent.value
if (pvalue.search(/=|-/) != -1) {
// Less than N units ago?
if (pvalue.match(/lte/)) {
var m = pvalue.match(/lte=(\d+)([dMyw])/)
ptype = 'lt'
if (m) {
document.getElementById('datepicker_lti').value = m[1]
var sel = document.getElementById('datepicker_lts')
for (var i in sel.options) {
if (parseInt(i) >= 0) {
if (sel.options[i].value == m[2]) {
sel.options[i].selected = "selected"
} else {
sel.options[i].selected = null
}
}
}
}
}
// More than N units ago?
if (pvalue.match(/gte/)) {
ptype = 'mt'
var m = pvalue.match(/gte=(\d+)([dMyw])/)
if (m) {
document.getElementById('datepicker_mti').value = m[1]
var sel = document.getElementById('datepicker_mts')
// Go through the unit values, select the one we use
for (var i in sel.options) {
if (parseInt(i) >= 0) {
if (sel.options[i].value == m[2]) {
sel.options[i].selected = "selected"
} else {
sel.options[i].selected = null
}
}
}
}
}
// Date range?
if (pvalue.match(/dfr/)) {
ptype = 'cd'
// Make sure we have both a dfr and a dto here, catch them
var mf = pvalue.match(/dfr=(\d+-\d+-\d+)/)
var mt = pvalue.match(/dto=(\d+-\d+-\d+)/)
if (mf && mt) {
// easy peasy, just set two text fields!
document.getElementById('datepicker_cfrom').value = mf[1]
document.getElementById('datepicker_cto').value = mt[1]
}
}
// Month??
if (pvalue.match(/(\d{4})-(\d+)/)) {
ptype = 'cd'
// Make sure we have both a dfr and a dto here, catch them
var m = pvalue.match(/(\d{4})-(\d+)/)
if (m.length == 3) {
// easy peasy, just set two text fields!
var dfrom = new Date(parseInt(m[1]),parseInt(m[2])-1,1, 0, 0, 0)
var dto = new Date(parseInt(m[1]),parseInt(m[2]),0, 23, 59, 59)
document.getElementById('datepicker_cfrom').value = m[0] + "-" + dfrom.getDate()
document.getElementById('datepicker_cto').value = m[0] + "-" + dto.getDate()
}
}
calcTimespan(ptype)
}
}
function datePickerValue(seedPeriod) {
// This is for recalcing the set options if spawned from a
// select/input box with an existing value derived from an
// earlier call to datePicker
var ptype = ""
var rv = seedPeriod
if (seedPeriod && seedPeriod.search && seedPeriod.search(/=|-/) != -1) {
// Less than N units ago?
if (seedPeriod.match(/lte/)) {
var m = seedPeriod.match(/lte=(\d+)([dMyw])/)
ptype = 'lt'
var unitt = units[m[2]]
if (parseInt(m[1]) != 1) {
unitt += "s"
}
rv = "Less than " + m[1] + " " + unitt + " ago"
}
// More than N units ago?
if (seedPeriod.match(/gte/)) {
ptype = 'mt'
var m = seedPeriod.match(/gte=(\d+)([dMyw])/)
var unitt = units[m[2]]
if (parseInt(m[1]) != 1) {
unitt += "s"
}
rv = "More than " + m[1] + " " + unitt + " ago"
}
// Date range?
if (seedPeriod.match(/dfr/)) {
ptype = 'cd'
var mf = seedPeriod.match(/dfr=(\d+-\d+-\d+)/)
var mt = seedPeriod.match(/dto=(\d+-\d+-\d+)/)
if (mf && mt) {
rv = "From " + mf[1] + " to " + mt[1]
}
}
// Month??
if (seedPeriod.match(/^(\d+)-(\d+)$/)) {
ptype = 'mr' // just a made up thing...(month range)
var mr = seedPeriod.match(/(\d+)-(\d+)/)
if (mr) {
dfrom = new Date(parseInt(mr[1]),parseInt(mr[2])-1,1, 0, 0, 0)
rv = months[dfrom.getMonth()] + ', ' + mr[1]
}
}
}
return rv
}
function datePickerDouble(seedPeriod) {
// This basically takes a date-arg and doubles it backwards
// so >=3M becomes =>6M etc. Also returns the cutoff for
// the original date and the span in days of the original
var ptype = ""
var rv = seedPeriod
var dbl = seedPeriod
var tspan = 1
var dfrom = new Date()
var dto = new Date()
// datepicker range?
if (seedPeriod && seedPeriod.search && seedPeriod.search(/=/) != -1) {
// Less than N units ago?
if (seedPeriod.match(/lte/)) {
var m = seedPeriod.match(/lte=(\d+)([dMyw])/)
ptype = 'lt'
rv = "<" + m[1] + m[2] + " ago"
dbl = "lte=" + (parseInt(m[1])*2) + m[2]
// N months ago
if (m[2] == "M") {
dfrom.setMonth(dfrom.getMonth()-parseInt(m[1]), dfrom.getDate())
}
// N days ago
if (m[2] == "d") {
dfrom.setDate(dfrom.getDate()-parseInt(m[1]))
}
// N years ago
if (m[2] == "y") {
dfrom.setYear(dfrom.getFullYear()-parseInt(m[1]))
}
// N weeks ago
if (m[2] == "w") {
dfrom.setDate(dfrom.getDate()-(parseInt(m[1])*7))
}
// Calc total duration in days for this time span
tspan = parseInt((dto.getTime() - dfrom.getTime() + 5000) / (1000*86400))
}
// More than N units ago?
if (seedPeriod.match(/gte/)) {
ptype = 'mt'
var m = seedPeriod.match(/gte=(\d+)([dMyw])/)
rv = ">" + m[1] + m[2] + " ago"
dbl = "gte=" + (parseInt(m[1])*2) + m[2]
tspan = parseInt(parseInt(m[1]) * 30.4)
dfrom = null
// Months
if (m[2] == "M") {
dto.setMonth(dto.getMonth()-parseInt(m[1]), dto.getDate())
}
// Days
if (m[2] == "d") {
dto.setDate(dto.getDate()-parseInt(m[1]))
}
// Years
if (m[2] == "y") {
dto.setYear(dto.getFullYear()-parseInt(m[1]))
}
// Weeks
if (m[2] == "w") {
dto.setDate(dto.getDate()-(parseInt(m[1])*7))
}
// Can't really figure out a timespan for this, so...null!
// This also sort of invalidates use on the trend page, but meh..
tspan = null
}
// Date range?
if (seedPeriod.match(/dfr/)) {
ptype = 'cd'
// Find from and to
var mf = seedPeriod.match(/dfr=(\d+)-(\d+)-(\d+)/)
var mt = seedPeriod.match(/dto=(\d+)-(\d+)-(\d+)/)
if (mf && mt) {
rv = "from " + mf[1] + " to " + mt[1]
// Starts at 00:00:00 on from date
dfrom = new Date(parseInt(mf[1]),parseInt(mf[2])-1,parseInt(mf[3]), 0, 0, 0)
// Ends at 23:59:59 on to date
dto = new Date(parseInt(mt[1]),parseInt(mt[2])-1,parseInt(mt[3]), 23, 59, 59)
// Get duration in days, add 5 seconds to we can floor the value and get an integer
tspan = parseInt((dto.getTime() - dfrom.getTime() + 5000) / (1000*86400))
// double the distance
var dpast = new Date(dfrom)
dpast.setDate(dpast.getDate() - tspan)
dbl = seedPeriod.replace(/dfr=[^|]+/, "dfr=" + (dpast.getFullYear()) + '-' + (dpast.getMonth()+1) + '-' + dpast.getDate())
} else {
tspan = 0
}
}
}
// just N days?
else if (parseInt(seedPeriod).toString() == seedPeriod.toString()) {
tspan = parseInt(seedPeriod)
dfrom.setDate(dfrom.getDate() - tspan)
dbl = "lte=" + (tspan*2) + "d"
}
// Specific month?
else if (seedPeriod.match(/^(\d+)-(\d+)$/)) {
// just a made up thing...(month range)
ptype = 'mr'
var mr = seedPeriod.match(/(\d+)-(\d+)/)
if (mr) {
rv = seedPeriod
// Same as before, start at 00:00:00
dfrom = new Date(parseInt(mr[1]),parseInt(mr[2])-1,1, 0, 0, 0)
// end at 23:59:59
dto = new Date(parseInt(mr[1]),parseInt(mr[2]),0, 23, 59, 59)
// B-A, add 5 seconds so we can floor the no. of days into an integer neatly
tspan = parseInt((dto.getTime() - dfrom.getTime() + 5000) / (1000*86400))
// Double timespan
var dpast = new Date(dfrom)
dpast.setDate(dpast.getDate() - tspan)
dbl = "dfr=" + (dpast.getFullYear()) + '-' + (dpast.getMonth()+1) + '-' + dpast.getDate() + "|dto=" + (dto.getFullYear()) + '-' + (dto.getMonth()+1) + '-' + dto.getDate()
} else {
tspan = 0
}
}
return [dbl, dfrom, dto, tspan]
}
// set date in caller and hide datepicker again.
function setDatepickerDate() {
calcTimespan()
blurDatePicker()
}
// findParent: traverse DOM and see if we can find a parent to 'el'
// called 'name'. This is used for figuring out whether 'el' has
// lost focus or not.
function findParent(el, name) {
if (el.getAttribute && el.getAttribute("id") == name) {
return true
}
if (el.parentNode && el.parentNode.getAttribute) {
if (el.parentNode.getAttribute("id") != name) {
return findParent(el.parentNode, name)
} else {
return true
}
} else {
return false;
}
}
// function for hiding the date picker
function blurDatePicker(evt) {
var es = evt ? (evt.target || evt.srcElement) : null;
if ((!es || !es.parentNode || (!findParent(es, "datepicker_popup") && !findParent(es, "calendarpicker_popup"))) && !(es ? es : "null").toString().match(/javascript:void/)) {
document.getElementById('datepicker_popup').style.display = "none"
$('html').trigger('hide.bs.dropdown')
}
}
// draws the actual calendar inside a calendarPicker object
function drawCalendarPicker(obj, date) {
obj.focus()
// Default to NOW for calendar.
var now = new Date()
// if called with an existing date (YYYY-MM-DD),
// convert it to a JS date object and use that for
// rendering the calendar
if (date) {
var ar = date.split(/-/)
now = new Date(ar[0],parseInt(ar[1])-1,ar[2])
}
var days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
var mat = now
// Go to first day of the month
mat.setDate(1)
obj.innerHTML = "<h3>" + months[mat.getMonth()] + ", " + mat.getFullYear() + ":</h3>"
var tm = mat.getMonth()
// -- Nav buttons --
// back-a-year button
var a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + (mat.getFullYear()-1) + '-' + (mat.getMonth()+1) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.innerHTML = "≪"
obj.appendChild(a)
// back-a-month button
a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + mat.getFullYear() + '-' + (mat.getMonth()) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.innerHTML = "&lt;"
obj.appendChild(a)
// forward-a-month button
a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + mat.getFullYear() + '-' + (mat.getMonth()+2) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.innerHTML = "&gt;"
obj.appendChild(a)
// forward-a-year button
a = document.createElement('a')
fixupPicker(a)
a.setAttribute("onclick", "drawCalendarPicker(this.parentNode, '" + (mat.getFullYear()+1) + '-' + (mat.getMonth()+1) + '-' + mat.getDate() + "');")
a.setAttribute("href", "javascript:void(0);")
a.innerHTML = "≫"
obj.appendChild(a)
obj.appendChild(document.createElement('br'))
// Table containing the dates of the selected month
var table = document.createElement('table')
table.setAttribute("border", "1")
table.style.margin = "0 auto"
// Add header day names
var tr = document.createElement('tr');
for (var m = 0; m < 7; m++) {
var td = document.createElement('th')
td.innerHTML = days[m]
tr.appendChild(td)
}
table.appendChild(tr)
// Until we hit the first day in a month, add blank days
tr = document.createElement('tr');
var weekday = mat.getDay()
if (weekday == 0) {
weekday = 7
}
weekday--;
for (var i = 0; i < weekday; i++) {
var td = document.createElement('td')
tr.appendChild(td)
}
// While still in this month, add day then increment date by 1 day.
while (mat.getMonth() == tm) {
weekday = mat.getDay()
if (weekday == 0) {
weekday = 7
}
weekday--;
if (weekday == 0) {
table.appendChild(tr)
tr = document.createElement('tr');
}
td = document.createElement('td')
// onclick for setting the calendarPicker's parent to this val.
td.setAttribute("onclick", "setCalendarDate('" + mat.getFullYear() + '-' + (mat.getMonth()+1) + '-' + mat.getDate() + "');")
td.innerHTML = mat.getDate()
mat.setDate(mat.getDate()+1)
tr.appendChild(td)
}
table.appendChild(tr)
obj.appendChild(table)
}
// callback for datePicker; sets the cd value to what date was picked
function setCalendarDate(what) {
$('html').on('hide.bs.dropdown', function (e) {
return false;
});
setTimeout(function() { $('html').unbind('hide.bs.dropdown');}, 250);
calendarpicker_spawner.value = what
var div = document.getElementById('calendarpicker_popup')
div.parentNode.focus()
div.style.display = "none"
calcTimespan('cd')
}
// caller for when someone clicks on a calendarPicker enabled field
function showCalendarPicker(parent, seedDate) {
calendarpicker_spawner = parent
// If supplied with a YYYY-MM-DD date, use this to seed the calendar
if (!seedDate) {
var m = parent.value.match(/(\d+-\d+(-\d+)?)/)
if (m) {
seedDate = m[1]
}
}
// Show or create the calendar object
var div = document.getElementById('calendarpicker_popup')
if (!div) {
div = document.createElement('div')
div.setAttribute("id", "calendarpicker_popup")
div.setAttribute("class", "calendarpicker")
document.getElementById('datepicker_popup').appendChild(div)
div.innerHTML = "Calendar goes here..."
}
div.style.display = "block"
var bb = parent.getBoundingClientRect()
// Align with the calling object, slightly below
div.style.top = (bb.bottom + 8) + "px"
div.style.left = (bb.right - 32) + "px"
drawCalendarPicker(div, seedDate)
}
/******************************************
Fetched from source/datetime-picker.js
******************************************/
var datetimes = {};
function validate_datetime(what, slug) {
let val = document.getElementById('%s_%s'.format(slug, what)).value;
let span = document.getElementById('%s_%s_span'.format(slug, what));
if (what == 'tz') {
datetimes[slug].tz = val;
validate_datetime('start', slug);
validate_datetime('stop', slug);
}
let spaninfo = '';
if (what == 'start' || what == 'stop') {
let dt = moment.tz(val, datetimes[slug].tz||"Etc/UTC").format();
if (val == '' || dt == 'Invalid date') {
if (datetimes[slug]['%s_require_valid'.format(what)] === true) {
spaninfo = "Invalid date!";
span.style.color = 'red';
} else {
dt = null;
}
} else {
spaninfo = dt;
span.style.color = 'green';
}
span.innerText = spaninfo;
datetimes[slug][what] = dt;
}
}
function datetime(slug, options) {
// defaults
let tz = options.tz || "Etc/UTC";
let start = options.start;
let stop = options.stop;
let title = options.title;
let start_require_valid = (options.requireStart === false) ? false: true; //default to true
let stop_require_valid = (options.requireStop === false) ? false: true; //default to true
datetimes[slug] = {
tz: tz,
start: start,
stop: stop,
title: title,
start_require_valid: start_require_valid,
stop_require_valid: stop_require_valid
};
let div = new HTML('div', { style: {textAlign: 'center'}});
// TZ
let tzdiv = new HTML('div', {style: {width: '180px', margin: '1%', position: 'relative', display: 'inline-block'}});
tzdiv.inject(new HTML('big', {}, "Timezone:"));
tzdiv.inject(br());
let zones = moment.tz.names();
let tzfield = new HTML('select', { id: '%s_tz'.format(slug), type: 'text', style: {width: '160px'}, onchange: 'validate_datetime("tz", "%s");'.format(slug)});
tzdiv.inject(tzfield);
for (var i = 0; i < zones.length; i++) {
let zn = String(zones[i]);
let opt = new HTML('option', { selected: zn == (tz || 'Etc/UTC') ? 'selected' : null}, zn);
tzfield.inject(opt);
}
tzdiv.inject(new HTML('span', {style: {display: 'inline-block', textIndent: '24px', width: '100%', height: '20px'},id: '%s_tz_span'.format(slug)}));
div.inject(tzdiv);
// START TIME
let stdiv = new HTML('div', {style: {width: '180px', margin: '1%', position: 'relative', display: 'inline-block'}});
stdiv.inject(new HTML('big',{},"From:"));
stdiv.inject(br());
let stfield = new HTML('input', { id: '%s_start'.format(slug), type: 'text', style: {width: '160px'}, value: start, onchange: 'validate_datetime("start", "%s");'.format(slug)});
stdiv.inject(stfield);
stdiv.inject(new HTML('span', {style: {display: 'inline-block', fontSize: '0.8rem', width: '100%', height: '20px'},id: '%s_start_span'.format(slug)}));
//starttimediv.inject(new HTML('p', {style: {padding: '12px'}}, "The date and time when the Call for Papers is open to submissions, relative to the local timezone above."));
div.inject(stdiv);
flatpickr(stfield, {enableTime: true});
// STOP TIME
let stopdiv = new HTML('div', {style: {width: '180px', margin: '1%', position: 'relative', display: 'inline-block'}});
stopdiv.inject(new HTML('big',{}, "To:"));
stopdiv.inject(br());
let stopfield = new HTML('input', { id: '%s_stop'.format(slug), type: 'text', style: {width: '160px'}, value: stop, onchange: 'validate_datetime("stop", "%s");'.format(slug)});
stopdiv.inject(stopfield);
stopdiv.inject(new HTML('span', {style: {display: 'inline-block', fontSize: '0.8rem', width: '100%', height: '20px'},id: '%s_stop_span'.format(slug)}));
//starttimediv.inject(new HTML('p', {style: {padding: '12px'}}, "The date and time when the Call for Papers is open to submissions, relative to the local timezone above."));
div.inject(stopdiv);
flatpickr(stopfield, {enableTime: true});
return div;
}
/******************************************
Fetched from source/fail2ban.js
******************************************/
function fail2ban_callback(state, json) {
let obj = document.getElementById('results');
let resno = json.results.whitelist.length + json.results.banlist.length + json.results.iptables.length;
let header = _h2("Found %u results for %s".format(resno, state.source));
obj.innerHTML = '';
obj.inject(header);
// whitelist results?
if (json.results.whitelist.length > 0) {
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Whitelist results (%u):".format(json.results.whitelist.length));
div.inject(wheader);
let tbl = new HTML('table');
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Source'),
new HTML('th', 'Times out'),
new HTML('th', 'Description'),
new HTML('th', 'Actions')
]));
tbl.inject(tbh);
div.inject(tbl);
for (var i = 0; i < json.results.whitelist.length; i++) {
let res = json.results.whitelist[i];
let timeout = 'never';
if (res.timeout) {
timeout = moment(res.timeout*1000.0).fromNow();
}
let actions = [
new HTML('a', {href:'javascript:void();', onclick: "remove_whitelist('%s');".format(res.rid)}, "Remove whitelisting")
];
let tr = new HTML('tr', {}, [
new HTML('td', {}, _kbd(res.ip)),
new HTML('td', timeout),
new HTML('td', res.reason),
new HTML('td', {}, actions)
]);
tbody.inject(tr);
}
tbl.inject(tbody);
obj.inject(div);
}
// banlist results?
if (json.results.banlist.length > 0) {
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Banlist results (%u):".format(json.results.banlist.length));
div.inject(wheader);
let tbl = new HTML('table');
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Source'),
new HTML('th', 'Last updated'),
new HTML('th', 'Reason for ban'),
new HTML('th', 'Actions')
]));
tbl.inject(tbh);
div.inject(tbl);
for (var i = 0; i < json.results.banlist.length; i++) {
let res = json.results.banlist[i];
let timeout = 'unknown?';
if (res.epoch) {
timeout = moment(res.epoch*1000.0).fromNow();
}
let actions = [
new HTML('a', {href:'javascript:void();', onclick: "remove_banlist('%s');".format(res.rid)}, "Remove ban")
];
let tr = new HTML('tr', {}, [
new HTML('td', {}, _kbd(res.ip)),
new HTML('td', timeout),
new HTML('td', res.reason),
new HTML('td', {}, actions)
]);
tbody.inject(tr);
}
tbl.inject(tbody);
obj.inject(div);
}
// iptables stuff?
if (json.results.iptables.length > 0) {
let div = _div();
div.inject(_hr());
obj.inject(div);
let iheader = _h3({class:'subtitle'},"Local iptables results (%u):".format(json.results.iptables.length));
div.inject(iheader);
if (json.results.iptables.length == 10) {
div.inject(_i("We're only showing the first ten results, there may be many more!"));
}
for (var i = 0; i < json.results.iptables.length; i++) {
let res = json.results.iptables[i];
let txt = _div("Found on %s, iptables line %u in the %s chain, as %s".format(res.hostname, res.linenumber, res.chain, res.source));
let link = new HTML('a', {style: {marginLeft: '10px'}, href:"whitelist.html?%s".format(res.source)}, "Create whitelist rule")
div.inject(txt);
txt.inject(link);
if (res.msg) {
txt.inject(br());
txt.inject(_i({style: {color: '#963'}}, res.msg));
txt.style.marginBottom = '5px';
}
}
}
}
function old_fail2ban(e) {
if (e && e.state && e.state.what == 'search') {
search(e.state.source, true);
}
}
function fail2ban(source, nopush) {
let obj = document.getElementById('results');
let m = source.match(/([a-z0-9:.\/]+)/);
if (m) {
source = m[0];
obj.innerText = "Searching, hang on...";
if (!nopush) history.pushState({what: 'search', source: source}, "Search: " + source, '?' + source);
window.onpopstate = old_search;
let s = document.getElementById('source');
if (s) s.value = source;
POST('./api/fail2ban', fail2ban_callback, {source: source}, manage_error, {source: source});
} else if (source.length > 0) {
alert("Invalid IP or CIDR notation entered!")
}
return false
}
/******************************************
Fetched from source/ruleset.js
******************************************/
var rule_json = {}
function create_rule_form(form, rid) {
if (!rid || rid == '') {
rid = 'new';
rule_json['new'] = {
name: '',
type: '',
span: 24,
limit: '',
query: [],
}
}
let n = _input({type: 'text', style: {width: '300px'}, id: "%s_name".format(rid), value: rule_json[rid].name});
form.inject("Name of rule: ");
form.inject(n);
form.inject(br());
// type
let s = _select({id: "%s_type".format(rid)});
s.inject(_option({value: 'httpd_traffic', selected: rule_json[rid].type == 'httpd_traffic' ? 'selected' : null}, 'HTTPd traffic (bytes)'));
s.inject(_option({value: 'httpd_visits', selected: rule_json[rid].type == 'httpd_visits' ? 'selected' : null}, 'HTTPd visits (requests)'));
form.inject("Type of rule: ");
form.inject(s);
form.inject(br());
// span
let sp = _select({id: "%s_span".format(rid)});
sp.inject(_option({value: 1, selected: rule_json[rid].span == 1 ? 'selected' : null}, '1 hour'));
sp.inject(_option({value: 6, selected: rule_json[rid].span == 6 ? 'selected' : null}, '6 hours'));
sp.inject(_option({value: 12, selected: rule_json[rid].span == 12 ? 'selected' : null}, '12 hours'));
sp.inject(_option({value: 24, selected: rule_json[rid].span == 24 ? 'selected' : null}, '24 hours'));
sp.inject(_option({value: (24*7), selected: rule_json[rid].span == (24*7) ? 'selected' : null}, 'one week'));
sp.inject(_option({value: (24*30), selected: rule_json[rid].span == (24*30) ? 'selected' : null}, 'one month'));
form.inject("Time span: ");
form.inject(sp);
form.inject(br());
let l = _input({type: 'number', style: {width: '120px'}, id: "%s_limit".format(rid), value: rule_json[rid].limit || 0});
form.inject("Traffic/Request limit: ");
form.inject(l);
form.inject(br());
let q = _textarea({style: {width: '300px', height: '120px'},id: "%s_query".format(rid)}, rule_json[rid].query.join("\n"));
form.inject("Query parameters: ");
form.inject(q);
form.inject(br());
form.inject(_input({type: 'submit', value: 'Save rule'}))
}
function list_rules(state, json) {
let obj = document.getElementById('rules');
obj.innerHTML = ''; // clear object
if (json.rules && json.rules.length > 0) {
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Current ban rules (%u):".format(json.rules.length));
div.inject(wheader);
let tbl = new HTML('table', { style: {fontSize: '0.8rem'}});
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Ruleset'),
]));
tbl.inject(tbh);
div.inject(tbl);
for (var i = 0; i < json.rules.length; i++) {
let res = json.rules[i];
rule_json[res.rid] = res;
let innards = _div();
let form = _form({onsubmit: "add_rule('%s'); return false;".format(res.rid)});
create_rule_form(form, res.rid);
innards.inject(form);
let tr = new HTML('tr', {}, [
new HTML('td', {style: {padding: '5px'}}, [
_a({href: 'javascript:void();', onclick:"showrule('%s');".format(res.rid)}, _kbd(res.name)),
" ",
_a({style: {color: '#930', float: 'right'}, href: 'javascript:void();', onclick:"remove_rule('%s');".format(res.rid)}, "Remove ruleset"),
_div({id: res.rid, style: {margin: '6px', background: '#3692', border: '1.5px solid #3339', padding: '3px', display: 'none'}}, innards)
])
]);
tbody.inject(tr);
}
tbl.inject(tbody);
obj.inject(div);
obj.inject(_hr());
obj.inject(_h3("Create a new rule:"));
let form = _form({onsubmit: "add_rule('new'); return false;"});
create_rule_form(form, '');
obj.inject(form);
}
}
function showrule(rid) {
let obj = document.getElementById(rid)
obj.style.display = obj.style.display == 'none' ? 'block' : 'none';
}
function init_rules(source) {
let obj = document.getElementById('rules');
obj.innerText = "Fetching rules, hang on...";
GET('./api/rules', list_rules, manage_error, {});
}
function remove_rule(rid) {
DELETE('./api/rules', rule_removed, {rid: rid}, manage_error, {rid: rid});
}
function rule_added(state, json) {
alert("Rule entry added!");
location.reload();
}
function rule_removed(state, json) {
alert("Rule entry removed!");
location.reload();
}
function add_rule(rid) {
let name = document.getElementById('%s_name'.format(rid)).value;
let rtype = document.getElementById('%s_type'.format(rid)).value;
let span = parseInt(document.getElementById('%s_span'.format(rid)).value);
let limit = parseInt(document.getElementById('%s_limit'.format(rid)).value);
let query = document.getElementById('%s_query'.format(rid)).value.split(/\r?\n/);
let entry = {
name: name,
type: rtype,
span: span,
limit: limit,
query: query
}
if (rid && rid != 'new') {
entry.rid = rid
}
if (span > 0 && limit > 0 && query.length > 0) {
PUT('./api/rules', rule_added, {}, manage_error, entry);
} else {
alert("Span, query lines, and limits must be greater than 0!");
}
return false
}
/******************************************
Fetched from source/scaffolding-html.js
******************************************/
/**
* HTML: DOM creator class
* args:
* - type: HTML element type (div, table, p etc) to produce
* - params: hash of element params to add (class, style etc)
* - children: optional child or children objects to insert into the new element
* Example:
* div = new HTML('div', {
* class: "footer",
* style: {
* fontWeight: "bold"
* }
#}, "Some text inside a div")
*/
var txt = (msg) => document.createTextNode(msg);
var HTML = (function() {
function HTML(type, params, children) {
/* create the raw element, or clone if passed an existing element */
var child, j, len, val;
if (typeof type === 'object') {
this.element = type.cloneNode();
} else {
this.element = document.createElement(type);
}
/* If params have been passed, set them */
if (isHash(params)) {
for (var key in params) {
val = params[key];
/* Standard string value? */
if (typeof val === "string" || typeof val === 'number') {
this.element.setAttribute(key, val);
} else if (isArray(val)) {
/* Are we passing a list of data to set? concatenate then */
this.element.setAttribute(key, val.join(" "));
} else if (isHash(val)) {
/* Are we trying to set multiple sub elements, like a style? */
for (var subkey in val) {
let subval = val[subkey];
if (!this.element[key]) {
throw "No such attribute, " + key + "!";
}
this.element[key][subkey] = subval;
}
}
}
} else {
if (!children) { children = params } // shortcut!
}
/* If any children have been passed, add them to the element */
if (children) {
/* If string, convert to textNode using txt() */
if (typeof children === "string") {
this.element.inject(txt(children));
} else {
/* If children is an array of elems, iterate and add */
if (isArray(children)) {
for (j = 0, len = children.length; j < len; j++) {
child = children[j];
/* String? Convert via txt() then */
if (typeof child === "string") {
this.element.inject(txt(child));
} else {
/* Plain element, add normally */
this.element.inject(child);
}
}
} else {
/* Just a single element, add it */
this.element.inject(children);
}
}
}
return this.element;
}
return HTML;
})();
/**
* prototype injector for HTML elements:
* Example: mydiv.inject(otherdiv)
*/
HTMLElement.prototype.inject = function(child) {
var item, j, len;
if (isArray(child)) {
for (j = 0, len = child.length; j < len; j++) {
item = child[j];
if (typeof item === 'string') {
item = txt(item);
}
this.appendChild(item);
}
} else {
if (typeof child === 'string') {
child = txt(child);
}
this.appendChild(child);
}
return child;
};
/**
* prototype for emptying an html element
*/
HTMLElement.prototype.empty = function() {
var ndiv;
ndiv = this.cloneNode();
this.parentNode.replaceChild(ndiv, this);
return ndiv;
};
function toggleView(id) {
let obj = document.getElementById(id);
if (obj) {
obj.style.display = (obj.style.display == 'block') ? 'none' : 'block';
}
}
function br() {
return new HTML('br');
}
// construction shortcuts for various elements
let _a = (a,b) => new HTML('a', a,b);
let _b = (a,b) => new HTML('b', a,b);
let _p = (a,b) => new HTML('p', a,b);
let _i = (a,b) => new HTML('i', a, b);
let _div = (a,b) => new HTML('div', a, b);
let _input = (a,b) => new HTML('input', a, b);
let _select = (a,b) => new HTML('select', a, b);
let _option = (a,b) => new HTML('option', a, b);
let _h1 = (a,b) => new HTML('h1', a, b);
let _h2 = (a,b) => new HTML('h2', a, b);
let _h3 = (a,b) => new HTML('h3', a, b);
let _h4 = (a,b) => new HTML('h4', a, b);
let _h5 = (a,b) => new HTML('h5', a, b);
let _kbd = (a,b) => new HTML('kbd', a, b);
let _pre = (a,b) => new HTML('pre', a, b);
let _form = (a,b) => new HTML('form', a, b);
let _hr = (a,b) => new HTML('hr', a, b);
let _span = (a,b) => new HTML('span', a, b);
let _textarea = (a,b) => new HTML('textarea', a, b);
let _get = (a) => document.getElementById(a);
function billitem(a,b,c, total) {
let d = _div({style: {position: 'relative', display: 'block'}});
if (total) { d.style.fontWeight = 'bold'; d.style.borderTop = '2px solid #444'; }
let da = _div({style: {position: 'relative', width: '600px', display: 'inline-block'}}, a);
let db = _div({style: {position: 'relative', width: '60px', display: 'inline-block'}}, b);
let dc = _div({style: {position: 'relative', width: '80px', textAlign: 'right', display: 'inline-block'}}, c);
d.inject(da);
d.inject(db);
d.inject(dc);
return d;
}
// Generic modal function
function modal(title, msg, type, isHTML) {
let modal = document.getElementById('modal');
let text = document.getElementById('modal_text');
if (modal == undefined) {
text = new HTML('p', {id: 'modal_text'}, "");
modal = new HTML('div', { id: 'modal'}, [
new HTML('div', {id: 'modal_content'}, [
new HTML('span', {id: 'modal_close', onclick: 'document.getElementById("modal").style.display = "none";'}, 'X'),
new HTML('h2', {id: 'modal_title'}, title),
new HTML('div', {}, text)
])
]);
document.body.appendChild(modal);
}
if (type) {
modal.setAttribute("class", "modal_" + type);
} else {
modal.setAttribute("class", undefined);
}
modal.style.display = 'block';
document.getElementById('modal_title').innerText = title;
// If we trust HTML, use it. Otherwise only show as textNode.
if (isHTML) {
text.innerHTML = msg;
} else {
msg = (typeof(msg) == "string") ? msg : "An internal browser error occurred.";
msg = msg.replace(/<.*?>/g, ""); // strip HTML tags
text.innerText = msg;
}
}
/******************************************
Fetched from source/search.js
******************************************/
function ban_removed(state, json) {
alert("Ban removed! IP whitelisted for one hour to flush bans.");
location.reload();
}
function remove_banlist(rid) {
DELETE('./api/bans', ban_removed, {rule: rid}, manage_error, {rule: rid});
}
function whitelist_removed(state, json) {
alert("Whitelisting removed!");
location.reload();
}
function remove_whitelist(rid) {
DELETE('./api/whitelist', whitelist_removed, {rule: rid}, manage_error, {rule: rid});
}
function search_callback(state, json) {
let obj = document.getElementById('results');
let resno = json.results.whitelist.length + json.results.banlist.length + json.results.iptables.length;
let header = _h2("Found %u results".format(resno));
obj.innerHTML = '';
obj.inject(header);
// whitelist results?
if (json.results.whitelist.length > 0) {
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Whitelist results (%u):".format(json.results.whitelist.length));
div.inject(wheader);
let tbl = new HTML('table');
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Source'),
new HTML('th', 'Times out'),
new HTML('th', 'Description'),
new HTML('th', 'Actions')
]));
tbl.inject(tbh);
div.inject(tbl);
for (var i = 0; i < json.results.whitelist.length; i++) {
let res = json.results.whitelist[i];
let timeout = 'never';
if (res.timeout) {
timeout = moment(res.timeout*1000.0).fromNow();
}
let actions = [
new HTML('a', {href:'javascript:void();', onclick: "remove_whitelist('%s');".format(res.rid)}, "Remove whitelisting")
];
let tr = new HTML('tr', {}, [
new HTML('td', {}, _kbd(res.ip)),
new HTML('td', timeout),
new HTML('td', res.reason),
new HTML('td', {}, actions)
]);
tbody.inject(tr);
}
tbl.inject(tbody);
obj.inject(div);
}
// banlist results?
if (json.results.banlist.length > 0) {
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Banlist results (%u):".format(json.results.banlist.length));
div.inject(wheader);
let tbl = new HTML('table');
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Source'),
new HTML('th', 'Last updated'),
new HTML('th', 'Reason for ban'),
new HTML('th', 'Actions')
]));
tbl.inject(tbh);
div.inject(tbl);
for (var i = 0; i < json.results.banlist.length; i++) {
let res = json.results.banlist[i];
let timeout = 'unknown?';
if (res.epoch) {
timeout = moment(res.epoch*1000.0).fromNow();
}
let actions = [
new HTML('a', {href:'javascript:void();', onclick: "remove_banlist('%s');".format(res.rid)}, "Remove ban")
];
let tr = new HTML('tr', {}, [
new HTML('td', {}, _kbd(res.ip)),
new HTML('td', timeout),
new HTML('td', res.reason),
new HTML('td', {}, actions)
]);
tbody.inject(tr);
}
tbl.inject(tbody);
obj.inject(div);
}
// iptables stuff?
if (json.results.iptables.length > 0) {
let div = _div();
div.inject(_hr());
obj.inject(div);
let iheader = _h3({class:'subtitle'},"Local iptables results (%u):".format(json.results.iptables.length));
div.inject(iheader);
if (json.results.iptables.length == 10) {
div.inject(_i("We're only showing the first ten results, there may be many more!"));
}
for (var i = 0; i < json.results.iptables.length; i++) {
let res = json.results.iptables[i];
let txt = _div("Found on %s, iptables line %u in the %s chain, as %s".format(res.hostname, res.linenumber, res.chain, res.source));
let link = new HTML('a', {style: {marginLeft: '10px'}, href:"whitelist.html?%s".format(res.source)}, "Create whitelist rule")
div.inject(txt);
txt.inject(link);
}
}
}
function old_search(e) {
if (e && e.state && e.state.what == 'search') {
search(e.state.source, true);
}
}
function search(source, nopush) {
let obj = document.getElementById('results');
let m = source.match(/([a-f0-9:.\/]+)/);
if (m) {
source = m[0];
obj.innerText = "Searching, hang on...";
if (!nopush) history.pushState({what: 'search', source: source}, "Search: " + source, '?' + source);
window.onpopstate = old_search;
let s = document.getElementById('source');
if (s) s.value = source;
POST('./api/search', search_callback, {source: source}, manage_error, {source: source});
} else if (source.length > 0) {
alert("Invalid IP or CIDR notation entered!")
}
return false
}
/******************************************
Fetched from source/whitelist.js
******************************************/
function list_whites(state, json) {
let obj = document.getElementById('whitelist');
obj.innerHTML = ''; // clear object
if (json.whitelist && json.whitelist.length > 0) {
json.whitelist = json.whitelist.sort((a,b) => b.epoch - a.epoch);
let div = _div();
div.inject(_hr());
let wheader = _h3({class:'subtitle'},"Current whitelist entries (%u):".format(json.whitelist.length));
div.inject(wheader);
let tbl = new HTML('table', { style: {fontSize: '0.8rem'}});
let tbh = new HTML('thead');
let tbody = new HTML('tbody');
tbh.inject(new HTML('tr', {}, [
new HTML('th', 'Source'),
new HTML('th', {style: {width: '110px'}},'Added'),
new HTML('th', {style: {width: '110px'}},'Times out'),
new HTML('th', 'Reason for whitelisting'),
new HTML('th', {style: {width: '140px'}},'Actions')
]));
tbl.inject(tbh);
div.inject(tbl);
for (var i = 0; i < json.whitelist.length; i++) {
let res = json.whitelist[i];
let timeout = 'Never';
if (res.timeout) {
timeout = moment(res.timeout*1000.0).fromNow();
}
let actions = [
new HTML('a', {href:'javascript:void();', onclick: "remove_whitelist('%s');".format(res.rid)}, "Remove whitelisting")
];
let name = res.ip;
if (res.dns && res.dns.length > 0) {
let m = res.dns.match(/([^.]+\.[^.]+)$/);
lastbit = m ? m[1] : '';
name = "%s (%s)".format(res.ip, lastbit);
}
let tr = new HTML('tr', {}, [
new HTML('td', {}, _kbd(name)),
new HTML('td', moment(res.epoch*1000.0).fromNow()),
new HTML('td', timeout),
new HTML('td', res.reason),
new HTML('td', {}, actions)
]);
tbody.inject(tr);
}
tbl.inject(tbody);
obj.inject(div);
}
}
function init_whitelist(source) {
let obj = document.getElementById('whitelist');
obj.innerText = "Fetching whitelist, hang on...";
GET('./api/whitelist', list_whites, manage_error, {});
if (source && source.length > 0) {
document.getElementById('source').value = source;
document.getElementById('force').checked = true;
document.getElementById('reason').value = "Manual whitelist for 1 hour to unban from iptables.";
document.getElementById('timeout').value = parseInt((new Date().getTime()/1000) + 3600);
}
}
function whitelist_added(state, json) {
alert("Whitelist entry added!");
location.reload();
}
function add_whitelist() {
let source = document.getElementById('source').value;
let target = document.getElementById('target').value;
let reason = document.getElementById('reason').value;
let timeout = document.getElementById('timeout').value;
let force = document.getElementById('force').checked;
if (timeout != 'never') {
timeout = parseInt(timeout);
} else {
timeout = 0;
}
let m = source.match(/([a-f0-9:.\/]+)/);
if (m) {
source = m[0];
PUT('./api/whitelist', whitelist_added, {}, manage_error, {
source: source,
target: target,
reason: reason,
timeout: timeout,
force: force
});
} else {
alert("Invalid source address entered!");
}
return false
}