mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
core: update detailed status
This commit is contained in:
1 parent
51ce9f80d5
commit
b9e08c7e93
15 files changed
+142
-99
No files matched your search
+5
-3
@@ -14,7 +14,9 @@ const {
|
||||
CI, GITHUB_EVENT_NAME, GITHUB_REF,
|
||||
} = process.env;
|
||||
|
||||
if (CI && (GITHUB_REF !== 'refs/heads/master' || GITHUB_EVENT_NAME !== 'push')) {
|
||||
const tag = GITHUB_REF === 'refs/heads/master' ? 'latest' : GITHUB_REF === 'refs/heads/develop' ? 'dev' : null;
|
||||
|
||||
if (CI && (!tag || GITHUB_EVENT_NAME !== 'push')) {
|
||||
console.log('publish skipped.');
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -35,7 +37,7 @@ if (CI && (GITHUB_REF !== 'refs/heads/master' || GITHUB_EVENT_NAME !== 'push'))
|
||||
try {
|
||||
meta = require(`../${name}/package.json`);
|
||||
if (!meta.private) {
|
||||
const version = await latest(meta.name);
|
||||
const version = await latest(meta.name, { version: tag });
|
||||
if (gt(meta.version, version)) bumpMap[name] = meta.version;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -48,7 +50,7 @@ if (CI && (GITHUB_REF !== 'refs/heads/master' || GITHUB_EVENT_NAME !== 'push'))
|
||||
if (Object.keys(bumpMap).length) {
|
||||
for (const name in bumpMap) {
|
||||
console.log(`publishing ${name}@${bumpMap[name]} ...`);
|
||||
await spawnAsync(`yarn publish ${name} --new-version ${bumpMap[name]} --access public`);
|
||||
await spawnAsync(`yarn publish ${name} --new-version ${bumpMap[name]}${tag === 'dev' ? '-dev' : ''} --access public --tag ${tag}`);
|
||||
}
|
||||
}
|
||||
console.log('Release created successfully.');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydrooj",
|
||||
"version": "2.21.0",
|
||||
"version": "2.21.1",
|
||||
"bin": "bin/hydrooj.js",
|
||||
"main": "dist/loader.js",
|
||||
"typings": "dist/loader.d.ts",
|
||||
|
||||
@@ -26,7 +26,7 @@ export const builtinModel = [
|
||||
export const builtinHandler = [
|
||||
'home', 'problem', 'record', 'judge', 'user',
|
||||
'contest', 'training', 'discussion', 'manage', 'import.syzoj',
|
||||
'misc', 'homework', 'domain', 'remote',
|
||||
'misc', 'homework', 'domain', 'remote', 'status',
|
||||
];
|
||||
|
||||
export const builtinScript = [
|
||||
|
||||
@@ -2,45 +2,10 @@
|
||||
import { BadRequestError } from '../error';
|
||||
import { PRIV } from '../model/builtin';
|
||||
import user from '../model/user';
|
||||
import db from '../service/db';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
|
||||
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].isOnline = online;
|
||||
stats[i].status = desc;
|
||||
}
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['status', null],
|
||||
];
|
||||
this.response.body = { stats, path };
|
||||
this.response.template = 'status.html';
|
||||
}
|
||||
}
|
||||
|
||||
class StatusUpdateHandler extends Handler {
|
||||
async post(args) {
|
||||
this.checkPriv(PRIV.PRIV_JUDGE);
|
||||
args.type = 'judge';
|
||||
return coll.updateOne(
|
||||
{ mid: args.mid, type: 'judge' },
|
||||
{ $set: args },
|
||||
{ upsert: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckInHandler extends Handler {
|
||||
async prepare() {
|
||||
const { checkincnt, lastcheckin } = this.user;
|
||||
@@ -82,8 +47,6 @@ class SwitchLanguageHandler extends Handler {
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
Route('status', '/status', StatusHandler);
|
||||
Route('status_update', '/status/update', StatusUpdateHandler);
|
||||
Route('check_in', '/checkin', CheckInHandler, PRIV.PRIV_USER_PROFILE);
|
||||
Route('switch_language', '/language/:lang', SwitchLanguageHandler);
|
||||
}
|
||||
|
||||
@@ -12,14 +12,7 @@ class NotFoundHandler extends Handler {
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
Route('notfound', '/:a', NotFoundHandler);
|
||||
Route('notfound', '/:a/:b', NotFoundHandler);
|
||||
Route('notfound', '/:a/:b/:c', NotFoundHandler);
|
||||
Route('notfound', '/:a/:b/:c/:d', NotFoundHandler);
|
||||
Route('notfound', '/:a/:b/:c/:d/:e', NotFoundHandler);
|
||||
Route('notfound', '/:a/:b/:c/:d/:e/:f', NotFoundHandler);
|
||||
Route('notfound', '/:a/:b/:c/:d/:e/:f/:g', NotFoundHandler);
|
||||
Route('notfound', '/:a/:b/:c/:d/:e/:f/:g/:h', NotFoundHandler);
|
||||
Route('notfound', '(/.*)+', NotFoundHandler);
|
||||
}
|
||||
|
||||
global.Hydro.handler.notfound = apply;
|
||||
@@ -0,0 +1,81 @@
|
||||
import { PRIV } from '../model/builtin';
|
||||
import RecordModel from '../model/record';
|
||||
import DomainModel from '../model/domain';
|
||||
import * as DocumentModel from '../model/document';
|
||||
import db from '../service/db';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import UserModel from '../model/user';
|
||||
|
||||
const coll = db.collection('status');
|
||||
|
||||
async function getStatus() {
|
||||
const stats = await coll.find().sort({ type: 1, updateAt: -1 }).toArray();
|
||||
for (const stat of stats) {
|
||||
let desc = '';
|
||||
const online = new Date(stat.updateAt).getTime() > new Date().getTime() - 300000;
|
||||
if (!online) desc = 'Offline';
|
||||
desc = desc || 'Online';
|
||||
stat.isOnline = online;
|
||||
stat.status = desc;
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
class StatusHandler extends Handler {
|
||||
async get() {
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['status', null],
|
||||
];
|
||||
const stats = await getStatus();
|
||||
for (const stat of stats) {
|
||||
if (!stat.battery.hasBattery) stat.battery = 'No battery';
|
||||
else stat.battery = `${stat.battery.type} ${stat.battery.model} ${stat.battery.percent}%${stat.battery.isCharging ? ' Charging' : ''}`;
|
||||
}
|
||||
this.response.body = { stats, path };
|
||||
this.response.template = 'status.html';
|
||||
}
|
||||
}
|
||||
|
||||
class AdminStatusHandler extends Handler {
|
||||
async get() {
|
||||
const record = await RecordModel.stat();
|
||||
const status = await getStatus();
|
||||
const [domainCount, userCount, problemCount, discussionCount, recordCount] = await Promise.all([
|
||||
DomainModel.getMulti().count(),
|
||||
UserModel.getMulti().count(),
|
||||
DocumentModel.coll.find({ docType: DocumentModel.TYPE_PROBLEM }).count(),
|
||||
DocumentModel.coll.find({ docType: DocumentModel.TYPE_DISCUSSION }).count(),
|
||||
RecordModel.coll.find().count(),
|
||||
]);
|
||||
this.response.body = {
|
||||
record,
|
||||
status,
|
||||
domainCount,
|
||||
userCount,
|
||||
problemCount,
|
||||
discussionCount,
|
||||
recordCount,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class StatusUpdateHandler extends Handler {
|
||||
async post(args) {
|
||||
this.checkPriv(PRIV.PRIV_JUDGE);
|
||||
args.type = 'judge';
|
||||
return coll.updateOne(
|
||||
{ mid: args.mid, type: 'judge' },
|
||||
{ $set: args },
|
||||
{ upsert: true },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
Route('status', '/status', StatusHandler);
|
||||
Route('status_admin', '/.status', AdminStatusHandler);
|
||||
Route('status_update', '/status/update', StatusUpdateHandler);
|
||||
}
|
||||
|
||||
global.Hydro.handler.status = apply;
|
||||
@@ -565,6 +565,7 @@ declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
Hydro: {
|
||||
version: Record<string, string>,
|
||||
model: Model,
|
||||
handler: Dict<Function>,
|
||||
script: Dict<Script>,
|
||||
|
||||
@@ -1,60 +1,54 @@
|
||||
import systeminformation from 'systeminformation';
|
||||
import { size } from './misc';
|
||||
import systeminformation, { Systeminformation } from 'systeminformation';
|
||||
|
||||
const cache = {
|
||||
mid: '', cpu: '', osinfo: '', flags: '',
|
||||
};
|
||||
const cache: any = {};
|
||||
|
||||
export async function get() {
|
||||
const [Cpu, Memory, OsInfo, CurrentLoad, CpuFlags, CpuTemp, Battery] = await Promise.all([
|
||||
export interface StatusUpdate {
|
||||
memory: Systeminformation.MemData;
|
||||
load: Systeminformation.CurrentLoadData;
|
||||
battery: Systeminformation.BatteryData;
|
||||
CpuTemp: Systeminformation.CpuTemperatureData;
|
||||
}
|
||||
|
||||
export interface StatusFull extends StatusUpdate {
|
||||
mid: string;
|
||||
cpu: Systeminformation.CpuData;
|
||||
osinfo: Systeminformation.OsData;
|
||||
}
|
||||
|
||||
export async function get(): Promise<StatusFull> {
|
||||
const [uuid, cpu, memory, osinfo, load, CpuTemp, battery] = await Promise.all([
|
||||
systeminformation.uuid(),
|
||||
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 mid = OsInfo.serial;
|
||||
const mid = uuid.hardware;
|
||||
cache.cpu = cpu;
|
||||
cache.osinfo = osinfo;
|
||||
cache.flags = flags;
|
||||
cache.mid = mid;
|
||||
return {
|
||||
mid, cpu, memory, osinfo, load, flags, CpuTemp, battery,
|
||||
mid, cpu, memory, osinfo, load, CpuTemp, battery,
|
||||
};
|
||||
}
|
||||
|
||||
export async function update(): Promise<[string, any, any]> {
|
||||
const [Memory, CurrentLoad, CpuTemp, Battery] = await Promise.all([
|
||||
export async function update(): Promise<[string, StatusUpdate, StatusFull]> {
|
||||
const [memory, load, CpuTemp, battery] = await Promise.all([
|
||||
systeminformation.mem(),
|
||||
systeminformation.currentLoad(),
|
||||
systeminformation.cpuTemperature(),
|
||||
systeminformation.battery(),
|
||||
]);
|
||||
const {
|
||||
mid, 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' : ''}`;
|
||||
const { mid, cpu, osinfo } = cache;
|
||||
return [
|
||||
mid,
|
||||
{
|
||||
memory, load, battery, CpuTemp,
|
||||
},
|
||||
{
|
||||
mid, cpu, memory, osinfo, load, flags, battery, CpuTemp,
|
||||
mid, cpu, memory, osinfo, load, battery, CpuTemp,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ if (versionNum < 14) throw new Error('NodeJS >=v14 required');
|
||||
|
||||
if (!global.Hydro) {
|
||||
global.Hydro = {
|
||||
version: {
|
||||
node: process.version,
|
||||
hydrooj: require('hydrooj/package.json').version,
|
||||
},
|
||||
stat: { reqCount: 0 },
|
||||
handler: {},
|
||||
// @ts-ignore
|
||||
|
||||
@@ -6,6 +6,7 @@ import db from './db';
|
||||
import * as bus from './bus';
|
||||
import * as sysinfo from '../lib/sysinfo';
|
||||
import { Logger } from '../logger';
|
||||
import type { StatusUpdate } from '../lib/sysinfo';
|
||||
|
||||
const coll = db.collection('status');
|
||||
const logger = new Logger('monitor');
|
||||
@@ -17,15 +18,13 @@ function crypt(str: string) {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
export async function feedback() {
|
||||
export async function feedback(): Promise<[string, StatusUpdate]> {
|
||||
const {
|
||||
system, domain, document, user, record,
|
||||
} = global.Hydro.model;
|
||||
const version = require('hydrooj/package.json').version;
|
||||
const [mid, , inf] = await sysinfo.update();
|
||||
const [installid, name, url] = system.getMany([
|
||||
'installid', 'server.name', 'server.url',
|
||||
]);
|
||||
const [mid, $update, inf] = await sysinfo.update();
|
||||
const [installid, name, url] = system.getMany(['installid', 'server.name', 'server.url']);
|
||||
const [domainCount, userCount, problemCount, discussionCount, recordCount] = await Promise.all([
|
||||
domain.getMulti().count(),
|
||||
user.getMulti().count(),
|
||||
@@ -47,20 +46,22 @@ export async function feedback() {
|
||||
memory: inf.memory,
|
||||
osinfo: inf.osinfo,
|
||||
cpu: inf.cpu,
|
||||
flags: inf.flags,
|
||||
}));
|
||||
superagent.post('https://feedback.undefined.moe/')
|
||||
.send({ payload })
|
||||
.catch(() => {
|
||||
logger.warn('Cannot connect to hydro center.');
|
||||
});
|
||||
return [mid, $update];
|
||||
}
|
||||
|
||||
export async function update() {
|
||||
await feedback();
|
||||
const [mid, $set] = await sysinfo.update();
|
||||
$set.updateAt = new Date();
|
||||
$set.reqCount = global.Hydro.stat.reqCount;
|
||||
const [mid, $update] = await feedback();
|
||||
const $set = {
|
||||
...$update,
|
||||
updateAt: new Date(),
|
||||
reqCount: global.Hydro.stat.reqCount,
|
||||
};
|
||||
await bus.serial('monitor/update', 'server', $set);
|
||||
await coll.updateOne(
|
||||
{ mid, type: 'server' },
|
||||
|
||||
@@ -163,6 +163,7 @@ env.addGlobal('static_url', (assetName) => {
|
||||
});
|
||||
// eslint-disable-next-line no-eval
|
||||
env.addGlobal('eval', eval);
|
||||
env.addGlobal('global', global);
|
||||
env.addGlobal('typeof', (o) => typeof o);
|
||||
env.addGlobal('datetimeSpan', misc.datetimeSpan);
|
||||
env.addGlobal('paginate', misc.paginate);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hydrooj/ui-default",
|
||||
"version": "4.5.0",
|
||||
"version": "4.5.1",
|
||||
"author": "undefined <i@undefined.moe>",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "hydro.js",
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
.page--status
|
||||
.col--id
|
||||
width: 70px
|
||||
width: 100px
|
||||
|
||||
.col--os
|
||||
width: 200px
|
||||
|
||||
.col--status
|
||||
width: 100px
|
||||
width: 130px
|
||||
|
||||
.col--memory
|
||||
width: 150px
|
||||
|
||||
.col--request
|
||||
width: 100px
|
||||
width: 130px
|
||||
|
||||
+mobile()
|
||||
.col--os
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
{%- for html in (model.system.get('ui-default.footer_extra_html')|default('')).split('\n') -%}
|
||||
<li class="footer__extra-link-item">{{ html|safe }}</li>
|
||||
{%- endfor -%}
|
||||
<li class="footer__extra-link-item">Powered by <a href="https://hydro.js.org">Hydro</a></li>
|
||||
<li class="footer__extra-link-item">Powered by <a href="https://hydro.js.org">Hydro v{{ global.Hydro.version.hydrooj }}</a></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
<tbody>
|
||||
{%- for doc in stats -%}
|
||||
<tr>
|
||||
<td class="col--id">{{ doc._id.toHexString()|truncate(6,True,'') }}</td>
|
||||
<td class="col--id">{{ doc._id.toHexString()|truncate(8,True,'') }}</td>
|
||||
<td class="col--status">{% if doc.isOnline %}{{ doc.status }}{% else %}{{ datetimeSpan(doc.updateAt)|safe }}{% endif %}</td>
|
||||
<td class="col--os">{{ doc.osinfo }}</td>
|
||||
<td class="col--cpu" data-tooltip="{{ doc.flags }}">{{ doc.cpu }}</td>
|
||||
<td class="col--memory"{% if doc.stack %} data-tooltip="Stack size: {{ doc.stack }}MB"{% endif %}>{{ doc.memory }}</td>
|
||||
<td class="col--os" data-tooltip="{{ doc.osinfo.kernel }}">{{ doc.osinfo.distro }} {{ doc.osinfo.release }} {{ doc.osinfo.codename }} {{ doc.osinfo.arch }}</td>
|
||||
<td class="col--cpu" data-tooltip="{{ doc.cpu.flags }}">{{ doc.cpu.manufacturer }} {{ doc.cpu.brand }}@{{ doc.cpu.speed }}GHz</td>
|
||||
<td class="col--memory"{% if doc.stack %} data-tooltip="Stack size: {{ doc.stack }}MB"{% endif %}>{{ size(doc.memory.used) }}/{{ size(doc.memory.total) }}</td>
|
||||
<td class="col--request">{{ doc.reqCount }}</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
|
||||
Reference in new issue
Block a user