blob: 86cf6f457e0c4df088f09b728c28ee23b3129a88 [file] [log] [blame]
// Async METHOD to API
async function METHOD(method = 'POST', url = '', data) {
let payload = {
method: method,
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: data ? {
'Content-Type': 'application/json'
} : {},
redirect: 'follow',
referrerPolicy: 'no-referrer'
}
if (data) {
payload.body = JSON.stringify(data);
}
try {
const response = await fetch(url, payload).catch( (e) => {throw e});
if (response.ok !== true) throw `HTTP Error ${response.status}: ${response.statusText}`
let js = response.json();
return js
} catch (e) {
alert(e);
}
}
// HTTP methods
let DELETE = (url, data) => METHOD('DELETE', url, data);
let GET = (url, data) => METHOD('GET', url, data);
let PATCH = (url, data) => METHOD('PATCH', url, data);
let POST = (url, data) => METHOD('POST', url, data);
let PUT = (url, data) => METHOD('PUT', url, data);
// Prettifier for large numbers (adds commas)
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,');
};
// HTML shortcuts
let htmlobj = (type, text) => { let obj = document.createElement(type); obj.innerText = text ? text : ""; return obj }
let _h1 = (title) => htmlobj('h1', title);
let _h2 = (title) => htmlobj('h2', title);
let _span = (title) => htmlobj('span', title);
let _p = (title) => htmlobj('p', title);
let _br = (title) => htmlobj('br');
let _hr = (title) => htmlobj('hr');
let _tr = () => htmlobj('tr');
let _td = (title) => htmlobj('td', title);
let _th = (title, width) => { let obj = htmlobj('th', title); if (width) obj.style.width = width + "px"; return obj }
let _table = () => htmlobj('table');
let _a = (txt) => htmlobj('a', txt);
async function unblock_ip(_entry, force = true) {
if (confirm(`This will unblock ${_entry.ip} and add it to the allow-list for 10 minutes. Continue?`)) {
result = await POST('allow', {
ip: _entry.ip,
reason: "Temporarily allow-listed for 10 minutes to unblock IP",
expires: parseInt(new Date().getTime()/1000) + 600,
force: force
})
if (result.success === true) {
alert("IP Unblocked. Please allow for a few moments for it to apply across the servers.");
location.reload();
} else {
alert(result.message);
}
}
return false;
}
async function remove_allow(_entry) {
if (confirm(`This will remove ${_entry.ip} from the allow list. Continue?`)) {
result = await DELETE('allow', {
ip: _entry.ip,
force: true
})
if (result.success === true) {
alert("IP Removed from allow-list.");
location.reload();
} else {
alert(result.message);
}
}
return false;
}
function unblock_link(entry, allowlist=false) {
let link = _a(allowlist ? 'Remove' : 'Unblock');
let _entry = entry;
link.setAttribute("href", 'javascript:void(0);');
if (allowlist === true) { link.addEventListener('click', () => remove_allow(_entry)); }
else {link.addEventListener('click', () => unblock_ip(_entry));}
return link;
}
async function prime_frontpage() {
let all = await GET("all");
let main = document.getElementById('main');
main.innerHTML = "";
let block_count = all.block.length.pretty();
let h1 = _h1(`Recent activity (${block_count} blocks in total)`);
main.appendChild(h1);
all.block.sort((a,b) => b.timestamp - a.timestamp); // sort desc by timestamp
// Recent blocks
let activity_table = _table();
activity_table.style.tableLayout = 'fixed';
main.appendChild(activity_table);
let theader = _tr();
theader.appendChild(_th('Source IP', 300));
theader.appendChild(_th('Added', 120));
theader.appendChild(_th('Expires', 120));
theader.appendChild(_th('Reason', 500));
theader.appendChild(_th('Actions', 100));
activity_table.appendChild(theader);
let results_shown = 0;
for (const entry of all.block) {
let tr = _tr();
let td_ip = _td(entry.ip);
td_ip.style.fontFamily = "monospace";
if (entry.ip.length > 16) td_ip.style.fontSize = "0.8rem";
let td_added = _td(moment(entry.timestamp*1000.0).fromNow());
let td_expires = _td(entry.expires > 0 ? moment(entry.expires*1000.0).fromNow() : 'Never');
let td_reason = _td(entry.reason);
let td_action = _td();
td_action.appendChild(unblock_link(entry));
tr.appendChild(td_ip);
tr.appendChild(td_added);
tr.appendChild(td_expires);
tr.appendChild(td_reason);
tr.appendChild(td_action);
activity_table.appendChild(tr);
results_shown++;
if (results_shown > 25 && all.block.length > 25) {
break
}
}
if (results_shown === 0) {
let tr = _tr();
tr.innerText = "No activity found...";
activity_table.appendChild(_tr);
}
}
async function prime_search(target, state) {
if (!state) {
window.history.pushState({}, '', `?search:${target}`);
}
let main = document.getElementById('main');
main.innerHTML = '';
if (target && target.length > 0) {
let title = _h1("Search results for " + target + ":");
main.appendChild(title);
let p = _p("Searching, please wait...");
main.appendChild(p);
let results = await POST('search', {source: target});
if (results.success === false) {
p.innerText = results.message;
return
}
main.removeChild(p);
// Allow list results
let h2 = _h2(`Allow list results (${results.allow.length})`);
main.appendChild(h2);
let allow_table = _table();
allow_table.style.tableLayout = 'fixed';
main.appendChild(allow_table);
let theader = _tr();
theader.appendChild(_th('Source IP', 300));
theader.appendChild(_th('Added', 120));
theader.appendChild(_th('Expires', 120));
theader.appendChild(_th('Reason', 500));
theader.appendChild(_th('Actions', 100));
allow_table.appendChild(theader);
let results_shown = 0;
results.allow.sort((a,b) => b.timestamp - a.timestamp); // sort desc by timestamp
for (const entry of results.allow) {
let tr = _tr();
let td_ip = _td(entry.ip);
let td_added = _td(moment(entry.timestamp * 1000.0).fromNow());
let td_expires = _td(entry.expires > 0 ? moment(entry.expires * 1000.0).fromNow() : 'Never');
let td_reason = _td(entry.reason);
let td_action = _td();
td_action.appendChild(unblock_link(entry, true));
td_ip.style.fontFamily = "monospace";
if (entry.ip.length > 16) td_ip.style.fontSize = "0.8rem";
tr.appendChild(td_ip);
tr.appendChild(td_added);
tr.appendChild(td_expires);
tr.appendChild(td_reason);
tr.appendChild(td_action);
allow_table.appendChild(tr);
results_shown++;
if (results_shown > 25 && results.block.length > 25) {
break
}
}
if (results_shown === 0) {
let tr = _tr();
tr.innerText = "No results found...";
allow_table.appendChild(tr);
}
// Block list results
let bh2 = _h2(`Block list results (${results.block.length})`);
main.appendChild(bh2);
let block_table = _table();
block_table.style.tableLayout = 'fixed';
main.appendChild(block_table);
let btheader = _tr();
btheader.appendChild(_th('Source IP', 300));
btheader.appendChild(_th('Added', 120));
btheader.appendChild(_th('Expires', 120));
btheader.appendChild(_th('Reason', 500));
btheader.appendChild(_th('Actions', 100));
block_table.appendChild(btheader);
results_shown = 0;
results.block.sort((a,b) => b.timestamp - a.timestamp); // sort desc by timestamp
for (const entry of results.block) {
let tr = _tr();
let td_ip = _td(entry.ip);
let td_added = _td(moment(entry.timestamp * 1000.0).fromNow());
let td_expires = _td(entry.expires > 0 ? moment(entry.expires * 1000.0).fromNow() : 'Never');
let td_reason = _td(entry.reason);
let td_actions = _td('');
td_actions.appendChild(unblock_link(entry));
td_ip.style.fontFamily = "monospace";
if (entry.ip.length > 16) td_ip.style.fontSize = "0.8rem";
tr.appendChild(td_ip);
tr.appendChild(td_added);
tr.appendChild(td_expires);
tr.appendChild(td_reason);
tr.appendChild(td_actions);
block_table.appendChild(tr);
results_shown++;
if (results_shown > 25 && results.block.length > 25) {
break
}
}
if (results_shown === 0) {
let tr = _tr();
tr.innerText = "No results found...";
block_table.appendChild(tr);
}
// iptables results
let ih2 = _h2(`Local iptables results (${results.iptables.length})`);
main.appendChild(ih2);
let iptables_table = _table();
iptables_table.style.tableLayout = 'fixed';
main.appendChild(iptables_table);
let itheader = _tr();
itheader.appendChild(_th('Source IP', 300));
itheader.appendChild(_th('Host', 200));
itheader.appendChild(_th('Chain', 100));
itheader.appendChild(_th('Reason', 450));
itheader.appendChild(_th('Actions', 100));
iptables_table.appendChild(itheader);
results_shown = 0;
for (const entry of results.iptables) {
let tr = _tr();
let td_ip = _td(entry.source);
let td_host = _td(entry.hostname);
let td_chain = _td(entry.chain);
let td_reason = _td(entry.extensions.replace(/\/\*\s*(.+)\s*\*\//, (b,a) => a));
let td_actions = _td();
td_actions.appendChild(unblock_iptables_link(entry));
td_ip.style.fontFamily = "monospace";
if (entry.source.length > 16) td_ip.style.fontSize = "0.8rem";
tr.appendChild(td_ip);
tr.appendChild(td_host);
tr.appendChild(td_chain);
tr.appendChild(td_reason);
tr.appendChild(td_actions);
iptables_table.appendChild(tr);
results_shown++;
if (results_shown > 25 && results.block.length > 25) {
break
}
}
if (results_shown === 0) {
let tr = _tr();
tr.innerText = "No results found...";
iptables_table.appendChild(tr);
}
} else {
//let x = await sleep(10);
let p = _p("Use the search bar in the top left corner for now...");
main.appendChild(p);
}
}
function unblock_iptables_link(entry) {
let link = _a('Soft Unblock');
let _entry = entry;
_entry.ip = _entry.source;
link.setAttribute("href", 'javascript:void(0);');
link.addEventListener('click', () => unblock_ip(_entry, false));
return link;
}
function rule_tr(rule) {
let tr = _tr();
// Description
let t_desc = _td();
let x_desc = document.createElement('input');
x_desc.setAttribute('type', 'text');
x_desc.setAttribute('id', `desc_${rule.id}`);
x_desc.style.width = "95%";
if (rule.description) {
x_desc.value = rule.description;
} else {
x_desc.placeholder = "Create a new rule here...";
}
t_desc.appendChild(x_desc);
tr.appendChild(t_desc);
// AggType
let t_agg = _td();
let x_agg = document.createElement('select');
x_agg.setAttribute('id', `agg_${rule.id}`);
for (let opt of ['requests', 'bytes']) {
let x_opt = document.createElement('option');
x_opt.value = opt;
x_opt.textContent = opt;
if (opt == rule.aggtype) x_opt.setAttribute('selected', 'selected');
x_agg.appendChild(x_opt);
}
t_agg.appendChild(x_agg);
tr.appendChild(t_agg);
// Limit
let t_limit = _td();
let x_limit = document.createElement('input');
x_limit.setAttribute('type', 'number');
x_limit.value = rule.limit;
x_limit.setAttribute('id', `limit_${rule.id}`);
x_limit.style.width = "95%";
t_limit.appendChild(x_limit);
tr.appendChild(t_limit);
// Timespan
let t_time = _td();
let x_time = document.createElement('input');
x_time.setAttribute('type', 'text');
x_time.setAttribute('id', `time_${rule.id}`);
x_time.style.width = "95%";
x_time.value = rule.duration;
t_time.appendChild(x_time);
tr.appendChild(t_time);
// Filters
let t_filters = _td();
let x_filters = document.createElement('textarea');
x_filters.setAttribute('id', `filters_${rule.id}`);
x_filters.style.width = "95%";
x_filters.textContent = rule.filters;
t_filters.appendChild(x_filters)
tr.appendChild(t_filters);
// Actions
let t_actions = _td();
t_actions.style.textAlign = 'center';
tr.appendChild(t_actions);
if (rule.description) {
let x_save = document.createElement('button');
x_save.innerText = 'Save';
x_save.addEventListener('click', () => patch_rule(rule));
t_actions.appendChild(x_save);
let x_delete = document.createElement('button');
x_delete.style.marginLeft = '32px';
x_delete.innerText = 'Delete';
x_delete.addEventListener('click', () => delete_rule(rule));
t_actions.appendChild(x_delete);
} else {
let x_save = document.createElement('button');
x_save.innerText = 'Create Rule';
x_save.addEventListener('click', () => create_rule(rule));
t_actions.appendChild(x_save);
}
return tr
}
function fetch_rule_data(rule) {
let desc = document.getElementById(`desc_${rule.id}`).value;
let agg = document.getElementById(`agg_${rule.id}`).value;
let limit = parseInt(document.getElementById(`limit_${rule.id}`).value);
let duration = document.getElementById(`time_${rule.id}`).value;
let filters = document.getElementById(`filters_${rule.id}`).value.trim();
return {
description: desc,
aggtype: agg,
limit: limit,
duration: duration,
filter: filters
}
}
async function delete_rule(rule) {
if (confirm("Are you sure you wish to delete this rule?")) {
let result = await DELETE('rules', {rule: rule.id});
alert(result.message);
if (result.success === true) location.reload();
}
}
async function patch_rule(rule) {
let new_rule = fetch_rule_data(rule);
new_rule.rule = rule.id;
let result = await PATCH('rules', new_rule);
alert(result.message);
if (result.success === true) location.reload();
}
async function create_rule(rule) {
let new_rule = fetch_rule_data(rule);
let result = await PUT('rules', new_rule);
alert(result.message);
if (result.success === true) location.reload();
}
async function prime_rules(target, state) {
let main = document.getElementById('main');
main.innerHTML = '';
let title = _h1("Block rules");
main.appendChild(title);
let t = _table();
t.style.tableLayout = 'fixed';
main.appendChild(t);
let btheader = _tr();
btheader.appendChild(_th('Description', 500));
btheader.appendChild(_th('Aggregation Type', 160));
btheader.appendChild(_th('Limit', 140));
btheader.appendChild(_th('Timespan', 80));
btheader.appendChild(_th('Filters', 360));
btheader.appendChild(_th('Actions', 150));
t.appendChild(btheader);
let rules = await GET('rules');
for (let rule of rules) {
let tr = rule_tr(rule);
t.appendChild(tr);
}
let tr = _tr();
let td = _td();
td.appendChild(_hr());
td.colSpan = 6;
tr.appendChild(td);
t.appendChild(tr);
let new_rule = rule_tr({id: 9999, duration: "24h", limit: 100});
t.appendChild(new_rule);
let p = _p("Filters support regular Lucene match (foo = bar), exact terms match (foo == bar), regexp (foo ~= ba[rz]). All matches can be negated with !, such as !=, !==, !~= etc")
main.appendChild(p);
}
async function save_allow() {
let ip = document.getElementById('add_source').value;
let expiry = parseInt(document.getElementById('add_expiry').value);
let reason = document.getElementById('add_reason').value;
let host = document.getElementById('add_host').value;
let true_expiry = parseInt(new Date().getTime() / 1000) + expiry;
let force = document.getElementById('add_force').checked ? true : false;
if (expiry == -1) true_expiry = -1;
let result = await PUT('allow', {
ip: ip,
host: host,
reason: reason,
expires: true_expiry,
force: force
});
alert(result.message);
if (result.success === true) location.reload();
}
async function prime_allow() {
let all = await GET("all");
let main = document.getElementById('main');
main.innerHTML = "";
let h2 = _h2("Add an allow rule:");
main.appendChild(h2);
// Add an entry
let add_table = _table();
add_table.style.tableLayout = 'fixed';
main.appendChild(add_table);
let atheader = _tr();
atheader.appendChild(_th('Source IP', 300));
atheader.appendChild(_th('Expiry', 120));
atheader.appendChild(_th('Reason', 500));
atheader.appendChild(_th('Host', 100));
atheader.appendChild(_th('Force', 60));
atheader.appendChild(_th(' ', 100));
add_table.appendChild(atheader);
let add_tr = _tr();
// source ip
let add_source = _td();
let add_source_input = document.createElement('input');
add_source_input.placeholder = "CIDR, e.g. 127.0.0.1/32 or 2001:dead:beef::1"
add_source_input.style.width = "95%";
add_source_input.id = "add_source"
add_source.appendChild(add_source_input);
add_tr.appendChild(add_source);
// expiry
let add_expiry = _td();
let add_expiry_input = document.createElement('select');
add_expiry_input.id = "add_expiry";
let options = {
"10 minutes": 600,
"1 hour": 3600,
"2 hours": 7200,
"12 hours": 43200,
"24 hours": 86400,
"7 days": 604800,
"never": -1
}
for (let key in options) {
let x_opt = document.createElement('option');
x_opt.value = options[key];
x_opt.text = key;
add_expiry_input.appendChild(x_opt);
}
add_expiry.appendChild(add_expiry_input);
add_tr.appendChild(add_expiry);
// Reason
let add_reason = _td();
let add_reason_input = document.createElement('input');
add_reason_input.placeholder = "Enter a reason for allowing this IP/block."
add_reason_input.style.width = "95%";
add_reason_input.id = "add_reason";
add_reason.appendChild(add_reason_input);
add_tr.appendChild(add_reason);
// Host
let add_host = _td();
let add_host_input = document.createElement('input');
add_host_input.placeholder = "* or foo.apache.org";
add_host_input.value = "*";
add_host_input.style.width = "95%";
add_host_input.id = "add_host";
add_host.appendChild(add_host_input);
add_tr.appendChild(add_host);
// Force
let add_force = _td();
let add_force_input = document.createElement('input');
add_force_input.setAttribute('type', 'checkbox');
add_force_input.setAttribute('value', 'true');
add_force_input.id = 'add_force';
add_force.appendChild(add_force_input);
add_tr.appendChild(add_force);
// Save button
let add_save = _td();
let add_save_button = document.createElement('button');
add_save_button.innerText = "Add allow rule";
add_save_button.addEventListener('click', () => save_allow());
add_save.appendChild(add_save_button);
add_tr.appendChild(add_save);
add_table.appendChild(add_tr);
main.appendChild(_hr());
let allow_count = all.allow.length.pretty();
let h1 = _h1(`Allowed IPs (${allow_count} entries in total)`);
main.appendChild(h1);
all.allow.sort((a,b) => b.timestamp - a.timestamp); // sort desc by timestamp
// Current entries in allow list
let activity_table = _table();
activity_table.style.tableLayout = 'fixed';
main.appendChild(activity_table);
let theader = _tr();
theader.appendChild(_th('Source IP', 300));
theader.appendChild(_th('Added', 120));
theader.appendChild(_th('Expires', 120));
theader.appendChild(_th('Reason', 500));
theader.appendChild(_th('Host', 100));
theader.appendChild(_th('Actions', 100));
activity_table.appendChild(theader);
let results_shown = 0;
for (const entry of all.allow) {
let tr = _tr();
let td_ip = _td(entry.ip);
td_ip.style.fontFamily = "monospace";
if (entry.ip.length > 16) td_ip.style.fontSize = "0.8rem";
let td_added = _td(moment(entry.timestamp*1000.0).fromNow());
let td_expires = _td(entry.expires > 0 ? moment(entry.expires*1000.0).fromNow() : 'Never');
let td_reason = _td(entry.reason);
let td_host = _td(entry.host);
let td_action = _td();
td_action.appendChild(unblock_link(entry, true));
tr.appendChild(td_ip);
tr.appendChild(td_added);
tr.appendChild(td_expires);
tr.appendChild(td_reason);
tr.appendChild(td_host);
tr.appendChild(td_action);
activity_table.appendChild(tr);
results_shown++;
}
if (results_shown === 0) {
let tr = _tr();
tr.innerText = "No entries found...";
activity_table.appendChild(_tr);
}
}
async function save_block() {
let ip = document.getElementById('add_source').value;
let expiry = parseInt(document.getElementById('add_expiry').value);
let reason = document.getElementById('add_reason').value;
let host = document.getElementById('add_host').value;
let true_expiry = parseInt(new Date().getTime() / 1000) + expiry;
if (expiry == -1) true_expiry = -1;
let result = await PUT('block', {
ip: ip,
host: host,
reason: reason,
expires: true_expiry
});
alert(result.message);
if (result.success === true) location.reload();
}
async function prime_block() {
let all = await GET("all");
let main = document.getElementById('main');
main.innerHTML = "";
let h2 = _h1("Add a block rule:");
main.appendChild(h2);
// Add an entry
let add_table = _table();
add_table.style.tableLayout = 'fixed';
main.appendChild(add_table);
let atheader = _tr();
atheader.appendChild(_th('Source IP', 300));
atheader.appendChild(_th('Expiry', 120));
atheader.appendChild(_th('Reason', 500));
atheader.appendChild(_th('Host', 100));
atheader.appendChild(_th(' ', 100));
add_table.appendChild(atheader);
let add_tr = _tr();
// source ip
let add_source = _td();
let add_source_input = document.createElement('input');
add_source_input.placeholder = "CIDR, e.g. 127.0.0.1/32 or 2001:dead:beef::1"
add_source_input.style.width = "95%";
add_source_input.id = "add_source"
add_source.appendChild(add_source_input);
add_tr.appendChild(add_source);
// expiry
let add_expiry = _td();
let add_expiry_input = document.createElement('select');
add_expiry_input.id = "add_expiry";
let options = {
"1 hour": 3600,
"2 hours": 7200,
"12 hours": 43200,
"24 hours": 86400,
"7 days": 604800,
"never": -1
}
for (let key in options) {
let x_opt = document.createElement('option');
x_opt.value = options[key];
x_opt.text = key;
add_expiry_input.appendChild(x_opt);
}
add_expiry.appendChild(add_expiry_input);
add_tr.appendChild(add_expiry);
// Reason
let add_reason = _td();
let add_reason_input = document.createElement('input');
add_reason_input.placeholder = "Enter a reason for allowing this IP/block."
add_reason_input.style.width = "95%";
add_reason_input.id = "add_reason";
add_reason.appendChild(add_reason_input);
add_tr.appendChild(add_reason);
// Host
let add_host = _td();
let add_host_input = document.createElement('input');
add_host_input.placeholder = "* or foo.apache.org";
add_host_input.value = "*";
add_host_input.style.width = "95%";
add_host_input.id = "add_host";
add_host.appendChild(add_host_input);
add_tr.appendChild(add_host);
// Save button
let add_save = _td();
let add_save_button = document.createElement('button');
add_save_button.innerText = "Add block rule";
add_save_button.addEventListener('click', () => save_block());
add_save.appendChild(add_save_button);
add_tr.appendChild(add_save);
add_table.appendChild(add_tr);
main.appendChild(_hr());
}
let actions = {
frontpage: prime_frontpage,
allow: prime_allow,
add: prime_block,
search: prime_search,
rules: prime_rules
};
async function prime(args) {
console.log(args);
let segs = location.search.substr(1).match(/^([a-z]*):?(.*)$/);
let action = segs[1];
let params = segs[2];
action_call = actions[action] ? actions[action] : actions['frontpage'];
await action_call(params, args ? args.state : null);
}
window.onpopstate = prime;