mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
模块化,添加webpack支持
This commit is contained in:
1 parent
4ec2a46877
commit
75ebc1a746
30 files changed
+622
-459
No files matched your search
@@ -7,3 +7,5 @@ ui/misc/.iconfont
|
||||
ui/static/locale
|
||||
.cache
|
||||
.webpackStats.json
|
||||
module/**
|
||||
!.gitkeep
|
||||
@@ -0,0 +1,53 @@
|
||||
const fs = require('fs');
|
||||
const webpack = require('webpack');
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
|
||||
const root = require('./root');
|
||||
const exist = (name) => {
|
||||
try {
|
||||
fs.statSync(root(name));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const build = async () => {
|
||||
const modules = fs.readdirSync(root('module'));
|
||||
const config = {
|
||||
mode: 'production',
|
||||
entry: {},
|
||||
output: {
|
||||
filename: 'module/[name].js',
|
||||
path: root('.build')
|
||||
},
|
||||
target: 'node',
|
||||
module: {},
|
||||
plugins: [
|
||||
new webpack.ProgressPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
]
|
||||
};
|
||||
for (let i of modules) {
|
||||
if (!i.startsWith('.')) {
|
||||
if (exist(`module/${i}/model.js`)) {
|
||||
config.entry[`${i}/model`] = root(`module/${i}/model.js`);
|
||||
}
|
||||
if (exist(`module/${i}/handler.js`)) {
|
||||
config.entry[`${i}/handler`] = root(`module/${i}/handler.js`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const compiler = webpack(config);
|
||||
await new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
console.error(err.stack || err);
|
||||
if (err.details) console.error(err.details);
|
||||
reject();
|
||||
}
|
||||
if (stats.hasErrors()) process.exitCode = 1;
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = build;
|
||||
+3
-5
@@ -4,8 +4,6 @@ try {
|
||||
fs.rmdirSync(root('.build'));
|
||||
} catch (e) { }
|
||||
fs.mkdirSync(root('.build'));
|
||||
let progress = (name) => ((prog) => {
|
||||
console.log(name, prog);
|
||||
});
|
||||
require('./locales')('locales', progress);
|
||||
require('./webpack')('webpack', progress);
|
||||
require('./locales')();
|
||||
require('./buildModule')();
|
||||
require('./webpack')();
|
||||
+1
-4
@@ -1,15 +1,12 @@
|
||||
const yaml = require('js-yaml');
|
||||
const fs = require('fs');
|
||||
const root = require('./root');
|
||||
const build = async (next) => {
|
||||
const build = async () => {
|
||||
let langs = fs.readdirSync(root('locales'));
|
||||
next({ total: langs.length });
|
||||
let lang = {};
|
||||
let count = 0;
|
||||
for (let i of langs) {
|
||||
const content = fs.readFileSync(root(`locales/${i}`)).toString();
|
||||
lang[i.split('.')[0]] = yaml.safeLoad(content);
|
||||
next({ progress: ++count });
|
||||
}
|
||||
fs.writeFileSync(root('.build/locales.json'), JSON.stringify(lang));
|
||||
}
|
||||
|
||||
+34
-22
@@ -1,11 +1,24 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const webpack = require('webpack');
|
||||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
|
||||
const root = require('./root');
|
||||
const build = async (next) => {
|
||||
const modules = fs.readdirSync(root('hydro', 'module'));
|
||||
const build = async () => {
|
||||
function hackNodeModuleFormidable() {
|
||||
const tasks = ['incoming_form', 'file', 'json_parser', 'querystring_parser'];
|
||||
for (let task of tasks) {
|
||||
let file = fs.readFileSync(root(`node_modules/formidable/lib/${task}.js`)).toString();
|
||||
if (file.startsWith('if (global.GENTLY) require = GENTLY.hijack(require);')) {
|
||||
file = file.split('\n');
|
||||
file[0] = '';
|
||||
file = file.join('\n');
|
||||
fs.writeFileSync(root(`node_modules/formidable/lib/${task}.js`), file);
|
||||
}
|
||||
}
|
||||
}
|
||||
hackNodeModuleFormidable();
|
||||
|
||||
const config = {
|
||||
mode: 'production',
|
||||
mode: 'development',
|
||||
entry: {
|
||||
development: root('hydro/development.js'),
|
||||
install: root('hydro/install.js'),
|
||||
@@ -16,25 +29,24 @@ const build = async (next) => {
|
||||
path: root('.build')
|
||||
},
|
||||
target: 'node',
|
||||
module: {}
|
||||
module: {},
|
||||
plugins: [
|
||||
new webpack.ProgressPlugin(),
|
||||
new FriendlyErrorsPlugin(),
|
||||
]
|
||||
};
|
||||
for (let i of modules) {
|
||||
if (fs.statSync(path.resolve(__dirname, 'hydro', 'module', i)).isDirectory()) {
|
||||
config.entry[root(`.build/module/${i}`)] = root(`./hydro/module/${i}/index.js`);
|
||||
} else {
|
||||
config.entry[root(`.build/module/${i}`)] = root(`./hydro/module/${i}`);
|
||||
}
|
||||
}
|
||||
const compiler = webpack(config);
|
||||
function compilerCallback(err, stats) {
|
||||
if (err) {
|
||||
console.error(err.stack || err);
|
||||
if (err.details) console.error(err.details);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if (!watch && stats.hasErrors()) process.exitCode = 1;
|
||||
compiler.run(compilerCallback);
|
||||
next({ total: 100 });
|
||||
await new Promise((resolve, reject) => {
|
||||
compiler.run((err, stats) => {
|
||||
if (err) {
|
||||
console.error(err.stack || err);
|
||||
if (err.details) console.error(err.details);
|
||||
reject(1);
|
||||
}
|
||||
if (stats.hasErrors()) process.exitCode = 1;
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = build;
|
||||
+19
-10
@@ -9,12 +9,16 @@ process.stdin.on('data', async (input) => {
|
||||
}
|
||||
});
|
||||
|
||||
global.Hydro = {};
|
||||
|
||||
require('./lib/i18n');
|
||||
require('./utils');
|
||||
|
||||
const bus = require('./service/bus');
|
||||
const loader = require('./lib/loader');
|
||||
|
||||
async function run() {
|
||||
await loader.prepare();
|
||||
await new Promise((resolve) => {
|
||||
const h = () => {
|
||||
console.log('Database connected');
|
||||
@@ -27,16 +31,21 @@ async function run() {
|
||||
require('./service/gridfs');
|
||||
require('./service/queue');
|
||||
const server = require('./service/server');
|
||||
require('./handler/home');
|
||||
require('./handler/problem');
|
||||
require('./handler/record');
|
||||
require('./handler/judge');
|
||||
require('./handler/user');
|
||||
require('./handler/contest');
|
||||
require('./handler/training');
|
||||
require('./handler/discussion');
|
||||
require('./handler/manage');
|
||||
require('./handler/import');
|
||||
const HandlerHome = require('./handler/home');
|
||||
const HandlerProblem = require('./handler/problem');
|
||||
const HandlerRecord = require('./handler/record');
|
||||
const HandlerJudge = require('./handler/judge');
|
||||
const HandlerUser = require('./handler/user');
|
||||
const HandlerContest = require('./handler/contest');
|
||||
const HandlerTraining = require('./handler/training');
|
||||
const HandlerDiscussion = require('./handler/discussion');
|
||||
const HandlerManage = require('./handler/manage');
|
||||
const HandlerImport = require('./handler/import');
|
||||
await loader.model();
|
||||
await loader.handler();
|
||||
HandlerContest.apply();
|
||||
HandlerDiscussion.apply();
|
||||
HandlerImport.apply();
|
||||
server.start();
|
||||
}
|
||||
run().catch((e) => {
|
||||
|
||||
+1
-1
@@ -225,7 +225,7 @@ class DiscussionNotFoundError extends NotFoundError {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
global.Hydro.error = module.exports = {
|
||||
BadRequestError,
|
||||
BlacklistedError,
|
||||
ForbiddenError,
|
||||
|
||||
@@ -267,11 +267,25 @@ class ContestCreateHandler extends ContestHandler {
|
||||
}
|
||||
}
|
||||
|
||||
Route('/c', ContestListHandler);
|
||||
Route('/c/:tid', ContestDetailHandler);
|
||||
Route('/c/:tid/edit', ContestEditHandler);
|
||||
Route('/c/:tid/scoreboard', ContestScoreboardHandler);
|
||||
Route('/c/:tid/export/:ext', ContestScoreboardDownloadHandler);
|
||||
Route('/c/:tid/p/:pid', ContestProblemHandler);
|
||||
Route('/c/:tid/p/:pid/submit', ContestProblemSubmitHandler);
|
||||
Route('/contest/create', ContestCreateHandler);
|
||||
function apply() {
|
||||
Route('/c', module.exports.ContestListHandler);
|
||||
Route('/c/:tid', module.exports.ContestDetailHandler);
|
||||
Route('/c/:tid/edit', module.exports.ContestEditHandler);
|
||||
Route('/c/:tid/scoreboard', module.exports.ContestScoreboardHandler);
|
||||
Route('/c/:tid/export/:ext', module.exports.ContestScoreboardDownloadHandler);
|
||||
Route('/c/:tid/p/:pid', module.exports.ContestProblemHandler);
|
||||
Route('/c/:tid/p/:pid/submit', module.exports.ContestProblemSubmitHandler);
|
||||
Route('/contest/create', module.exports.ContestCreateHandler);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ContestListHandler,
|
||||
ContestDetailHandler,
|
||||
ContestEditHandler,
|
||||
ContestScoreboardHandler,
|
||||
ContestScoreboardDownloadHandler,
|
||||
ContestProblemHandler,
|
||||
ContestProblemSubmitHandler,
|
||||
ContestCreateHandler,
|
||||
apply,
|
||||
};
|
||||
@@ -257,11 +257,25 @@ class DiscussionEditHandler extends DiscussionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
Route('/discuss', DiscussionMainHandler);
|
||||
Route('/discuss/:did', DiscussionDetailHandler);
|
||||
Route('/discuss/:did/edit', DiscussionEditHandler);
|
||||
Route('/discuss/:did/raw', DiscussionDetailRawHandler);
|
||||
Route('/discuss/:did/:drid/raw', DiscussionReplyRawHandler);
|
||||
Route('/discuss/:did/:drid/:drrid/raw', DiscussionTailReplyRawHandler);
|
||||
Route('/discuss/:type/:docId', DiscussionNodeHandler);
|
||||
Route('/discuss/:type/:docId/create', DiscussionCreateHandler);
|
||||
function apply() {
|
||||
Route('/discuss', module.exports.DiscussionMainHandler);
|
||||
Route('/discuss/:did', module.exports.DiscussionDetailHandler);
|
||||
Route('/discuss/:did/edit', module.exports.DiscussionEditHandler);
|
||||
Route('/discuss/:did/raw', module.exports.DiscussionDetailRawHandler);
|
||||
Route('/discuss/:did/:drid/raw', module.exports.DiscussionReplyRawHandler);
|
||||
Route('/discuss/:did/:drid/:drrid/raw', module.exports.DiscussionTailReplyRawHandler);
|
||||
Route('/discuss/:type/:docId', module.exports.DiscussionNodeHandler);
|
||||
Route('/discuss/:type/:docId/create', module.exports.DiscussionCreateHandler);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DiscussionMainHandler,
|
||||
DiscussionDetailHandler,
|
||||
DiscussionEditHandler,
|
||||
DiscussionDetailRawHandler,
|
||||
DiscussionReplyRawHandler,
|
||||
DiscussionTailReplyRawHandler,
|
||||
DiscussionNodeHandler,
|
||||
DiscussionCreateHandler,
|
||||
apply,
|
||||
};
|
||||
+228
-1
@@ -1,8 +1,19 @@
|
||||
const {
|
||||
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
|
||||
NotFoundError,
|
||||
} = require('../error');
|
||||
const options = require('../options');
|
||||
const { Route, Handler } = require('../service/server');
|
||||
const md5 = require('../lib/md5');
|
||||
const contest = require('../model/contest');
|
||||
const user = require('../model/user');
|
||||
const setting = require('../model/setting');
|
||||
const token = require('../model/token');
|
||||
const training = require('../model/training');
|
||||
const { PERM_VIEW_TRAINING, PERM_VIEW_CONTEST, PERM_VIEW_DISCUSSION } = require('../permission');
|
||||
const {
|
||||
PERM_VIEW_TRAINING, PERM_VIEW_CONTEST, PERM_VIEW_DISCUSSION,
|
||||
PERM_LOGGEDIN,
|
||||
} = require('../permission');
|
||||
const { CONTESTS_ON_MAIN, TRAININGS_ON_MAIN, DISCUSSIONS_ON_MAIN } = require('../options').constants;
|
||||
|
||||
class HomeHandler extends Handler {
|
||||
@@ -57,4 +68,220 @@ class HomeHandler extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeSecurityHandler extends Handler {
|
||||
async prepare() {
|
||||
this.checkPerm(PERM_LOGGEDIN);
|
||||
}
|
||||
|
||||
async get() {
|
||||
// TODO(iceboy): pagination? or limit session count for uid?
|
||||
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,
|
||||
}));
|
||||
this.response.template = 'home_security.html';
|
||||
this.response.body = { sessions: parsed };
|
||||
}
|
||||
|
||||
async postChangePassword({ current, password, verifyPassword }) {
|
||||
if (password !== verifyPassword) throw new VerifyPasswordError();
|
||||
await user.changePassword(this.user._id, current, password);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postChangeMail({ currentPassword, mail }) {
|
||||
this.limitRate('send_mail', 3600, 30);
|
||||
this.user.checkPassword(currentPassword);
|
||||
const udoc = await user.getByMail(mail);
|
||||
if (udoc) throw new UserAlreadyExistError(mail);
|
||||
const [rid] = await token.add(
|
||||
token.TYPE_CHANGEMAIL,
|
||||
options.changemail_token_expire_seconds,
|
||||
{ uid: this.udoc._id, mail },
|
||||
);
|
||||
await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', {
|
||||
url: `/changeMail/${rid}`, uname: this.udoc.uname,
|
||||
});
|
||||
this.response.template = 'user_changemail_mail_sent.html';
|
||||
}
|
||||
|
||||
async postDeleteToken({ tokenDigest }) {
|
||||
const sessions = await token.getSessionListByUid(this.user._id);
|
||||
for (const session in sessions) {
|
||||
if (tokenDigest === md5(session._id)) {
|
||||
await token.delete(session._id, token.TYPE_SESSION);
|
||||
return this.back();
|
||||
}
|
||||
}
|
||||
throw new InvalidTokenError(tokenDigest);
|
||||
}
|
||||
|
||||
async postDeleteAllTokens() {
|
||||
await token.deleteByUid(this.user._id);
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
class HomeSettingsHandler extends Handler {
|
||||
async prepare() {
|
||||
this.checkPerm(PERM_LOGGEDIN);
|
||||
}
|
||||
|
||||
async get({ category }) {
|
||||
this.response.template = 'home_settings.html';
|
||||
if (category === 'preference') {
|
||||
this.response.body = {
|
||||
category,
|
||||
page_name: `home_${category}`,
|
||||
settings: setting.PREFERENCE_SETTINGS,
|
||||
};
|
||||
} else if (category === 'account') {
|
||||
this.response.body = {
|
||||
category,
|
||||
page_name: `home_${category}`,
|
||||
settings: setting.ACCOUNT_SETTINGS,
|
||||
};
|
||||
} else throw new NotFoundError();
|
||||
}
|
||||
|
||||
async post(args) {
|
||||
// FIXME validation
|
||||
await user.setById(this.user._id, args);
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
class UserChangemailWithCodeHandler extends Handler {
|
||||
async get({ code }) {
|
||||
const tdoc = await token.get(code, token.TYPE_CHANGEMAIL);
|
||||
if (!tdoc || tdoc.uid !== this.user._id) {
|
||||
throw new InvalidTokenError(code);
|
||||
}
|
||||
const udoc = await user.getByEmail(tdoc.mail);
|
||||
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
|
||||
// TODO(twd2): Ensure mail is unique.
|
||||
await user.set_mail(this.user._id, tdoc.mail);
|
||||
await token.delete(code, token.TYPE_CHANGEMAIL);
|
||||
this.response.body = {};
|
||||
this.response.redirect = '/home/security';
|
||||
}
|
||||
}
|
||||
|
||||
Route('/', HomeHandler);
|
||||
Route('/home/security', HomeSecurityHandler);
|
||||
Route('/home/changeMail/:code', UserChangemailWithCodeHandler);
|
||||
Route('/home/settings/:category', HomeSettingsHandler);
|
||||
|
||||
/*
|
||||
@app.route('/home/messages', 'home_messages', global_route=True)
|
||||
class HomeMessagesHandler(base.OperationHandler):
|
||||
def modify_udoc(this, udict, key):
|
||||
udoc = udict.get(key)
|
||||
if not udoc:
|
||||
return
|
||||
gravatar_url = misc.gravatar_url(udoc.get('gravatar'))
|
||||
if 'gravatar' in udoc and udoc['gravatar']:
|
||||
udict[key] = {**udoc,
|
||||
'gravatar_url': gravatar_url,
|
||||
'gravatar': ''}
|
||||
|
||||
@base.require_priv(builtin.PRIV_USER_PROFILE)
|
||||
async def get(this):
|
||||
# TODO(iceboy): projection, pagination.
|
||||
mdocs = await message.get_multi(this.user['_id']).sort([('_id', -1)]).limit(50).to_list()
|
||||
udict = await user.get_dict(
|
||||
itertools.chain.from_iterable((mdoc['sender_uid'], mdoc['sendee_uid']) for mdoc in mdocs),
|
||||
fields=user.PROJECTION_PUBLIC)
|
||||
# TODO(twd2): improve here:
|
||||
for mdoc in mdocs:
|
||||
this.modify_udoc(udict, mdoc['sender_uid'])
|
||||
this.modify_udoc(udict, mdoc['sendee_uid'])
|
||||
this.json_or_render('home_messages.html', messages=mdocs, udict=udict)
|
||||
|
||||
@base.require_priv(builtin.PRIV_USER_PROFILE)
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_send_message(this, *, uid: int, content: str):
|
||||
udoc = await user.get_by_uid(uid, user.PROJECTION_PUBLIC)
|
||||
if not udoc:
|
||||
raise error.UserNotFoundError(uid)
|
||||
mdoc = await message.add(this.user['_id'], udoc['_id'], content)
|
||||
# TODO(twd2): improve here:
|
||||
# projection
|
||||
sender_udoc = await user.get_by_uid(this.user['_id'], user.PROJECTION_PUBLIC)
|
||||
mdoc['sender_udoc'] = sender_udoc
|
||||
this.modify_udoc(mdoc, 'sender_udoc')
|
||||
mdoc['sendee_udoc'] = udoc
|
||||
this.modify_udoc(mdoc, 'sendee_udoc')
|
||||
if this.user['_id'] != uid:
|
||||
await bus.publish('message_received-' + str(uid), {'type': 'new', 'data': mdoc})
|
||||
this.json_or_redirect(this.url, mdoc=mdoc)
|
||||
|
||||
@base.require_priv(builtin.PRIV_USER_PROFILE)
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_reply_message(this, *, message_id: objectid.ObjectId, content: str):
|
||||
mdoc, reply = await message.add_reply(message_id, this.user['_id'], content)
|
||||
if not mdoc:
|
||||
return error.MessageNotFoundError(message_id)
|
||||
if mdoc['sender_uid'] != mdoc['sendee_uid']:
|
||||
if mdoc['sender_uid'] == this.user['_id']:
|
||||
other_uid = mdoc['sendee_uid']
|
||||
else:
|
||||
other_uid = mdoc['sender_uid']
|
||||
mdoc['reply'] = [reply]
|
||||
await bus.publish('message_received-' + str(other_uid), {'type': 'reply', 'data': mdoc})
|
||||
this.json_or_redirect(this.url, reply=reply)
|
||||
|
||||
@base.require_priv(builtin.PRIV_USER_PROFILE)
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_delete_message(this, *, message_id: objectid.ObjectId):
|
||||
await message.delete(message_id, this.user['_id'])
|
||||
this.back();
|
||||
|
||||
|
||||
@app.connection_route('/home/messages-conn', 'home_messages-conn', global_route=True)
|
||||
class HomeMessagesConnection(base.Connection):
|
||||
@base.require_priv(builtin.PRIV_USER_PROFILE)
|
||||
async def on_open(this):
|
||||
await super(HomeMessagesConnection, this).on_open()
|
||||
bus.subscribe(this.on_message_received, ['message_received-' + str(this.user['_id'])])
|
||||
|
||||
async def on_message_received(this, e):
|
||||
this.send(**e['value'])
|
||||
|
||||
async def on_close(this):
|
||||
bus.unsubscribe(this.on_message_received)
|
||||
|
||||
|
||||
@app.route('/home/file', 'home_file', global_route=True)
|
||||
class HomeFileHandler(base.OperationHandler):
|
||||
def file_url(this, fdoc):
|
||||
return options.cdn_prefix.rstrip('/') + \
|
||||
this.reverse_url('fs_get', domain_id=builtin.DOMAIN_ID_SYSTEM,
|
||||
secret=fdoc['metadata']['secret'])
|
||||
|
||||
@base.require_priv(builtin.PRIV_USER_PROFILE)
|
||||
async def get(this):
|
||||
ufdocs = await userfile.get_multi(owner_uid=this.user['_id']).to_list()
|
||||
fdict = await fs.get_meta_dict(ufdoc.get('file_id') for ufdoc in ufdocs)
|
||||
this.render('home_file.html', ufdocs=ufdocs, fdict=fdict)
|
||||
|
||||
@base.require_priv(builtin.PRIV_USER_PROFILE)
|
||||
@base.post_argument
|
||||
@base.require_csrf_token
|
||||
@base.sanitize
|
||||
async def post_delete(this, *, ufid: document.convert_doc_id):
|
||||
ufdoc = await userfile.get(ufid)
|
||||
if not this.own(ufdoc, priv=builtin.PRIV_DELETE_FILE_this):
|
||||
this.check_priv(builtin.PRIV_DELETE_FILE)
|
||||
result = await userfile.delete(ufdoc['doc_id'])
|
||||
if result:
|
||||
await userfile.dec_usage(this.user['_id'], ufdoc['length'])
|
||||
this.redirect(this.referer_or_main)
|
||||
*/
|
||||
+9
-50
@@ -1,57 +1,10 @@
|
||||
const assert = require('assert');
|
||||
const download = require('../lib/download');
|
||||
|
||||
const problem = require('../model/problem');
|
||||
const { Route, Handler } = require('../service/server');
|
||||
const { PERM_CREATE_PROBLEM } = require('../permission');
|
||||
const axios = require('../lib/axios');
|
||||
const { ValidationError, RemoteOnlineJudgeError } = require('../error');
|
||||
const { ValidationError } = require('../error');
|
||||
|
||||
class ProblemImportHandler extends Handler {
|
||||
async syzoj(url) {
|
||||
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i;
|
||||
assert(url.match(RE_SYZOJ), new ValidationError('url'));
|
||||
if (!url.endsWith('/')) url += '/';
|
||||
const [, host, pid] = RE_SYZOJ.exec(url);
|
||||
const res = await axios.get(`${url}export`);
|
||||
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
|
||||
assert(res.data.success, new RemoteOnlineJudgeError((res.data.error || {}).message));
|
||||
const p = res.data.obj;
|
||||
const content = [
|
||||
this.translate('problem.import.problem_description'),
|
||||
p.description,
|
||||
this.translate('problem.import.input_format'),
|
||||
p.input_format,
|
||||
this.translate('problem.import.output_format'),
|
||||
p.output_format,
|
||||
this.translate('problem.import.hint'),
|
||||
p.hint,
|
||||
this.translate('problem.import.limit_and_hint'),
|
||||
p.limit_and_hint,
|
||||
];
|
||||
if (p.have_additional_file) {
|
||||
content.push(
|
||||
this.translate('problem.import.additional_file'),
|
||||
`${url}download/additional_file`,
|
||||
);
|
||||
}
|
||||
const pdoc = {
|
||||
title: p.title,
|
||||
content: content.join(' \n'),
|
||||
owner: this.user._id,
|
||||
from: url,
|
||||
pid: `${host}_${pid}`,
|
||||
config: {
|
||||
time: p.time_limit,
|
||||
memory: p.memory_limit * 1024,
|
||||
filename: p.file_io_input_name,
|
||||
type: p.type,
|
||||
tags: p.tags,
|
||||
},
|
||||
};
|
||||
const r = await download(`${url}testdata/download`);
|
||||
return [pdoc, r];
|
||||
}
|
||||
|
||||
async prepare() {
|
||||
this.checkPerm(PERM_CREATE_PROBLEM);
|
||||
}
|
||||
@@ -82,4 +35,10 @@ class ProblemImportHandler extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
Route('/problem/import', ProblemImportHandler);
|
||||
async function apply() {
|
||||
Route('/problem/import', module.exports.ProblemImportHandler);
|
||||
}
|
||||
|
||||
global.Hydro['handler.import'] = module.exports = {
|
||||
ProblemImportHandler, apply,
|
||||
};
|
||||
+1
-1
@@ -20,4 +20,4 @@ async function post(url, data, options) {
|
||||
return res;
|
||||
}
|
||||
|
||||
module.exports = { get, post };
|
||||
global.Hydro['lib.axios'] = module.exports = { get, post };
|
||||
@@ -19,4 +19,5 @@ async function download(url, path, retry = 3) {
|
||||
}
|
||||
return r;
|
||||
}
|
||||
module.exports = download;
|
||||
|
||||
global.Hydro['lib.download'] = module.exports = download;
|
||||
@@ -0,0 +1,43 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let installed;
|
||||
function root(name) {
|
||||
return path.resolve(process.cwd(), name);
|
||||
}
|
||||
function exist(name) {
|
||||
try {
|
||||
fs.statSync(root(name));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const superRequire = (name) => (__non_webpack_require__
|
||||
? __non_webpack_require__(root(name))
|
||||
: require(root(name)));
|
||||
|
||||
async function prepare() {
|
||||
installed = fs.readdirSync(root('.build/module'));
|
||||
}
|
||||
|
||||
async function handler() {
|
||||
for (const i of installed) {
|
||||
if (exist(`.build/module/${i}/handler.js`)) {
|
||||
superRequire(`.build/module/${i}/handler.js`);
|
||||
}
|
||||
console.log(`Handler init: ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function model() {
|
||||
for (const i of installed) {
|
||||
if (exist(`.build/module/${i}/model.js`)) {
|
||||
superRequire(`.build/module/${i}/model.js`);
|
||||
}
|
||||
console.log(`Model init: ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
global.Hydro['lib.loader'] = module.exports = { prepare, handler, model };
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
module.exports = function* ranked(diter, equ = (a, b) => a === b) {
|
||||
function* ranked(diter, equ = (a, b) => a === b) {
|
||||
let last = null;
|
||||
let r = 0;
|
||||
let count = 0;
|
||||
@@ -8,4 +8,6 @@ module.exports = function* ranked(diter, equ = (a, b) => a === b) {
|
||||
last = doc;
|
||||
yield [r, doc];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
global.Hydro['lib.rank'] = module.exports = ranked;
|
||||
@@ -11,11 +11,7 @@ const db = require('../service/db');
|
||||
const coll = db.collection('contest');
|
||||
const collStatus = db.collection('contest.status');
|
||||
|
||||
const RULES = {
|
||||
homework: require('../module/contest/homework'),
|
||||
oi: require('../module/contest/oi'),
|
||||
acm: require('../module/contest/acm'),
|
||||
};
|
||||
const RULES = {};
|
||||
|
||||
/**
|
||||
* @typedef {import('bson').ObjectID} ObjectID
|
||||
@@ -197,6 +193,8 @@ module.exports = {
|
||||
: 'finished'),
|
||||
};
|
||||
|
||||
global.Hydro['model.contest'] = module.exports;
|
||||
|
||||
/*
|
||||
|
||||
def _get_status_journal(tsdoc):
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
const builtin = require('./builtin');
|
||||
const options = require('../options');
|
||||
const i18n = require('../lib/i18n');
|
||||
|
||||
const Setting = (
|
||||
family, key, range = null,
|
||||
value = null, ui = 'text', name = '',
|
||||
desc = '', imageClass = '',
|
||||
) => ({
|
||||
family, key, range, value, ui, name, desc, imageClass,
|
||||
});
|
||||
|
||||
const PREFERENCE_SETTINGS = [
|
||||
Setting('setting_display', 'view_lang', i18n,
|
||||
options.default_locale, 'select', 'UI Language'),
|
||||
Setting('setting_display', 'timezone', [], // TODO(masnn) timezone
|
||||
'Asia/Shanghai', 'select', 'Timezone'),
|
||||
Setting('setting_usage', 'code_lang', builtin.LANG_TEXTS,
|
||||
null, 'select', 'Default Code Language'),
|
||||
Setting('setting_usage', 'code_template', null,
|
||||
null, 'textarea', 'Default Code Template',
|
||||
'If left blank, the built-in template of the corresponding language will be used.'),
|
||||
];
|
||||
|
||||
const ACCOUNT_SETTINGS = [
|
||||
Setting('setting_info', 'gravatar', null,
|
||||
null, null, 'Gravatar Email',
|
||||
'We use <a href="https://en.gravatar.com/" target="_blank">Gravatar</a> to present your avatar icon.'),
|
||||
Setting('setting_info', 'qq', null,
|
||||
null, null, 'QQ'),
|
||||
Setting('setting_info', 'wechat', null,
|
||||
null, null, 'WeChat'),
|
||||
Setting('setting_info', 'gender', builtin.USER_GENDER_RANGE,
|
||||
null, 'select', 'Gender'),
|
||||
Setting('setting_info', 'bio', null,
|
||||
null, 'markdown', 'Bio'),
|
||||
Setting('setting_privacy', 'show_mail', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'Email Visibility'),
|
||||
Setting('setting_privacy', 'show_qq', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'QQ Visibility'),
|
||||
Setting('setting_privacy', 'show_wechat', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'WeChat Visibility'),
|
||||
Setting('setting_privacy', 'show_gender', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'Gender Visibility'),
|
||||
Setting('setting_privacy', 'show_bio', builtin.PRIVACY_RANGE,
|
||||
null, 'select', 'Bio Visibility'),
|
||||
Setting('setting_customize', 'background_img', builtin.BACKGROUND_RANGE,
|
||||
null, 'image_radio', 'Profile Background Image',
|
||||
'Choose the background image in your profile page.',
|
||||
'user-profile-bg--thumbnail-{0}'),
|
||||
];
|
||||
|
||||
const SETTINGS = [...PREFERENCE_SETTINGS, ...ACCOUNT_SETTINGS];
|
||||
const SETTINGS_BY_KEY = {};
|
||||
|
||||
for (const setting in SETTINGS) SETTINGS_BY_KEY[setting.key] = setting;
|
||||
|
||||
module.exports = {
|
||||
PREFERENCE_SETTINGS, ACCOUNT_SETTINGS, SETTINGS, SETTINGS_BY_KEY,
|
||||
};
|
||||
@@ -7,6 +7,8 @@ module.exports = {
|
||||
TYPE_SESSION: 0,
|
||||
TYPE_CSRF_TOKEN: 1,
|
||||
TYPE_REGISTER: 2,
|
||||
TYPE_CHANGEMAIL: 3,
|
||||
|
||||
/**
|
||||
* Add a token.
|
||||
* @param {number} tokenType type of the token.
|
||||
@@ -71,7 +73,13 @@ module.exports = {
|
||||
const result = await coll.deleteOne({ _id: tokenId, tokenType });
|
||||
return !!result.deletedCount;
|
||||
},
|
||||
deleteByUid(uid) {
|
||||
return coll.deleteMany({ uid });
|
||||
},
|
||||
getMostRecentSessionByUid(uid) {
|
||||
return coll.findOne({ uid, token_type: this.TYPE_SESSION }, { sort: { updateAt: -1 } });
|
||||
},
|
||||
getSessionListByUid(uid) {
|
||||
return coll.find({ uid, token_type: this.TYPE_SESSION }).sort('updateAt', -1).toArray();
|
||||
},
|
||||
};
|
||||
@@ -1,111 +0,0 @@
|
||||
module.exports = {
|
||||
TEXT: 'ACM/ICPC',
|
||||
check: () => { },
|
||||
showScoreboard: () => true,
|
||||
showRecord: (tdoc, now) => now > tdoc.endAt,
|
||||
stat: (tdoc, journal) => {
|
||||
const naccept = {};
|
||||
const effective = {};
|
||||
const detail = [];
|
||||
let accept = 0;
|
||||
let time = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid)
|
||||
&& !(effective.includes(j.pid) && effective[j.pid].accept)) {
|
||||
effective[j.pid] = j;
|
||||
}
|
||||
if (!j.accept) naccept[j.pid]++;
|
||||
}
|
||||
function _time(jdoc) {
|
||||
const real = jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000);
|
||||
const penalty = 20 * 60 * naccept[jdoc.pid];
|
||||
return real + penalty;
|
||||
}
|
||||
for (const j of effective) detail.push({ ...j, naccept: naccept[j.pid], time: _time(j) });
|
||||
for (const d of detail) {
|
||||
accept += d.accept;
|
||||
if (d.accept) time += d.time;
|
||||
}
|
||||
return { accept, time, detail };
|
||||
},
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'solved_problems', value: _('Solved Problems') },
|
||||
];
|
||||
if (isExport) {
|
||||
columns.push({ type: 'total_time', value: _('Total Time (Seconds)') });
|
||||
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
||||
}
|
||||
for (const i in tdoc.pids) {
|
||||
if (isExport) {
|
||||
columns.push({
|
||||
type: 'problem_flag',
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
||||
});
|
||||
columns.push({
|
||||
type: 'problem_time',
|
||||
value: '#{0} {1}'.format(i + 1, _('Time (Seconds)')),
|
||||
});
|
||||
columns.push({
|
||||
type: 'problem_time_str',
|
||||
value: '#{0} {1}'.format(i + 1, _('Time')),
|
||||
});
|
||||
} else {
|
||||
columns.push({
|
||||
type: 'problem_detail',
|
||||
value: '#{0}'.format(i + 1),
|
||||
raw: pdict[tdoc.pids[i]],
|
||||
});
|
||||
}
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||
const tsddict = {};
|
||||
if (tdoc.detail) { for (const item of tsdoc.detail) tsddict[item.pid] = item; }
|
||||
const row = [];
|
||||
row.push(
|
||||
{ type: 'string', value: rank },
|
||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] },
|
||||
{ type: 'string', value: tsdoc.accept || 0 },
|
||||
);
|
||||
if (isExport) {
|
||||
row.push(
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
);
|
||||
}
|
||||
for (const pid of tdoc.pids) {
|
||||
let rdoc;
|
||||
let colAccepted;
|
||||
let colTime;
|
||||
let colTimeStr;
|
||||
if ((tsddict[pid] || {}).accept) {
|
||||
rdoc = tsddict[pid].rid;
|
||||
colAccepted = _('Accepted');
|
||||
colTime = tsddict[pid].time;
|
||||
colTimeStr = colTime;
|
||||
} else {
|
||||
rdoc = null;
|
||||
colAccepted = '-';
|
||||
colTime = '-';
|
||||
colTimeStr = '-';
|
||||
}
|
||||
if (isExport) {
|
||||
row.push({ type: 'string', value: colAccepted });
|
||||
row.push({ type: 'string', value: colTime });
|
||||
row.push({ type: 'string', value: colTimeStr });
|
||||
} else {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: '{0}\n{1}'.format(colAccepted, colTimeStr),
|
||||
raw: rdoc,
|
||||
});
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
module.exports = {
|
||||
TEXT: 'Assignment',
|
||||
check() {},
|
||||
stat(tdoc, journal) {
|
||||
const effective = {};
|
||||
const detail = [];
|
||||
let score = 0;
|
||||
let time = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid)
|
||||
&& !(effective.includes(j.pid) && effective[j.pid].accept)) {
|
||||
effective[j.pid] = j;
|
||||
}
|
||||
}
|
||||
const _time = (jdoc) => jdoc.rid.generationTime - Math.floor(tdoc.beginAt / 1000);
|
||||
for (const j in effective) {
|
||||
detail.push({
|
||||
...effective[j],
|
||||
time: _time(effective[j]),
|
||||
});
|
||||
}
|
||||
for (const d of detail) {
|
||||
score += d.score;
|
||||
time += d.time;
|
||||
}
|
||||
return {
|
||||
score, time, detail,
|
||||
};
|
||||
},
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'display_name', value: _('Display Name') },
|
||||
{ type: 'total_score', value: _('Score') },
|
||||
];
|
||||
if (isExport) {
|
||||
columns.push(
|
||||
{ type: 'total_original_score', value: _('Original Score') },
|
||||
{ type: 'total_time', value: _('Total Time (Seconds)') },
|
||||
);
|
||||
}
|
||||
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
||||
for (const i in tdoc.pids) {
|
||||
if (isExport) {
|
||||
columns.push(
|
||||
{ type: 'problem_score', value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title) },
|
||||
{ type: 'problem_original_score', value: '#{0} {1}'.format(i + 1, _('Original Score')) },
|
||||
{ type: 'problem_time', value: '#{0} {1}'.format(i + 1, _('Time (Seconds)')) },
|
||||
{ type: 'problem_time_str', value: '#{0} {1}'.format(i + 1, _('Time')) },
|
||||
);
|
||||
} else columns.push({ type: 'problem_detail', value: '#{0}'.format(i + 1), raw: pdict[tdoc.pids[i]] });
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] in rankedTsdocs) {
|
||||
const tsddict = {};
|
||||
if (tsdoc.detail) { for (const item of tsdoc.detail) tsddict[item.pid] = item; }
|
||||
const row = [
|
||||
{ type: 'string', value: rank },
|
||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] },
|
||||
{ type: 'string', value: tsdoc.penalty_score || 0 },
|
||||
];
|
||||
if (isExport) {
|
||||
row.push(
|
||||
{ type: 'string', value: tsdoc.score || 0 },
|
||||
{ type: 'string', value: tsdoc.time || 0.0 },
|
||||
);
|
||||
}
|
||||
row.push({ type: 'string', value: tsdoc.time || 0 });
|
||||
for (const pid of tdoc.pids) {
|
||||
const rdoc = (tsddict[pid] || {}).rid || null;
|
||||
const colScore = (tsddict[pid] || {}).penalty_score || '-';
|
||||
const colOriginalScore = (tsddict[pid] || {}).score || '-';
|
||||
const colTime = (tsddict[pid] || {}).time || '-';
|
||||
const colTimeStr = colTime !== '-' ? colTime : '-';
|
||||
if (isExport) {
|
||||
row.push(
|
||||
{ type: 'string', value: colScore },
|
||||
{ type: 'string', value: colOriginalScore },
|
||||
{ type: 'string', value: colTime },
|
||||
{ type: 'string', value: colTimeStr },
|
||||
);
|
||||
} else {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: '{0} / {1}\n{2}'.format(colScore, colOriginalScore, colTimeStr),
|
||||
raw: rdoc,
|
||||
});
|
||||
}
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
};
|
||||
@@ -1,63 +0,0 @@
|
||||
const ranked = require('../../lib/rank');
|
||||
|
||||
module.exports = {
|
||||
TEXT: 'OI',
|
||||
check: () => { },
|
||||
stat: (tdoc, journal) => {
|
||||
const detail = {};
|
||||
let score = 0;
|
||||
for (const j in journal) {
|
||||
if (tdoc.pids.includes(j.pid)) {
|
||||
detail[j.pid] = j;
|
||||
score += j.score;
|
||||
}
|
||||
}
|
||||
return { score, detail };
|
||||
},
|
||||
showScoreboard(tdoc, now) {
|
||||
return now > tdoc.endAt;
|
||||
},
|
||||
showRecord(tdoc, now) {
|
||||
return now > tdoc.endAt;
|
||||
},
|
||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||
const columns = [
|
||||
{ type: 'rank', value: _('Rank') },
|
||||
{ type: 'user', value: _('User') },
|
||||
{ type: 'total_score', value: _('Total Score') },
|
||||
];
|
||||
for (const i in tdoc.pids) {
|
||||
if (isExport) {
|
||||
columns.push({
|
||||
type: 'problem_score',
|
||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
||||
});
|
||||
} else {
|
||||
columns.push({
|
||||
type: 'problem_detail',
|
||||
value: '#{0}'.format(i + 1),
|
||||
raw: pdict[tdoc.pids[i]],
|
||||
});
|
||||
}
|
||||
}
|
||||
const rows = [columns];
|
||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||
const tsddict = {};
|
||||
if (tsdoc.journal) { for (const item of tsdoc.journal) tsddict[item.pid] = item; }
|
||||
const row = [];
|
||||
row.push({ type: 'string', value: rank });
|
||||
row.push({ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] });
|
||||
row.push({ type: 'string', value: tsdoc.score || 0 });
|
||||
for (const pid of tdoc.pids) {
|
||||
row.push({
|
||||
type: 'record',
|
||||
value: (tsddict[pid] || {}).score || '-',
|
||||
raw: (tsddict[pid] || {}).rid || null,
|
||||
});
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
rank: (tdocs) => ranked(tdocs, (a, b) => a.score === b.score),
|
||||
};
|
||||
+1
-1
@@ -13,7 +13,7 @@ let options = {
|
||||
password: '',
|
||||
},
|
||||
template: {
|
||||
path: path.join(__dirname, '..', 'templates'),
|
||||
path: path.join(process.cwd(), 'templates'),
|
||||
},
|
||||
listen: {
|
||||
host: '127.0.0.1',
|
||||
|
||||
@@ -240,7 +240,9 @@ class Handler {
|
||||
console.error(error.message, error.params);
|
||||
console.error(error.stack);
|
||||
this.response.template = error instanceof UserFacingError ? 'error.html' : 'bsod.html';
|
||||
this.response.body = { error: { message: error.message, params: error.params, stack: error.stack } };
|
||||
this.response.body = {
|
||||
error: { message: error.message, params: error.params, stack: error.stack },
|
||||
};
|
||||
await this.___cleanup().catch(() => { });
|
||||
}
|
||||
}
|
||||
|
||||
Whitespace-only changes.
+1
-1
@@ -31,7 +31,7 @@
|
||||
"scripts": {
|
||||
"build": "node build/index.js",
|
||||
"pack": "pkg .",
|
||||
"lint": "eslint hydro --fix"
|
||||
"lint": "eslint hydro module --fix"
|
||||
},
|
||||
"pkg": {
|
||||
"scripts": [
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
{% macro render_sidebar() %}
|
||||
<div class="section side">
|
||||
<ol class="menu">
|
||||
{{ sidemenu.render_item('account--circle', 'user_detail', label='My Profile', uid=handler.user['_id']) }}
|
||||
{{ sidemenu.render_item('comment--multiple', 'home_messages') }}
|
||||
{{ sidemenu.render_item('account--circle', 'user_detail', '/user/' + handler.user._id, label='My Profile', uid=handler.user._id) }}
|
||||
{{ sidemenu.render_item('comment--multiple', 'home_messages', '/home/messages') }}
|
||||
<li class="menu__seperator"></li>
|
||||
{{ sidemenu.render_item('wrench', 'home_account') }}
|
||||
{{ sidemenu.render_item('web', 'home_domain_account', label='@ '+handler.domain['name']) }}
|
||||
{{ sidemenu.render_item('sliders', 'home_preference') }}
|
||||
{{ sidemenu.render_item('security', 'home_security') }}
|
||||
{{ sidemenu.render_item('wrench', 'home_account', '/home/settings/account') }}
|
||||
{{ sidemenu.render_item('sliders', 'home_preference', '/home/settings/preference') }}
|
||||
{{ sidemenu.render_item('security', 'home_security', '/home/security') }}
|
||||
<li class="menu__seperator"></li>
|
||||
{{ sidemenu.render_item('file', 'home_file') }}
|
||||
{{ sidemenu.render_item('web', 'home_domain') }}
|
||||
{{ sidemenu.render_item('file', 'home_file', '/home/file') }}
|
||||
</ol>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,3 +1,4 @@
|
||||
{% set page_name = "home_security" %}
|
||||
{% extends "layout/home_base.html" %}
|
||||
{% block home_content %}
|
||||
<div data-heading-extract-to="#menu-item-home_security">
|
||||
@@ -9,9 +10,28 @@
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<form method="post">
|
||||
{{ form.form_text(type='password', label='Current Password', columns=10, name='current_password', required=true) }}
|
||||
{{ form.form_text(type='text', label='Current Email', columns=10, name='current_email', value=handler.user['mail'], disabled=true) }}
|
||||
{{ form.form_text(type='text', label='New Email', columns=10, name='mail', required=true) }}
|
||||
{{ form.form_text({
|
||||
type:'password',
|
||||
label:'Current Password',
|
||||
columns:10,
|
||||
name:'password',
|
||||
required:true
|
||||
}) }}
|
||||
{{ form.form_text({
|
||||
type:'text',
|
||||
label:'Current Email',
|
||||
columns:10,
|
||||
name:'currentEmail',
|
||||
value:this.user.email,
|
||||
disabled:true
|
||||
}) }}
|
||||
{{ form.form_text({
|
||||
type:'text',
|
||||
label:'New Email',
|
||||
columns:10,
|
||||
name:'mail',
|
||||
required:true
|
||||
}) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="hidden" name="operation" value="change_mail">
|
||||
@@ -28,9 +48,27 @@
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<form method="post">
|
||||
{{ form.form_text(type='password', label='Current Password', columns=10, name='current_password', required=true) }}
|
||||
{{ form.form_text(type='password', label='New Password', columns=10, name='new_password', required=true) }}
|
||||
{{ form.form_text(type='password', label='Repeat Password', columns=10, name='verify_password', required=true) }}
|
||||
{{ form.form_text({
|
||||
type:'password',
|
||||
label:'Current Password',
|
||||
columns:10,
|
||||
name:'current',
|
||||
required:true
|
||||
}) }}
|
||||
{{ form.form_text({
|
||||
type:'password',
|
||||
label:'New Password',
|
||||
columns:10,
|
||||
name:'password',
|
||||
required:true
|
||||
}) }}
|
||||
{{ form.form_text({
|
||||
type:'password',
|
||||
label:'Repeat Password',
|
||||
columns:10,
|
||||
name:'verifyPassword',
|
||||
required:true
|
||||
}) }}
|
||||
<div class="row"><div class="columns">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="hidden" name="operation" value="change_password">
|
||||
@@ -53,25 +91,23 @@
|
||||
<li class="sessionlist__item">
|
||||
<div class="media">
|
||||
<div class="media__left medium">
|
||||
<span class="sessionlist__icon icon icon-platform--{{ session['update_ua']['icon'] }}"></span>
|
||||
<span class="sessionlist__icon icon icon-platform--{{ session.updateUa.icon }}"></span>
|
||||
</div>
|
||||
<div class="media__body medium typo">
|
||||
<p>{{ _('Last Update At') }}: {{ datetime_span(session['update_at']) }}</p>
|
||||
<p>{{ _('Location') }}: {{ _(session['update_geoip']) }} ({{ session['update_ip'] }})</p>
|
||||
<p>{{ _('Operating System') }}: {{ session['update_ua']['os'] }}</p>
|
||||
<p>{{ _('Browser') }}: {{ session['update_ua']['browser'] }}</p>
|
||||
<p>{{ _('Last Update At') }}: {{ datetime_span(session.updateAt)|safe }}</p>
|
||||
<p>{{ _('Location') }}: {{ _(session.updateGeoip) }} ({{ session.updateIp }})</p>
|
||||
<p>{{ _('Operating System') }}: {{ session.updateUa.os }}</p>
|
||||
<p>{{ _('Browser') }}: {{ session.updateUa.browser }}</p>
|
||||
<!-- {{ _('User-Agent') }}: {{ session['update_ua']['str'] }} -->
|
||||
<p>{{ _('Type') }}: {{ _(vj4.handler.home.TOKEN_TYPE_TEXTS[session['token_type']]) }}</p>
|
||||
{% if session['is_current'] %}
|
||||
{% if session.isCurrent %}
|
||||
<p class="sessionlist__current-session"><span class="icon icon-check"></span> {{ _('This is the current session') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not session['is_current'] %}
|
||||
{% if not session.isCurrent %}
|
||||
<div class="media__right medium">
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="delete_token">
|
||||
<input type="hidden" name="token_type" value="{{ session['token_type'] }}">
|
||||
<input type="hidden" name="token_digest" value="{{ session['token_digest'] }}">
|
||||
<input type="hidden" name="token_digest" value="{{ session._id }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="submit" value="{{ _('Logout This Session') }}" class="rounded button">
|
||||
</form>
|
||||
|
||||
@@ -9,53 +9,53 @@
|
||||
<div class="section__body">
|
||||
{% for setting in family_settings %}
|
||||
{% if setting.ui == 'text' or setting.ui == 'password' %}
|
||||
{{ form.form_text(
|
||||
type=setting.ui,
|
||||
label=setting.name,
|
||||
help_text=setting.desc,
|
||||
name=setting.key,
|
||||
value=handler.get_setting(setting.key))
|
||||
}}
|
||||
{{ form.form_text({
|
||||
type:setting.ui,
|
||||
label:setting.name,
|
||||
help_text:setting.desc,
|
||||
name:setting.key,
|
||||
value:handler.user[setting.key] or setting.value
|
||||
}) }}
|
||||
{% elif setting.ui == 'select' %}
|
||||
{{ form.form_select(
|
||||
options=setting.range.items(),
|
||||
label=setting.name,
|
||||
help_text=setting.desc,
|
||||
name=setting.key,
|
||||
value=handler.get_setting(setting.key))
|
||||
}}
|
||||
{{ form.form_select({
|
||||
options:setting.range,
|
||||
label:setting.name,
|
||||
help_text:setting.desc,
|
||||
name:setting.key,
|
||||
value:handler.user[setting.key] or setting.value
|
||||
}) }}
|
||||
{% elif setting.ui == 'radio' %}
|
||||
{{ form.form_radio(
|
||||
options=setting.range.items(),
|
||||
label=setting.name,
|
||||
help_text=setting.desc,
|
||||
name=setting.key,
|
||||
value=handler.get_setting(setting.key))
|
||||
}}
|
||||
{{ form.form_radio({
|
||||
options:setting.range,
|
||||
label:setting.name,
|
||||
help_text:setting.desc,
|
||||
name:setting.key,
|
||||
value:handler.user[setting.key] or setting.value
|
||||
}) }}
|
||||
{% elif setting.ui == 'image_radio' %}
|
||||
{{ form.form_image_radio(
|
||||
options=setting.range.items(),
|
||||
image_class=setting.image_class,
|
||||
label=setting.name,
|
||||
help_text=setting.desc,
|
||||
name=setting.key,
|
||||
value=handler.get_setting(setting.key))
|
||||
}}
|
||||
{{ form.form_image_radio({
|
||||
options:setting.range,
|
||||
image_class:setting.image_class,
|
||||
label:setting.name,
|
||||
help_text:setting.desc,
|
||||
name:setting.key,
|
||||
value:handler.user[setting.key] or setting.value
|
||||
}) }}
|
||||
{% elif setting.ui == 'textarea' %}
|
||||
{{ form.form_textarea(
|
||||
label=setting.name,
|
||||
help_text=setting.desc,
|
||||
name=setting.key,
|
||||
value=handler.get_setting(setting.key))
|
||||
}}
|
||||
{{ form.form_textarea({
|
||||
label:setting.name,
|
||||
help_text:setting.desc,
|
||||
name:setting.key,
|
||||
value:handler.user[setting.key] or setting.value
|
||||
}) }}
|
||||
{% elif setting.ui == 'markdown' %}
|
||||
{{ form.form_textarea(
|
||||
label=setting.name,
|
||||
help_text=setting.desc,
|
||||
name=setting.key,
|
||||
value=handler.get_setting(setting.key),
|
||||
markdown=true)
|
||||
}}
|
||||
{{ form.form_textarea({
|
||||
label:setting.name,
|
||||
help_text:setting.desc,
|
||||
name:setting.key,
|
||||
value:handler.user[setting.key] or setting.value,
|
||||
markdown:true
|
||||
}) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="dialog--signin__bg">
|
||||
<div class="dialog--signin__side">
|
||||
<h1>{{ _("Don\'t have an account?") }}</h1>
|
||||
<p>{{ _('By signing up a Vijos universal account, you can submit code and join discussions in all online judging services provided by us.') }}</p>
|
||||
<p>{{ _('By signing up a Hydro universal account, you can submit code and join discussions in all online judging services provided by us.') }}</p>
|
||||
<div><a href="/register" class="inverse rounded button">{{ _('Sign Up Now') }}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -13,7 +13,7 @@
|
||||
<a name="dialog--signin__close" href="javascript:;">{{ _('CLOSE') }}</a>
|
||||
</div>
|
||||
<h1 class="dialog--signin__title">{{ _('SIGN IN') }}</h1>
|
||||
<p class="dialog--signin__note">{{ _('Using your Vijos universal account') }}</p>
|
||||
<p class="dialog--signin__note">{{ _('Using your Hydro universal account') }}</p>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="material textbox">
|
||||
{{ _('Username') }}
|
||||
|
||||
@@ -7,7 +7,6 @@ import FriendlyErrorsPlugin from 'friendly-errors-webpack-plugin';
|
||||
import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin';
|
||||
import ExtractCssPlugin from 'mini-css-extract-plugin';
|
||||
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import UglifyJsPlugin from 'uglifyjs-webpack-plugin';
|
||||
import StaticManifestPlugin from '../plugins/webpackStaticManifestPlugin';
|
||||
import mapWebpackUrlPrefix from '../utils/mapWebpackUrlPrefix';
|
||||
import root from '../utils/root';
|
||||
|
||||
Reference in new issue
Block a user