From d040b153a11b08d62e3cefade2388c80151570a0 Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 18 Jun 2020 13:14:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BD=A9=E8=89=B2=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E4=BF=A1=E6=81=AF=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hydro/entry/common.js | 30 +++++++++++----- hydro/entry/master.js | 11 +++--- hydro/entry/unzip.js | 6 ++++ hydro/entry/worker.js | 4 ++- hydro/handler/manage.js | 3 +- hydro/handler/problem.js | 2 +- hydro/lib/hash.hydro.js | 2 +- hydro/lib/misc.js | 10 +++++- hydro/lib/template.js | 1 + hydro/lib/useragent.js | 9 +++++ hydro/loader.js | 23 +++++++------ hydro/model/setting.js | 53 +++++++++++++++++++++-------- hydro/model/user.js | 1 - hydro/service/server.js | 10 ++---- module/migrate-vijos/script.js | 8 ++--- package.json | 2 ++ templates/record_detail_status.html | 4 +-- yarn.lock | 10 ++++++ 18 files changed, 130 insertions(+), 59 deletions(-) create mode 100644 hydro/lib/useragent.js diff --git a/hydro/entry/common.js b/hydro/entry/common.js index cc6a80855..f452b9b9c 100644 --- a/hydro/entry/common.js +++ b/hydro/entry/common.js @@ -2,12 +2,13 @@ /* eslint-disable no-eval */ const os = require('os'); const fs = require('fs'); +const yaml = require('js-yaml'); const builtinLib = [ 'axios', 'download', 'i18n', 'mail', 'markdown', 'md5', 'misc', 'paginate', 'hash.hydro', 'rank', 'template', 'validator', 'nav', 'sysinfo', 'testdata.convert.ini', - 'readConfig', 'logger', + 'readConfig', 'logger', 'useragent', ]; const builtinModel = [ @@ -60,17 +61,28 @@ async function locale(pending, fail) { } } -async function config(pending, fail, system) { +async function setting(pending, fail, modelSetting, modelSystem) { + const map = { + system: modelSetting.SystemSetting, + account: modelSetting.AccountSetting, + preference: modelSetting.PreferenceSetting, + }; for (const i of pending) { - const p = `${os.tmpdir()}/hydro/tmp/${i}/config.js`; + const p = `${os.tmpdir()}/hydro/tmp/${i}/setting.yaml`; if (fs.existsSync(p) && !fail.includes(i)) { try { - global.Hydro.config[i] = eval('require')(p); - for (const key in global.Hydro.config[i]) { - if (global.Hydro.config[i][key].default) { - const current = await system.get(`${i}.${key}`); - if (!current) await system.set(`${i}.${key}`, global.Hydro.config[i][key].default); + const cfg = yaml.safeLoad(fs.readFileSync(p).toString()); + for (const key in cfg) { + if (cfg[key].default && modelSystem) { + const current = await modelSystem.get(`${i}.${key}`); + if (!current) await modelSystem.set(`${i}.${key}`, cfg[key].default); } + map[cfg[key].category || 'system']( + modelSetting.Setting( + i, `${i}.${key}`, cfg[key].range, cfg[key].default, + cfg[key].type || 'text', cfg[key].name || key, cfg[key].desc || '', + ), + ); } } catch (e) { console.error(`Config Load Fail: ${i}`); @@ -177,5 +189,5 @@ module.exports = { script, service, template, - config, + setting, }; diff --git a/hydro/entry/master.js b/hydro/entry/master.js index 3d7fcc288..bd100b496 100644 --- a/hydro/entry/master.js +++ b/hydro/entry/master.js @@ -4,7 +4,7 @@ const os = require('os'); const fs = require('fs'); const path = require('path'); const { - lib, service, model, config, + lib, service, model, setting, builtinLib, builtinHandler, builtinModel, } = require('./common'); @@ -61,13 +61,14 @@ async function load(call) { await global.Hydro.model[m].ensureIndexes(); } } - const system = require('../model/system'); - const dbVer = await system.get('db.ver'); + const modelSystem = require('../model/system'); + const modelSetting = require('../model/setting'); + const dbVer = await modelSystem.get('db.ver'); if (dbVer !== 1) { const ins = require('../script/install'); await ins.run({ username: 'Root', password: 'rootroot' }); } - await config(pending, fail); + await setting(pending, fail, modelSetting, modelSystem); for (const i in global.Hydro.service) { if (global.Hydro.service[i].postInit) { try { @@ -78,7 +79,7 @@ async function load(call) { } } pending = []; - return { fail, active }; + return await modelSystem.get('server.worker'); } module.exports = load; diff --git a/hydro/entry/unzip.js b/hydro/entry/unzip.js index efd3f731a..db794deb3 100644 --- a/hydro/entry/unzip.js +++ b/hydro/entry/unzip.js @@ -56,6 +56,12 @@ async function load() { JSON.stringify(m.locale), ); } + if (m.setting) { + fs.writeFileSync( + `${os.tmpdir()}/hydro/tmp/${id}/setting.yaml`, + m.setting, + ); + } if (m.template) { fs.writeFileSync( `${os.tmpdir()}/hydro/tmp/${id}/template.json`, diff --git a/hydro/entry/worker.js b/hydro/entry/worker.js index 211ef7171..c3cb0f132 100644 --- a/hydro/entry/worker.js +++ b/hydro/entry/worker.js @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable import/no-dynamic-require */ const { - locale, template, lib, service, model, handler, script, + locale, template, lib, service, model, handler, script, setting, builtinLib, builtinScript, builtinHandler, builtinModel, } = require('./common'); @@ -34,6 +34,8 @@ async function load() { for (const i of builtinModel) require(`../model/${i}`); for (const i of builtinHandler) require(`../handler/${i}`); await model(pending, fail); + const modelSetting = require('../model/setting'); + await setting(pending, fail, modelSetting); await handler(pending, fail); for (const i in global.Hydro.handler) { await global.Hydro.handler[i](); diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index a4090bb70..94b87a068 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -33,7 +33,8 @@ class SystemDashboardHandler extends SystemHandler { } async postRestart() { - process.send({ event: 'restart' }); + const count = await system.get('server.worker'); + process.send({ event: 'restart', count }); this.back(); } } diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index f3c402bdc..c7f71b435 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -183,7 +183,7 @@ class ProblemSubmitHandler extends ProblemDetailHandler { user.incDomain(domainId, this.user._id, 'nSubmit'), ]); this.response.body = { rid }; - this.response.redirect = `/r/${rid}`; + this.response.redirect = this.url('record_detail', { rid }); } } diff --git a/hydro/lib/hash.hydro.js b/hydro/lib/hash.hydro.js index 5389a2851..309fcd3fd 100644 --- a/hydro/lib/hash.hydro.js +++ b/hydro/lib/hash.hydro.js @@ -1,7 +1,7 @@ const crypto = require('crypto'); function hash(password, salt) { - return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256').toString('hex'); + return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256').toString('hex').substr(0, 64); } global.Hydro.lib['hash.hydro'] = module.exports = hash; diff --git a/hydro/lib/misc.js b/hydro/lib/misc.js index 8fedd9a92..8e5b13aa2 100644 --- a/hydro/lib/misc.js +++ b/hydro/lib/misc.js @@ -1,5 +1,13 @@ +const ansiup = require('ansi_up'); const md5 = require('./md5'); +// eslint-disable-next-line new-cap +const AU = new ansiup.default(); + +function ansiToHtml(str) { + return AU.ansi_to_html(str); +} + function gravatar(email, s = 32) { return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${s}`; } @@ -64,5 +72,5 @@ function formatSeconds(seconds = 0) { } global.Hydro.lib.misc = module.exports = { - gravatar, datetimeSpan, paginate, size, formatSeconds, + gravatar, datetimeSpan, paginate, size, formatSeconds, ansiToHtml, }; diff --git a/hydro/lib/template.js b/hydro/lib/template.js index 023f5b818..ce9315e24 100644 --- a/hydro/lib/template.js +++ b/hydro/lib/template.js @@ -23,6 +23,7 @@ class Nunjucks extends nunjucks.Environment { if (safe) return markdown.safe.render(self); return markdown.unsafe.render(self); }); + this.addFilter('ansi', (self) => misc.ansiToHtml(self)); this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64')); } } diff --git a/hydro/lib/useragent.js b/hydro/lib/useragent.js new file mode 100644 index 000000000..eb8cf76b3 --- /dev/null +++ b/hydro/lib/useragent.js @@ -0,0 +1,9 @@ +const browser = require('detect-browser'); + +function get(str) { + const ua = browser.parseUserAgent(str); + console.log(ua); + return ua; +} + +global.Hydro.lib.useragent = module.exports = get; diff --git a/hydro/loader.js b/hydro/loader.js index dd0320b65..7792f7aab 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -2,7 +2,6 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable no-eval */ const cluster = require('cluster'); -const numCPUs = require('os').cpus().length; async function terminate() { for (const task of global.onDestory) { @@ -16,16 +15,17 @@ async function entry(config) { if (config.entry) { // TODO newProcess const loader = require(`./entry/${config.entry}`); - await loader(entry); + return await loader(entry); } + return null; } async function stopWorker() { cluster.disconnect(); } -async function startWorker() { - for (let i = 0; i < numCPUs; i++) { +async function startWorker(cnt) { + for (let i = 0; i < cnt; i++) { cluster.fork(); } } @@ -46,7 +46,7 @@ async function messageHandler(worker, msg) { console.log('Restarting'); await stopWorker(); console.log('Worker stopped'); - await startWorker(); + await startWorker(msg.count); } else if (msg.event && msg.event === 'run') { await executeCommand(msg.command); } @@ -74,6 +74,7 @@ async function load() { process.on('unhandledRejection', (e) => console.error(e)); process.on('SIGINT', terminate); process.on('message', messageHandler); + cluster.on('message', messageHandler); if (cluster.isMaster) { console.log(`Master ${process.pid} Starting`); process.stdin.setEncoding('utf8'); @@ -84,20 +85,20 @@ async function load() { executeCommand(input); } }); - await entry({ entry: 'master' }); + const cnt = await entry({ entry: 'master' }); cluster.on('exit', (worker, code, signal) => { - console.log(`Worker ${worker.process.pid} exit: ${code} ${signal}`); + console.log(`Worker ${worker.process.pid} ${worker.id} exit: ${code} ${signal}`); }); cluster.on('disconnect', (worker) => { - console.log(`Worker ${worker.process.pid} disconnected`); + console.log(`Worker ${worker.process.pid} ${worker.id} disconnected`); }); cluster.on('listening', (worker, address) => { - console.log(`Worker ${worker.process.pid} listening at `, address); + console.log(`Worker ${worker.process.pid} ${worker.id} listening at `, address); }); cluster.on('online', (worker) => { - console.log(`Worker ${worker.process.pid} is online`); + console.log(`Worker ${worker.process.pid} ${worker.id} is online`); }); - await startWorker(); + await startWorker(cnt); } else { console.log(`Worker ${process.pid} Starting`); await entry({ entry: 'worker' }); diff --git a/hydro/model/setting.js b/hydro/model/setting.js index f56a3e98f..b6f01a43d 100644 --- a/hydro/model/setting.js +++ b/hydro/model/setting.js @@ -10,6 +10,13 @@ for (const country of countries) { } const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]); +const PREFERENCE_SETTINGS = []; +const ACCOUNT_SETTINGS = []; +const SYSTEM_SETTINGS = []; +const SETTINGS = []; +const SETTINGS_BY_KEY = {}; +const SYSTEM_SETTINGS_BY_KEY = {}; + const Setting = ( family, key, range = null, value = null, ui = 'text', name = '', @@ -18,7 +25,28 @@ const Setting = ( family, key, range, value, ui, name, desc, }); -const PREFERENCE_SETTINGS = [ +const PreferenceSetting = (...settings) => { + for (const setting of settings) { + PREFERENCE_SETTINGS.push(setting); + SETTINGS.push(setting); + SETTINGS_BY_KEY[setting.key] = setting; + } +}; +const AccountSetting = (...settings) => { + for (const setting of settings) { + ACCOUNT_SETTINGS.push(setting); + SETTINGS.push(setting); + SETTINGS_BY_KEY[setting.key] = setting; + } +}; +const SystemSetting = (...settings) => { + for (const setting of settings) { + SYSTEM_SETTINGS.push(setting); + SYSTEM_SETTINGS_BY_KEY[setting.key] = setting; + } +}; + +PreferenceSetting( Setting('setting_display', 'viewLang', builtin.VIEW_LANGS.map((i) => [i.code, i.name]), options.default_locale, 'select', 'UI Language'), Setting('setting_display', 'timezone', timezones, @@ -28,9 +56,9 @@ const PREFERENCE_SETTINGS = [ Setting('setting_usage', 'codeTemplate', null, null, 'textarea', 'Default Code Template', 'If left blank, the built-in template of the corresponding language will be used.'), -]; +); -const ACCOUNT_SETTINGS = [ +AccountSetting( Setting('setting_info', 'gravatar', null, null, 'text', 'Gravatar Email', 'We use Gravatar to present your avatar icon.'), @@ -43,14 +71,9 @@ const ACCOUNT_SETTINGS = [ Setting('setting_customize', 'backgroundImage', null, '/components/background/profile/backgrounds/1.jpg', 'text', 'Profile Background Image', 'Choose the background image in your profile page.'), -]; +); -const SETTINGS = [...PREFERENCE_SETTINGS, ...ACCOUNT_SETTINGS]; -const SETTINGS_BY_KEY = {}; - -for (const setting of SETTINGS) SETTINGS_BY_KEY[setting.key] = setting; - -const SYSTEM_SETTINGS = [ +SystemSetting( Setting('setting_basic', 'user.register', null, false, 'checkbox', 'Allow register'), Setting('setting_basic', 'user.create_domain', null, true, 'checkbox', 'Allow domain creation'), Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'), @@ -59,6 +82,7 @@ const SYSTEM_SETTINGS = [ 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.secure', null, false, 'checkbox', 'SSL'), + Setting('setting_server', 'server.worker', null, 1, 'number', 'Server Workers Number'), Setting('setting_server', 'server.hostname', null, null, 'text', 'Server Hostname'), Setting('setting_server', 'server.host', null, null, 'text', 'Server Host'), Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'), @@ -70,14 +94,13 @@ const SYSTEM_SETTINGS = [ Setting('setting_constant', 'SOLUTION_PER_PAGE', null, 20, 'number', 'Solutions per Page'), Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'number', 'Training per Page'), Setting('setting_constant', 'REPLY_PER_PAGE', null, 50, 'number', 'Reply per Page'), -]; - -const SYSTEM_SETTINGS_BY_KEY = {}; - -for (const setting of SYSTEM_SETTINGS) SYSTEM_SETTINGS_BY_KEY[setting.key] = setting; +); global.Hydro.model.setting = module.exports = { Setting, + PreferenceSetting, + AccountSetting, + SystemSetting, PREFERENCE_SETTINGS, ACCOUNT_SETTINGS, SETTINGS, diff --git a/hydro/model/user.js b/hydro/model/user.js index e1c4dac29..59351ebd4 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -42,7 +42,6 @@ class USER { checkPassword(password) { const h = global.Hydro.lib[`hash.${this.hashType || 'hydro'}`]; if (!h) throw new Error('Unknown hash method'); - console.log(h(password, this.salt(), this), this.hash()); if (!(h(password, this.salt(), this) === this.hash())) { throw new LoginError(this.uname); } diff --git a/hydro/service/server.js b/hydro/service/server.js index 7073bc1cf..0fa2dbeb6 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -487,13 +487,9 @@ async function start() { app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); app.use(router.routes()).use(router.allowedMethods()); Route('notfound_handler', '*', Handler); - if (cluster.worker.id === 1) { - const port = await system.get('server.port'); - server.listen(port); - console.log('Server listening at: %s', port); - } else { - server.listen(); - } + const port = await system.get('server.port'); + server.listen(port); + console.log('Server listening at: %s', port); } global.Hydro.service.server = module.exports = { diff --git a/module/migrate-vijos/script.js b/module/migrate-vijos/script.js index a65d0d240..055229f3e 100644 --- a/module/migrate-vijos/script.js +++ b/module/migrate-vijos/script.js @@ -268,10 +268,10 @@ async function domainUser(src, report) { for (const key in doc) { if (typeof mapper[key] === 'string') { d[mapper[key]] = doc[key]; - } else if (typeof mapper[key] === 'object') { - d[mapper[key].field] = mapper[key].processer(doc[key]); } else if (mapper[key] === null) { // Ignore this key + } else if (typeof mapper[key] === 'object') { + d[mapper[key].field] = mapper[key].processer(doc[key]); } else { await report({ message: `Unknown key ${key} in collection domain.user` }); } @@ -348,10 +348,10 @@ async function task(name, src, report) { for (const key in doc) { if (typeof mapper[key] === 'string') { d[mapper[key]] = doc[key]; - } else if (typeof mapper[key] === 'object') { - d[mapper[key].field] = mapper[key].processer(doc[key]); } else if (mapper[key] === null) { // Ignore this key + } else if (typeof mapper[key] === 'object') { + d[mapper[key].field] = mapper[key].processer(doc[key]); } else { await report({ message: `Unknown key ${key} in collection ${name}` }); } diff --git a/package.json b/package.json index 3fd4aa4f1..27f253916 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,10 @@ "license": "GPL-3.0-only", "dependencies": { "adm-zip": "^0.4.14", + "ansi_up": "^4.0.4", "axios": "^0.19.0", "bson": "^4.0.2", + "detect-browser": "^5.1.1", "js-yaml": "^3.13.1", "koa": "^2.11.0", "koa-body": "^4.1.1", diff --git a/templates/record_detail_status.html b/templates/record_detail_status.html index ea1b2188f..ca1c3a0c5 100644 --- a/templates/record_detail_status.html +++ b/templates/record_detail_status.html @@ -9,12 +9,12 @@ {% if rdoc.compilerTexts.length %}
-
{{ rdoc.compilerTexts|join('\n') }}
+
{{ rdoc.compilerTexts|join('\n')|ansi|safe }}
{% endif %} {% if rdoc.judgeTexts.length %}
-
{{ rdoc.judgeTexts|join('\n') }}
+
{{ rdoc.judgeTexts|join('\n')|ansi|safe }}
{% endif %} {% if rdoc.testCases.length %} diff --git a/yarn.lock b/yarn.lock index a7c8f5883..bc17a4357 100644 --- a/yarn.lock +++ b/yarn.lock @@ -316,6 +316,11 @@ ansi-styles@^4.1.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" +ansi_up@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/ansi_up/-/ansi_up-4.0.4.tgz#5b8c35f0b02e4476f3f18cf89c3bf48d15d054f6" + integrity sha512-vRxC8q6QY918MbehO869biJW4tiunJdjOhi5fpY6NLOliBQlZhOkKgABJKJqH+JZfb/WfjvjN1chLWI6tODerw== + any-promise@^1.0.0, any-promise@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -1142,6 +1147,11 @@ destroy@^1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-browser@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.1.1.tgz#a800db91d3fd60d0861669f5984f1be9ffbe009c" + integrity sha512-5n2aWI57qC3kZaK4j2zYsG6L1LrxgLptGCNhMQgdKhVn6cSdcq43pp6xHPfTHG3TYM6myF4tIPWiZtfdVDgb9w== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"