You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
doushio/admin/panel.js

204 lines
4.8 KiB

var _ = require('../lib/underscore'),
authcommon = require('./common'),
caps = require('../server/caps'),
common = require('../common'),
okyaku = require('../server/okyaku'),
STATE = require('../server/state');
var ADDRS = STATE.dbCache.addresses;
authcommon.modCache.addresses = ADDRS;
var ip_key = authcommon.ip_key;
function on_client_ip(ip, clients) {
var addr = {key: ip_key(ip), ip: ip, count: clients.length};
// This will leak 0-count clients.
// I want them to expire after a delay, really. Should reduce churn.
this.send([0, common.COLLECTION_ADD, 'addrs', addr]);
}
function on_refresh(info) {
this.send([0, common.MODEL_SET, 'adminState', info]);
}
function connect() {
return global.redis;
}
function address_view(addr) {
addr = _.extend({}, addr);
addr.shallow = false;
var clients = STATE.clientsByIP[addr.ip];
if (clients) {
addr.count = clients.length;
addr.country = clients[0] && clients[0].country || '';
}
return addr;
}
okyaku.dispatcher[authcommon.FETCH_ADDRESS] = function (msg, client) {
if (!caps.can_moderate(client.ident))
return false;
var ip = msg[0];
if (!authcommon.is_valid_ip(ip))
return false;
var key = ip_key(ip);
var addr = ADDRS[key];
if (addr) {
client.send([0, common.COLLECTION_ADD, 'addrs', address_view(addr)]);
return true;
}
// Cache miss
ADDRS[key] = addr = {ip, key, shallow: true};
var r = connect();
r.hgetall('ip:'+key, function (err, info) {
if (err) {
if (ADDRS[key] === addr)
delete ADDRS[key];
return client.kotowaru(err);
}
if (ADDRS[key] !== addr)
return;
_.extend(addr, info);
client.send([0, common.COLLECTION_ADD, 'addrs', address_view(addr)]);
});
return true;
}
okyaku.dispatcher[authcommon.SET_ADDRESS_NAME] = function (msg, client) {
if (!caps.can_moderate(client.ident))
return false;
var ip = msg[0], name = msg[1];
if (!authcommon.is_valid_ip(ip) || typeof name != 'string')
return false;
var key = ip_key(ip);
name = name.trim().slice(0, 30);
var m = connect().multi();
if (!name) {
m.hdel('ip:' + key, 'name');
m.srem('namedIPs', key);
}
else {
m.hset('ip:' + key, 'name', name);
m.sadd('namedIPs', key);
}
m.exec(function (err) {
if (err)
return client.kotowaru(err);
// should observe a publication for this cache update
var addr = ADDRS[key];
if (!addr)
addr = ADDRS[key] = {key: key, ip: ip};
addr.name = name;
var amend = {name: name};
client.send([0, common.MODEL_SET, ['addrs', key], amend]);
});
return true;
};
var panelListeners = 0, panelInterval = 0;
function listen_panel(client) {
STATE.emitter.on('change:clientsByIP', client.on_client_ip);
STATE.emitter.on('refresh', client.on_refresh);
panelListeners++;
if (panelListeners == 1) {
panelInterval = setInterval(refresh_panel_state, 10*1000);
}
}
function unlisten_panel(client) {
STATE.emitter.removeListener('change:clientsByIP',client.on_client_ip);
STATE.emitter.removeListener('refresh', client.on_refresh);
panelListeners--;
if (panelListeners == 0) {
clearInterval(panelInterval);
panelInterval = 0;
}
}
function snapshot_panel() {
var addrCount = 0;
for (var key in ADDRS)
addrCount++;
var ranges = STATE.dbCache.ranges;
var banCount = ranges.bans ? ranges.bans.length : 0;
return {
memoryUsage: process.memoryUsage(),
uptime: process.uptime(),
addrs: addrCount,
bans: banCount,
};
}
function refresh_panel_state() {
STATE.emitter.emit('refresh', snapshot_panel());
}
function subscribe() {
if (this.on_client_ip)
return false;
this.on_client_ip = on_client_ip.bind(this);
this.on_refresh = on_refresh.bind(this);
this.unsubscribe_admin_state = unsubscribe.bind(this);
this.once('close', this.unsubscribe_admin_state);
listen_panel(this);
var state = snapshot_panel();
state.visible = true;
this.send([0, common.MODEL_SET, 'adminState', state]);
var ips = [];
for (var ip in STATE.clientsByIP) {
var key = ip_key(ip);
var a = ADDRS[key];
ips.push(a ? address_view(a) : {
key: key, ip: ip, shallow: true,
count: STATE.clientsByIP[ip].length
});
}
this.send([0, common.COLLECTION_RESET, 'addrs', ips]);
return true;
}
function unsubscribe() {
if (!this.on_client_ip)
return false;
unlisten_panel(this);
this.removeListener('close', this.unsubscribe_admin_state);
this.on_client_ip = null;
this.on_refresh = null;
this.unsubscribe_admin_state = null;
this.send([0, common.MODEL_SET, 'adminState', {visible: false}]);
return true;
}
okyaku.dispatcher[common.SUBSCRIBE] = function (msg, client) {
if (!caps.can_administrate(client.ident))
return false;
if (msg[0] != 'adminState')
return false;
return subscribe.call(client);
};
okyaku.dispatcher[common.UNSUBSCRIBE] = function (msg, client) {
if (!caps.can_administrate(client.ident))
return false;
if (msg[0] != 'adminState')
return false;
return unsubscribe.call(client);
};