system check & setting_secret

This commit is contained in:
undefined committed 2020-07-03 13:37:04 +08:00
1 parent c52ddd7512
commit 57f711ed4f
18 files changed
+295 -134

No files matched your search

+1
View File
@@ -28,5 +28,6 @@ module.exports = {
'no-nested-ternary': 'off', 'no-nested-ternary': 'off',
'no-multi-assign': 'off', 'no-multi-assign': 'off',
'no-return-await': 'off', 'no-return-await': 'off',
'no-bitwise': 'off',
}, },
}; };
+69
View File
@@ -0,0 +1,69 @@
// TODO check more
const os = require('os');
const superagent = require('superagent');
const system = require('./model/system');
const db = require('./service/db');
const c = {};
async function checkDb(log, warn, error) {
try {
const coll = db.collection('check');
const d = await coll.findOne({ _id: 'check' });
if (!d) await coll.insertOne({ _id: 'check', value: 'check' });
await coll.createIndex('check');
await coll.deleteOne({ _id: 'check' });
} catch (e) {
error(`Mongo Error: Database read-write failed.\n${e.message}`);
}
}
async function checkPerm(log, warn) {
const { username } = os.userInfo();
if (username === 'root') warn('Hydro should not be run as root.');
// TODO check cwd read-write
}
async function checkMail(log, warn) {
const from = await system.get('smtp.from');
if (!from) warn('SMTP account was not provided, email verification disabled.');
}
async function checkProxy(log, warn) {
const [github, google, proxy] = await system.getMany([
'oauth.githubappid', 'oauth.googleappid', 'proxy',
]);
if (proxy) {
superagent.get('https://www.google.com/').timeout(10000).proxy(proxy)
.catch(() => warn('The proxy configured seem to be invalid.'));
} else if (github || google) {
warn('OAuth enabled. But for well-known reasons, a proxy is required.');
}
}
const checks = [
checkDb,
checkPerm,
checkMail,
checkProxy,
];
async function start(log, warn, error, cb) {
const id = String.random(6);
cb(id);
for (const check of checks) {
if (c[id]) {
delete c[id];
return;
}
// eslint-disable-next-line no-await-in-loop
await check(log, warn, error);
}
}
async function cancel(id) {
c[id] = true;
}
module.exports = { start, cancel };
+6 -2
View File
@@ -132,8 +132,12 @@ class HomeSecurityHandler extends Handler {
); );
} }
} }
const path = [
['Hydro', 'homepage'],
['home_security', null],
];
this.response.template = 'home_security.html'; this.response.template = 'home_security.html';
this.response.body = { sessions, geoipProvider: (geoip || {}).provider }; this.response.body = { sessions, geoipProvider: (geoip || {}).provider, path };
if (useragent) this.response.body.icon = useragent.icon; if (useragent) this.response.body.icon = useragent.icon;
} }
@@ -202,7 +206,7 @@ class HomeSettingsHandler extends Handler {
async post(args) { async post(args) {
const $set = {}; const $set = {};
for (const key in args) { for (const key in args) {
if (setting.SETTINGS_BY_KEY[key]) { if (setting.SETTINGS_BY_KEY[key] && !setting.SETTINGS_BY_KEY[key].disabled) {
$set[key] = args[key]; $set[key] = args[key];
} }
} }
+27 -5
View File
@@ -3,9 +3,12 @@ const system = require('../model/system');
const { STATUS, PRIV } = require('../model/builtin'); const { STATUS, PRIV } = require('../model/builtin');
const record = require('../model/record'); const record = require('../model/record');
const judge = require('./judge'); const judge = require('./judge');
const { Route, Handler } = require('../service/server'); const {
Route, Connection, Handler, ConnectionHandler,
} = require('../service/server');
const hpm = require('../lib/hpm'); const hpm = require('../lib/hpm');
const loader = require('../loader'); const loader = require('../loader');
const check = require('../check');
class SystemHandler extends Handler { class SystemHandler extends Handler {
async prepare() { async prepare() {
@@ -24,6 +27,24 @@ class SystemMainHandler extends SystemHandler {
} }
} }
class SystemCheckConnHandler extends ConnectionHandler {
async prepare() {
this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
await this.check();
}
async check() {
const log = (payload) => this.send({ type: 'log', payload });
const warn = (payload) => this.send({ type: 'warn', payload });
const error = (payload) => this.send({ type: 'error', payload });
await check.start(log, warn, error, (id) => { this.id = id; });
}
async cleanup() {
check.cancel(this.token);
}
}
class SystemDashboardHandler extends SystemHandler { class SystemDashboardHandler extends SystemHandler {
async get() { async get() {
this.response.template = 'manage_dashboard.html'; this.response.template = 'manage_dashboard.html';
@@ -111,8 +132,8 @@ class SystemScriptHandler extends SystemHandler {
class SystemSettingHandler extends SystemHandler { class SystemSettingHandler extends SystemHandler {
async get() { async get() {
this.response.template = 'manage_settings.html'; this.response.template = 'manage_setting.html';
this.response.body.path.push(['manage_settings', null]); this.response.body.path.push(['manage_setting', null]);
this.response.body.current = {}; this.response.body.current = {};
this.response.body.settings = setting.SYSTEM_SETTINGS; this.response.body.settings = setting.SYSTEM_SETTINGS;
for (const s of this.response.body.settings) { for (const s of this.response.body.settings) {
@@ -129,7 +150,7 @@ class SystemSettingHandler extends SystemHandler {
const subtasks = []; const subtasks = [];
for (const sub in args[key]) { for (const sub in args[key]) {
const s = setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`]; const s = setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`];
if (s) { if (s && !((s.flag & setting.FLAG_SECRET) && args[key][sub] === '')) {
if (s.ui === 'number') args[key][sub] = Number(args[key][sub]); if (s.ui === 'number') args[key][sub] = Number(args[key][sub]);
subtasks.push(system.set(`${key}.${sub}`, args[key][sub])); subtasks.push(system.set(`${key}.${sub}`, args[key][sub]));
} }
@@ -137,7 +158,7 @@ class SystemSettingHandler extends SystemHandler {
tasks.push(Promise.all(subtasks)); tasks.push(Promise.all(subtasks));
} else { } else {
const s = setting.SYSTEM_SETTINGS_BY_KEY[key]; const s = setting.SYSTEM_SETTINGS_BY_KEY[key];
if (s) { if (s && !((s.flag & setting.FLAG_SECRET) && args[key] === '')) {
if (s.ui === 'number') args[key] = Number(args[key]); if (s.ui === 'number') args[key] = Number(args[key]);
tasks.push(system.set(key, args[key])); tasks.push(system.set(key, args[key]));
} }
@@ -154,6 +175,7 @@ async function apply() {
Route('manage_script', '/manage/script', SystemScriptHandler); Route('manage_script', '/manage/script', SystemScriptHandler);
Route('manage_module', '/manage/module', SystemModuleHandler); Route('manage_module', '/manage/module', SystemModuleHandler);
Route('manage_setting', '/manage/setting', SystemSettingHandler); Route('manage_setting', '/manage/setting', SystemSettingHandler);
Connection('manage_check', '/manage/check-conn', SystemCheckConnHandler);
} }
global.Hydro.handler.manage = module.exports = apply; global.Hydro.handler.manage = module.exports = apply;
+7 -1
View File
@@ -1,10 +1,16 @@
/* eslint-disable no-empty-function */
/* eslint-disable class-methods-use-this */
const { Route, Handler } = require('../service/server'); const { Route, Handler } = require('../service/server');
const { NotFoundError } = require('../error'); const { NotFoundError } = require('../error');
class NotFoundHandler extends Handler { class NotFoundHandler extends Handler {
async prepare() { // eslint-disable-line class-methods-use-this async prepare() {
throw new NotFoundError(); throw new NotFoundError();
} }
async get() { }
async post() { }
} }
async function apply() { async function apply() {
+1 -1
View File
@@ -1,7 +1,7 @@
// From https://github.com/pvorb/node-md5/blob/master/md5.js // From https://github.com/pvorb/node-md5/blob/master/md5.js
// BSD 3-Clause "New" or "Revised" License // BSD 3-Clause "New" or "Revised" License
/* eslint-disable no-bitwise */ /* eslint-disable max-len */ /* eslint-disable max-len */
function safeAdd(x, y) { function safeAdd(x, y) {
const lsw = (x & 0xffff) + (y & 0xffff); const lsw = (x & 0xffff) + (y & 0xffff);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16); const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+1
View File
@@ -23,6 +23,7 @@ class Nunjucks extends nunjucks.Environment {
}); });
this.addFilter('ansi', (self) => misc.ansiToHtml(self)); this.addFilter('ansi', (self) => misc.ansiToHtml(self));
this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64')); this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64'));
this.addFilter('bitand', (self, val) => self & val);
} }
} }
const env = new Nunjucks(); const env = new Nunjucks();
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-bitwise */
const PERM = { const PERM = {
PERM_NONE: '', PERM_NONE: '',
+17 -6
View File
@@ -9,6 +9,10 @@ for (const country of countries) {
} }
const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]); const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]);
const FLAG_HIDDEN = 1;
const FLAG_DISABLED = 2;
const FLAG_SECRET = 4;
const PREFERENCE_SETTINGS = []; const PREFERENCE_SETTINGS = [];
const ACCOUNT_SETTINGS = []; const ACCOUNT_SETTINGS = [];
const DOMAIN_SETTINGS = []; const DOMAIN_SETTINGS = [];
@@ -23,9 +27,9 @@ const SYSTEM_SETTINGS_BY_KEY = {};
const Setting = ( const Setting = (
family, key, range = null, family, key, range = null,
value = null, type = 'text', name = '', value = null, type = 'text', name = '',
desc = '', hidden = false, disabled = false, desc = '', flag = 0,
) => ({ ) => ({
family, key, range, value, type, name, desc, hidden, disabled, family, key, range, value, type, name, desc, flag,
}); });
const PreferenceSetting = (...settings) => { const PreferenceSetting = (...settings) => {
@@ -93,11 +97,15 @@ DomainSetting(
Setting('setting_domain', 'name', null, 'New domain', 'text', 'name'), Setting('setting_domain', 'name', null, 'New domain', 'text', 'name'),
Setting('setting_domain', 'gravatar', null, '', 'text', 'gravatar', 'Will be used as the domain icon.'), Setting('setting_domain', 'gravatar', null, '', 'text', 'gravatar', 'Will be used as the domain icon.'),
Setting('setting_domain', 'bulletin', null, '', 'markdown', 'Bulletin'), Setting('setting_domain', 'bulletin', null, '', 'markdown', 'Bulletin'),
Setting('storage', 'nAccept', null, 0, 'number', 'nAccept', null, FLAG_HIDDEN & FLAG_DISABLED),
Setting('storage', 'nSubmit', null, 0, 'number', 'nSubmit', null, FLAG_HIDDEN & FLAG_DISABLED),
Setting('storage', 'nLike', null, 0, 'number', 'nLike', null, FLAG_HIDDEN & FLAG_DISABLED),
Setting('storage', 'rating', null, 1500, 'number', 'rating', null, FLAG_HIDDEN & FLAG_DISABLED),
); );
SystemSetting( SystemSetting(
Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'), Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'),
Setting('setting_smtp', 'smtp.pass', null, null, 'password', 'SMTP Password'), Setting('setting_smtp', 'smtp.pass', null, null, 'password', 'SMTP Password', null, FLAG_SECRET),
Setting('setting_smtp', 'smtp.host', null, null, 'text', 'SMTP Server Host'), Setting('setting_smtp', 'smtp.host', null, null, 'text', 'SMTP Server Host'),
Setting('setting_smtp', 'smtp.port', null, 465, 'number', 'SMTP Server Port'), Setting('setting_smtp', 'smtp.port', null, 465, 'number', 'SMTP Server Port'),
Setting('setting_smtp', 'smtp.from', null, null, 'text', 'Mail From'), Setting('setting_smtp', 'smtp.from', null, null, 'text', 'Mail From'),
@@ -109,9 +117,9 @@ SystemSetting(
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'), Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'), Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'),
Setting('setting_oauth', 'oauth.githubappid', null, null, 'text', 'Github Oauth AppID'), Setting('setting_oauth', 'oauth.githubappid', null, null, 'text', 'Github Oauth AppID'),
Setting('setting_oauth', 'oauth.githubsecret', null, null, 'text', 'Github Oauth Secret'), Setting('setting_oauth', 'oauth.githubsecret', null, null, 'text', 'Github Oauth Secret', null, FLAG_SECRET),
Setting('setting_oauth', 'oauth.googleappid', null, null, 'text', 'Google Oauth ClientID'), Setting('setting_oauth', 'oauth.googleappid', null, null, 'text', 'Google Oauth ClientID', null),
Setting('setting_oauth', 'oauth.googlesecret', null, null, 'text', 'Google Oauth Secret'), Setting('setting_oauth', 'oauth.googlesecret', null, null, 'text', 'Google Oauth Secret', null, FLAG_SECRET),
Setting('setting_proxy', 'proxy', null, null, 'text', 'Proxy Server URL'), Setting('setting_proxy', 'proxy', null, null, 'text', 'Proxy Server URL'),
Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'number', 'Problems per Page'), Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'number', 'Problems per Page'),
Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'number', 'Contests per Page'), Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'number', 'Contests per Page'),
@@ -129,6 +137,9 @@ global.Hydro.model.setting = module.exports = {
DomainSetting, DomainSetting,
DomainUserSetting, DomainUserSetting,
SystemSetting, SystemSetting,
FLAG_HIDDEN,
FLAG_DISABLED,
FLAG_SECRET,
PREFERENCE_SETTINGS, PREFERENCE_SETTINGS,
ACCOUNT_SETTINGS, ACCOUNT_SETTINGS,
SETTINGS, SETTINGS,
+22 -19
View File
@@ -9,6 +9,14 @@ const db = require('../service/db');
const coll = db.collection('user'); const coll = db.collection('user');
function setPassword(uid, password) {
const salt = String.random();
return coll.findOneAndUpdate(
{ _id: uid },
{ $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } },
);
}
class USER { class USER {
constructor(udoc, dudoc) { constructor(udoc, dudoc) {
this.udoc = () => udoc; this.udoc = () => udoc;
@@ -18,7 +26,12 @@ class USER {
this.uname = udoc.uname; this.uname = udoc.uname;
this.salt = () => udoc.salt; this.salt = () => udoc.salt;
this.hash = () => udoc.hash; this.hash = () => udoc.hash;
this.hashType = udoc.hashType || 'hydro';
this.priv = udoc.priv; this.priv = udoc.priv;
this.regat = udoc.regat;
this.loginat = udoc.loginat;
this.perm = dudoc.perm;
this.role = dudoc.role || 'default';
for (const key in setting.SETTINGS_BY_KEY) { for (const key in setting.SETTINGS_BY_KEY) {
if (udoc[key]) this[key] = udoc[key]; if (udoc[key]) this[key] = udoc[key];
@@ -27,14 +40,12 @@ class USER {
} }
} }
this.regat = udoc.regat; for (const key in setting.DOMAIN_USER_SETTINGS_BY_KEY) {
this.loginat = udoc.loginat; if (dudoc[key]) this[key] = dudoc[key];
this.ban = udoc.ban || false; else if (setting.DOMAIN_USER_SETTINGS_BY_KEY[key].value) {
this.nAccept = dudoc.nAccept || 0; this[key] = setting.DOMAIN_USER_SETTINGS_BY_KEY[key].value;
this.nSubmit = dudoc.nSubmit || 0; }
this.rating = dudoc.rating || 1500; }
this.perm = dudoc.perm;
this.role = dudoc.role || 'default';
} }
hasPerm(p) { hasPerm(p) {
@@ -42,15 +53,16 @@ class USER {
} }
hasPriv(p) { hasPriv(p) {
// eslint-disable-next-line no-bitwise
return this.priv & p; return this.priv & p;
} }
checkPassword(password) { checkPassword(password) {
const h = global.Hydro.lib[`hash.${this.hashType || 'hydro'}`]; const h = global.Hydro.lib[`hash.${this.hashType}`];
if (!h) throw new Error('Unknown hash method'); if (!h) throw new Error('Unknown hash method');
if (!(h(password, this.salt(), this) === this.hash())) { if (!(h(password, this.salt(), this) === this.hash())) {
throw new LoginError(this.uname); throw new LoginError(this.uname);
} else if (this.hashType !== 'hydro') {
setPassword(this._id, password);
} }
} }
} }
@@ -59,7 +71,6 @@ async function getInDomain(domainId, udoc) {
let dudoc = await document.getStatus(domainId, document.TYPE_DOMAIN_USER, 0, udoc._id); let dudoc = await document.getStatus(domainId, document.TYPE_DOMAIN_USER, 0, udoc._id);
dudoc = dudoc || {}; dudoc = dudoc || {};
if (udoc._id === 1) dudoc.role = 'guest'; if (udoc._id === 1) dudoc.role = 'guest';
// eslint-disable-next-line no-bitwise
if (udoc.priv & PRIV.PRIV_MANAGE_ALL_DOMAIN) dudoc.role = 'admin'; if (udoc.priv & PRIV.PRIV_MANAGE_ALL_DOMAIN) dudoc.role = 'admin';
const p = await document.get(domainId, document.TYPE_DOMAIN_USER, dudoc.role || 'default'); const p = await document.get(domainId, document.TYPE_DOMAIN_USER, dudoc.role || 'default');
dudoc.perm = p ? p.content : BUILTIN_ROLES[dudoc.role || 'default'].perm; dudoc.perm = p ? p.content : BUILTIN_ROLES[dudoc.role || 'default'].perm;
@@ -116,14 +127,6 @@ async function getByEmail(domainId, mail, ignoreMissing = false) {
return new USER(udoc, dudoc); return new USER(udoc, dudoc);
} }
function setPassword(uid, password) {
const salt = String.random();
return coll.findOneAndUpdate(
{ _id: uid },
{ $set: { salt, hash: pwhash(password, salt), hashType: 'hydro' } },
);
}
function setById(uid, $set, $unset) { function setById(uid, $set, $unset) {
const op = {}; const op = {};
if ($set && Object.keys($set).length) op.$set = $set; if ($set && Object.keys($set).length) op.$set = $set;
+74 -86
View File
@@ -121,41 +121,7 @@ async function prepare() {
})); }));
} }
class Handler { const HandlerMixin = (Class) => class extends Class {
/**
* @param {import('koa').Context} ctx
*/
constructor(ctx) {
this.ctx = ctx;
this.request = {
host: ctx.request.host,
hostname: ctx.request.hostname,
ip: ctx.request.ip,
headers: ctx.request.headers,
cookies: ctx.cookies,
body: ctx.request.body,
files: ctx.request.files,
query: ctx.query,
path: ctx.path,
params: ctx.params,
referer: ctx.request.headers.referer || '/',
};
this.response = {
body: '',
type: '',
status: null,
template: null,
redirect: null,
attachment: (name) => ctx.attachment(name),
};
this.UIContext = {
cdn_prefix: '/',
url_prefix: '/',
};
this._handler = {};
this.session = {};
}
async renderHTML(name, context) { async renderHTML(name, context) {
if (enableLog) console.time(name); if (enableLog) console.time(name);
this.hasPerm = (perm) => this.user.hasPerm(perm); this.hasPerm = (perm) => this.user.hasPerm(perm);
@@ -171,9 +137,12 @@ class Handler {
return res; return res;
} }
async render(name, context) { async limitRate(op, periodSecs, maxOperations) {
this.response.body = await this.renderHTML(name, context); await opcount.inc(op, this.request.ip, periodSecs, maxOperations);
this.response.type = 'text/html'; }
translate(str) {
return str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : '';
} }
renderTitle(str) { renderTitle(str) {
@@ -214,26 +183,6 @@ class Handler {
} }
} }
async limitRate(op, periodSecs, maxOperations) {
await opcount.inc(op, this.request.ip, periodSecs, maxOperations);
}
back(body) {
this.response.body = body || this.response.body || {};
this.response.redirect = this.request.headers.referer || '/';
}
translate(str) {
return str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : '';
}
binary(data, name) {
this.response.body = data;
this.response.template = null;
this.response.type = 'application/octet-stream';
this.response.disposition = `attachment; filename="${name}"`;
}
url(name, kwargs = {}) { url(name, kwargs = {}) {
let res = '#'; let res = '#';
const args = { ...kwargs }; const args = { ...kwargs };
@@ -252,8 +201,61 @@ class Handler {
} }
return res; return res;
} }
};
async ___prepare({ domainId }) { const Handler = HandlerMixin(class {
/**
* @param {import('koa').Context} ctx
*/
constructor(ctx) {
this.ctx = ctx;
this.request = {
host: ctx.request.host,
hostname: ctx.request.hostname,
ip: ctx.request.ip,
headers: ctx.request.headers,
cookies: ctx.cookies,
body: ctx.request.body,
files: ctx.request.files,
query: ctx.query,
path: ctx.path,
params: ctx.params,
referer: ctx.request.headers.referer || '/',
};
this.response = {
body: '',
type: '',
status: null,
template: null,
redirect: null,
attachment: (name) => ctx.attachment(name),
};
this.UIContext = {
cdn_prefix: '/',
url_prefix: '/',
};
this._handler = {};
this.session = {};
}
async render(name, context) {
this.response.body = await this.renderHTML(name, context);
this.response.type = 'text/html';
}
back(body) {
this.response.body = body || this.response.body || {};
this.response.redirect = this.request.headers.referer || '/';
}
binary(data, name) {
this.response.body = data;
this.response.template = null;
this.response.type = 'application/octet-stream';
this.response.disposition = `attachment; filename="${name}"`;
}
async init({ domainId }) {
this.response.body = {}; this.response.body = {};
this.now = new Date(); this.now = new Date();
this._handler.sid = this.request.cookies.get('sid'); this._handler.sid = this.request.cookies.get('sid');
@@ -377,7 +379,7 @@ class Handler {
}; };
await this.___cleanup().catch(() => { }); await this.___cleanup().catch(() => { });
} }
} });
async function handle(ctx, HandlerClass, checker) { async function handle(ctx, HandlerClass, checker) {
global.Hydro.stat.reqCount++; global.Hydro.stat.reqCount++;
@@ -394,7 +396,7 @@ async function handle(ctx, HandlerClass, checker) {
.replace(/_([a-z])/gm, (s) => s[1].toUpperCase()); .replace(/_([a-z])/gm, (s) => s[1].toUpperCase());
} }
if (h.___prepare) await h.___prepare(args); if (h.init) await h.init(args);
if (checker) checker.call(h); if (checker) checker.call(h);
if (method === 'post') { if (method === 'post') {
await h.checkCsrfToken(args.csrfToken); await h.checkCsrfToken(args.csrfToken);
@@ -437,7 +439,7 @@ async function handle(ctx, HandlerClass, checker) {
if (h.__cleanup) await h.__cleanup(args); if (h.__cleanup) await h.__cleanup(args);
if (h.___cleanup) await h.___cleanup(args); if (h.___cleanup) await h.___cleanup(args);
} catch (e) { } catch (e) {
if (h.onerror) await h.onerror(e); await h.onerror(e);
} }
} }
@@ -471,7 +473,7 @@ function Route(name, route, RouteHandler, ...permPrivChecker) {
router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker)); router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker));
} }
class ConnectionHandler { const ConnectionHandler = HandlerMixin(class {
/** /**
* @param {import('sockjs').Connection} conn * @param {import('sockjs').Connection} conn
*/ */
@@ -486,25 +488,6 @@ class ConnectionHandler {
for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]); for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]);
} }
url(name, kwargs = {}) {
let res = '#';
const args = { ...kwargs };
try {
if (this.args.domainId !== 'system') {
name += '_with_domainId';
args.domainId = args.domainId || this.args.domainId;
}
const { anchor, query } = args;
if (query) res = router.url(name, args, { query });
else res = router.url(name, args);
if (anchor) return `${res}#${anchor}`;
} catch (e) {
console.error(e.message);
console.error(name, kwargs);
}
return res;
}
async renderHTML(name, context) { async renderHTML(name, context) {
this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) }; this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
const res = await template.render(name, Object.assign(context, { const res = await template.render(name, Object.assign(context, {
@@ -524,7 +507,12 @@ class ConnectionHandler {
this.conn.close(code, reason); this.conn.close(code, reason);
} }
async ___prepare({ domainId }) { onerror(err) {
console.error(err);
this.close(1001, err.toString());
}
async init({ domainId }) {
try { try {
this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN); this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN);
await token.del(this.request.params.token, token.TYPE_CSRF_TOKEN); await token.del(this.request.params.token, token.TYPE_CSRF_TOKEN);
@@ -536,7 +524,7 @@ class ConnectionHandler {
this.user = await user.getById(domainId, this.session.uid); this.user = await user.getById(domainId, this.session.uid);
if (!this.user) throw new UserNotFoundError(this.session.uid); if (!this.user) throw new UserNotFoundError(this.session.uid);
} }
} });
function Connection(name, prefix, RouteConnHandler, permission) { function Connection(name, prefix, RouteConnHandler, permission) {
const sock = sockjs.createServer({ prefix }); const sock = sockjs.createServer({ prefix });
@@ -545,7 +533,7 @@ function Connection(name, prefix, RouteConnHandler, permission) {
try { try {
const args = { domainId: 'system', ...h.request.params }; const args = { domainId: 'system', ...h.request.params };
h.args = args; h.args = args;
if (h.___prepare) await h.___prepare(args); if (h.init) await h.init(args);
if (permission) h.checkPerm(permission); if (permission) h.checkPerm(permission);
let checking = ''; let checking = '';
try { try {
@@ -575,7 +563,7 @@ function Connection(name, prefix, RouteConnHandler, permission) {
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
if (h.onerror) await h.onerror(e); await h.onerror(e);
} }
}); });
sock.installHandlers(server); sock.installHandlers(server);
+1 -1
View File
@@ -405,7 +405,7 @@ Mark Yellow: 标黄
Memory Cost: 内存占用 Memory Cost: 内存占用
Memory: 内存 Memory: 内存
Message {0} not found.: 消息 {0} 不存在。 Message {0} not found.: 消息 {0} 不存在。
Messages: 站内消息 Messages: 消息
Method: 方法 Method: 方法
Month: 月 Month: 月
Monthly Popular: 月度最受欢迎 Monthly Popular: 月度最受欢迎
+1 -1
View File
@@ -266,7 +266,7 @@ Manage: 管理
Memory Cost: 記憶體佔用 Memory Cost: 記憶體佔用
Memory: 記憶體 Memory: 記憶體
Message {0} not found.: 訊息 {0} 不存在。 Message {0} not found.: 訊息 {0} 不存在。
Messages: 站內訊息 Messages: 訊息
Most Upvoted Solutions: 最熱題解 Most Upvoted Solutions: 最熱題解
My Domains: 我的域 My Domains: 我的域
My Profile: 我的資料 My Profile: 我的資料
+8 -4
View File
@@ -1,13 +1,17 @@
{% import "components/user.html" as user %} {% import "components/user.html" as user %}
{% extends "manage_base.html" %} {% extends "manage_base.html" %}
{% block manage_content %} {% block manage_content %}
{% if domain.bulletin %} <script>
var Context = {
'socketUrl': "/manage/check-conn?token={{ handler.csrfToken }}"
};
</script>
<div class="section"> <div class="section">
<div class="section__body typo"> <div class="section__header">
{{ domain.bulletin|markdown|safe }} <h1 class="section__title" data-heading>{{ _('Messages') }}</h1>
</div> </div>
<div class="section__body typo" id="messages"></div>
</div> </div>
{% endif %}
<div class="section"> <div class="section">
<div class="section__header"> <div class="section__header">
<h1 class="section__title" data-heading>{{ _('Information') }}</h1> <h1 class="section__title" data-heading>{{ _('Information') }}</h1>
File renamed without changes.
+11 -7
View File
@@ -5,15 +5,17 @@
</div> </div>
<div class="section__body"> <div class="section__body">
{% for setting in family_settings %} {% for setting in family_settings %}
{% if not setting.hidden %} {% if not setting.flag|bitand(model.setting.FLAG_HIDDEN) %}
{% set secret = setting.flag|bitand(model.setting.FLAG_SECRET) != 0 %}
{% if setting.type == 'text' or setting.type == 'password' or setting.type == 'number' %} {% if setting.type == 'text' or setting.type == 'password' or setting.type == 'number' %}
{{ form.form_text({ {{ form.form_text({
type:setting.type, type:setting.type,
label:setting.name, label:setting.name,
help_text:setting.desc, help_text:setting.desc,
name:setting.key, name:setting.key,
value:current[setting.key] or setting.value, value:'' if secret else (current[setting.key]|default(setting.value)),
disabled:setting.disabled disabled:setting.disabled,
placeholder:_('(Not changed)') if secret else ''
}) }} }) }}
{% elif setting.type == 'select' %} {% elif setting.type == 'select' %}
{{ form.form_select({ {{ form.form_select({
@@ -48,17 +50,19 @@
label:setting.name, label:setting.name,
help_text:setting.desc, help_text:setting.desc,
name:setting.key, name:setting.key,
value:current[setting.key] or setting.value, value:'' if secret else (current[setting.key]|default(setting.value)),
disabled:setting.disabled disabled:setting.disabled,
placeholder:_('(Not changed)') if secret else ''
}) }} }) }}
{% elif setting.type == 'markdown' %} {% elif setting.type == 'markdown' %}
{{ form.form_textarea({ {{ form.form_textarea({
label:setting.name, label:setting.name,
help_text:setting.desc, help_text:setting.desc,
name:setting.key, name:setting.key,
value:current[setting.key] or setting.value, value:'' if secret else (current[setting.key]|default(setting.value)),
markdown:true, markdown:true,
disabled:setting.disabled disabled:setting.disabled,
placeholder:_('(Not changed)') if secret else ''
}) }} }) }}
{% elif setting.type == 'boolean' %} {% elif setting.type == 'boolean' %}
{{ form.form_checkbox({ {{ form.form_checkbox({
+15
View File
@@ -106,6 +106,21 @@ blockquote
content: 'WARNING:' content: 'WARNING:'
color: red color: red
&.red
border-color: red
background: #F8F8F8
color: lighten($text-1-color, 10%)
&.yellow
border-color: yellow
background: #F8F8F8
color: lighten($text-1-color, 10%)
&.blue
border-color: blue
background: #F8F8F8
color: lighten($text-1-color, 10%)
acronym, abbr acronym, abbr
border-bottom: 1px dotted border-bottom: 1px dotted
font-variant: normal font-variant: normal
+34
View File
@@ -0,0 +1,34 @@
import { NamedPage } from 'vj/misc/PageLoader';
const page = new NamedPage('manage_dashboard', async () => {
const { default: SockJs } = await import('sockjs-client');
const sock = new SockJs(Context.socketUrl);
sock.onopen = () => {
$('<blockquote class="blue"><p>Connection opened.</p></blockquote>').appendTo('#messages');
};
sock.onmessage = (message) => {
const msg = JSON.parse(message.data);
if (msg.type === 'log') {
const div = $('<blockquote class="blue">').appendTo('#messages');
$(`<p>${msg.payload}</p>`).appendTo(div);
} else if (msg.type === 'warn') {
const div = $('<blockquote class="yellow">').appendTo('#messages');
$(`<p>${msg.payload}</p>`).appendTo(div);
} else if (msg.type === 'error') {
const div = $('<blockquote class="red">').appendTo('#messages');
$(`<p>${msg.payload}</p>`).appendTo(div);
}
};
sock.onclose = (message) => {
$(`
<blockquote class="yellow">
<p>Connection closed,reason=${JSON.stringify(message.reason)}</p>
</blockquote>
`).appendTo('#messages');
};
});
export default page;