服务状态(/status)

This commit is contained in:
masnn committed 2020-05-31 20:07:52 +08:00
1 parent b2989cb9ea
commit 216a4a91e4
19 files changed
+322 -136

No files matched your search

+15 -13
View File
@@ -21,6 +21,8 @@ const {
PERM_LOGGEDIN,
} = require('../permission');
const { geoip, useragent } = global.Hydro.lib;
class HomeHandler extends Handler {
async contest(domainId) {
if (this.user.hasPerm(PERM_VIEW_CONTEST)) {
@@ -81,13 +83,13 @@ class HomeSecurityHandler extends Handler {
// TODO(iceboy): pagination? or limit session count for uid?
// TODO(masnn): UA & IP
const sessions = await token.getSessionListByUid(this.user._id);
const parsed = sessions.map((session) => ({
...session,
// updateUa: useragent.parse(session.updateUa || session.createUa || ''),
// updateGeoip: geoip.ip2geo(session.updateIp || session.createIp, this.user.viewLang),
_id: md5(session._id),
isCurrent: session._id === this.session._id,
}));
const parsed = [];
for (const session of sessions) {
session.isCurrent = session._id === this.session._id;
session._id = md5(session._id);
if (useragent) session.updateUa = useragent.parse(session.updateUa || session.createUa || '');
if (geoip) session.updateGeoip = geoip.lookup(session.updateIp || session.createIp);
}
this.response.template = 'home_security.html';
this.response.body = { sessions: parsed };
}
@@ -153,7 +155,7 @@ class HomeSettingsHandler extends Handler {
async post(args) {
// FIXME validation
await user.setById(domainId, this.user._id, args);
await user.setById(args.domainId, this.user._id, args);
this.back();
}
}
@@ -166,9 +168,10 @@ class UserChangemailWithCodeHandler extends Handler {
}
const udoc = await user.getByEmail(tdoc.mail);
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
// TODO(twd2): Ensure mail is unique.
await user.setEmail(this.user._id, tdoc.mail);
await token.delete(code, token.TYPE_CHANGEMAIL);
await Promise.all([
user.setEmail(this.user._id, tdoc.mail),
token.delete(code, token.TYPE_CHANGEMAIL),
]);
this.response.redirect = '/home/security';
}
}
@@ -211,8 +214,7 @@ class HomeMessagesHandler extends Handler {
if (type === 'single') {
mdoc = mdoc.reply[mdoc.reply.length - 1];
}
// TODO(twd2): improve here:
// projection
// TODO(twd2): improve here: projection
mdoc.from_udoc = this.user;
mdoc.to_udoc = udoc;
this.udoc(mdoc, 'from');
+3 -1
View File
@@ -1,6 +1,7 @@
const user = require('../model/user');
const domain = require('../model/domain');
const setting = require('../model/setting');
const system = require('../model/system');
const { Route, Handler } = require('../service/server');
const { PERM_MANAGE } = require('../permission');
const hpm = require('../lib/hpm');
@@ -53,6 +54,7 @@ class ManageEditHandler extends ManageHandler {
class ManageUserHandler extends ManageHandler {
async get({ domainId }) {
// FIXME(masnn) UPDATE CODE
const uids = [];
const rudocs = {};
const [udocs, roles] = await Promise.all([
@@ -73,7 +75,7 @@ class ManageUserHandler extends ManageHandler {
];
this.response.template = 'manage_user.html';
this.response.body = {
roles, rolesSelect, rudocs, udict, system: sys, path,
roles, rolesSelect, rudocs, udict, path,
};
}
+34
View File
@@ -0,0 +1,34 @@
const { PERM_JUDGE } = require('../permission');
const { Route, Handler } = require('../service/server');
const db = require('../service/db');
const coll = db.collection('status');
class StatusHandler extends Handler {
async get() {
const stats = await coll.find().sort({ type: 1, updateAt: -1 }).toArray();
for (const i in stats) {
let desc = '';
const online = new Date(stats[i].updateAt).getTime() > new Date().getTime() - 300000;
if (!online) desc = 'Offline';
desc = desc || 'Online';
stats[i].status = desc;
}
this.response.body = { stats };
this.response.template = 'status.html';
}
}
class StatusUpdateHandler extends Handler {
async post(args) {
this.checkPerm(PERM_JUDGE);
await coll.updateOne({ _id: args._id }, { $set: args }, { upsert: true });
}
}
async function apply() {
Route('/status', StatusHandler);
Route('/status/update', StatusUpdateHandler);
}
global.Hydro.handler.status = module.exports = { StatusHandler, StatusUpdateHandler, apply };
+91
View File
@@ -0,0 +1,91 @@
const domain = require('../model/domain');
const system = require('../model/system');
const setting = require('../model/setting');
const { Route, Handler } = require('../service/server');
const { PERM_MANAGE } = require('../permission');
const hpm = require('../lib/hpm');
const loader = require('../loader');
class SystemHandler extends Handler {
async prepare({ domainId }) {
this.checkPerm(PERM_MANAGE);
this.domain = await domain.get(domainId);
}
}
class SystemDashboardHandler extends SystemHandler {
async get() {
const path = [
['Hydro', '/'],
['manage', '/manage'],
['manage_dashboard', null],
];
this.response.template = 'manage_dashboard.html';
this.response.body = { domain: this.domain, path };
}
}
class SystemModuleHandler extends SystemHandler {
async get() {
const installed = await hpm.getInstalled();
const path = [
['Hydro', '/'],
['manage', '/manage'],
['manage_module', null],
];
this.response.body = { installed, active: loader.active, path };
this.response.template = 'manage_module.html';
}
async postInstall({ url, id }) {
await hpm.install(url, id);
this.back();
}
async postDelete({ id }) {
await hpm.del(id);
this.back();
}
}
class SystemSettingHandler extends SystemHandler {
async get() {
this.response.template = 'manage_settings.html';
const current = {};
const settings = setting.SYSTEM_SETTINGS;
for (const s of settings) {
current[s.key] = await system.get(s.key);
}
this.response.body = {
current, settings,
};
}
async post(args) {
const tasks = [];
for (const key in args) {
if (typeof args[key] === 'object') {
const subtasks = [];
for (const sub in args[key]) {
subtasks.push(system.set(`${key}.${sub}`, args[key][sub]));
}
tasks.push(Promise.all(subtasks));
} else tasks.push(system.set(key, args[key]));
}
await Promise.all(tasks);
this.back();
}
}
async function apply() {
Route('/system', module.exports.SystemDashboardHandler);
Route('/system/module', module.exports.SystemModuleHandler);
Route('/system/setting', module.exports.SystemSettingHandler);
}
global.Hydro.handler.manage = module.exports = {
SystemDashboardHandler,
SystemModuleHandler,
SystemSettingHandler,
apply,
};
+1 -1
View File
@@ -43,7 +43,7 @@ function size(s, base = 1) {
const unit = 1024;
const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
for (const unitName of unitNames) {
if (s < unit) return '{0} {1}'.format(Math.round(s), unitName);
if (s < unit) return '{0} {1}'.format(Math.round(s * 10) / 10, unitName);
s /= unit;
}
return '{0} {1}'.format(Math.round(s * unit), unitNames[unitNames.length - 1]);
+60
View File
@@ -0,0 +1,60 @@
const systeminformation = require('systeminformation');
const { size } = require('./misc');
const cache = {};
async function get() {
const [Cpu, Memory, OsInfo, CurrentLoad, CpuFlags, CpuTemp, Battery] = await Promise.all([
systeminformation.cpu(),
systeminformation.mem(),
systeminformation.osInfo(),
systeminformation.currentLoad(),
systeminformation.cpuFlags(),
systeminformation.cpuTemperature(),
systeminformation.battery(),
]);
const cpu = `${Cpu.manufacturer} ${Cpu.brand}`;
const memory = `${size(Memory.active)}/${size(Memory.total)}`;
const osinfo = `${OsInfo.distro} ${OsInfo.release} ${OsInfo.codename} ${OsInfo.kernel} ${OsInfo.arch}`;
const load = `${CurrentLoad.avgload}`;
const flags = CpuFlags;
let battery;
if (!Battery.hasbattery) battery = 'No battery';
else battery = `${Battery.type} ${Battery.model} ${Battery.percent}%${Battery.ischarging ? ' Charging' : ''}`;
const _id = OsInfo.serial;
cache.cpu = cpu;
cache.osinfo = osinfo;
cache.flags = flags;
cache._id = _id;
return {
_id, cpu, memory, osinfo, load, flags, CpuTemp, battery,
};
}
async function update() {
const [Memory, CurrentLoad, CpuTemp, Battery] = await Promise.all([
systeminformation.mem(),
systeminformation.currentLoad(),
systeminformation.cpuTemperature(),
systeminformation.battery(),
]);
const {
_id, cpu, osinfo, flags,
} = cache;
const memory = `${size(Memory.active)}/${size(Memory.total)}`;
const load = `${CurrentLoad.avgload}`;
let battery;
if (!Battery.hasbattery) battery = 'No battery';
else battery = `${Battery.type} ${Battery.model} ${Battery.percent}%${Battery.ischarging ? ' Charging' : ''}`;
return [
_id,
{
memory, load, battery, CpuTemp,
},
{
_id, cpu, memory, osinfo, load, flags, battery, CpuTemp,
},
];
}
global.Hydro.lib.sysinfo = module.exports = { get, update };
+3 -2
View File
@@ -229,11 +229,12 @@ async function load() {
const builtinLib = [
'axios', 'download', 'i18n', 'mail', 'markdown',
'md5', 'misc', 'paginate', 'hash.hydro', 'rank',
'template', 'validator', 'nav',
'template', 'validator', 'nav', 'sysinfo',
];
for (const i of builtinLib) require(`./lib/${i}`);
await lib();
require('./service/gridfs');
require('./service/monitor');
const server = require('./service/server');
await server.prepare();
await service();
@@ -256,7 +257,7 @@ async function load() {
const builtinHandler = [
'home', 'problem', 'record', 'judge', 'user',
'contest', 'training', 'discussion', 'manage', 'import',
'fs',
'fs', 'status',
];
for (const i of builtinHandler) require(`./handler/${i}`);
await model();
+6 -5
View File
@@ -23,6 +23,7 @@ async function add(
) {
const _id = new ObjectID();
const doc = {
_id,
content,
owner,
domainId,
@@ -53,8 +54,10 @@ async function set(domainId, docType, docId, args) {
}
function deleteOne(domainId, docType, docId) {
// TODO(twd2): delete status?
return coll.deleteOne({ domainId, docType, docId });
return Promise.all([
coll.deleteMany({ domainId, docType, docId }),
coll.deleteOne({ domainId, docType, docId }),
]);
}
function deleteMulti(domainId, docType, args = {}) {
@@ -134,9 +137,7 @@ async function getSub(domainId, docType, docId, key, subId) {
});
if (!doc) return [null, null];
for (const sdoc of doc[key] || []) {
if (sdoc._id === subId) {
return [doc, sdoc];
}
if (sdoc._id === subId) return [doc, sdoc];
}
return [doc, null];
}
+6 -5
View File
@@ -1,3 +1,4 @@
const builtin = require('./builtin');
const document = require('./document');
const system = require('./system');
const { UserNotFoundError, UserAlreadyExistError } = require('../error');
@@ -40,15 +41,15 @@ class USER {
async function getPerm(domainId, udoc) {
if (udoc._id === 1) { // Is Guest
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, 'guest');
return p.content;
return p ? p.content : builtin.BUILTIN_ROLES.guest.perm;
}
if (udoc.priv === 1) {
if (udoc.priv === 1) { // Is superadmin
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, 'admin');
return p.content;
return p ? p.content : builtin.BUILTIN_ROLES.admin.perm;
}
const role = await document.getStatus(domainId, document.TYPE_DOMAIN_ROLE, 0, udoc._id);
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, role.role || 'default');
return p.content;
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, (role || {}).role || 'default');
return p ? p.content : builtin.BUILTIN_ROLES.default.perm;
}
async function getById(domainId, _id) {
+25
View File
@@ -0,0 +1,25 @@
const db = require('./db');
const sysinfo = require('../lib/sysinfo');
const coll = db.collection('status');
async function update() {
const [_id, $set] = await sysinfo.update();
await coll.updateOne(
{ _id },
{ $set: { ...$set, updateAt: new Date() } },
{ upsert: true },
);
}
async function postInit() {
const info = await sysinfo.get();
await coll.updateOne(
{ _id: info._id },
{ $set: { ...info, updateAt: new Date() } },
{ upsert: true },
);
setInterval(update, 3 * 60 * 1000);
}
global.Hydro.service.monitor = module.exports = { postInit };