mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
优化安装流程
This commit is contained in:
1 parent
63d55cedf4
commit
508f5644c3
30 files changed
+279
-139
No files matched your search
+1
-3
@@ -1,7 +1,5 @@
|
||||
FROM mhart/alpine-node:slim-10
|
||||
WORKDIR /app
|
||||
ADD .uibuild .build /app/
|
||||
RUN mkdir /config
|
||||
VOLUME [ "/config" ]
|
||||
ADD .build /app
|
||||
EXPOSE 8888
|
||||
CMD ["node", ".build/development.js"]
|
||||
+2
-5
@@ -56,14 +56,11 @@ async function build(type) {
|
||||
rmdir(root(`.build/module/${f}`));
|
||||
}
|
||||
}
|
||||
const modules = [
|
||||
'builtin', 'contest-rule-homework', 'judger', 'migrate-vijos', 'module-pastebin',
|
||||
'problem-import-syzoj', 'wiki',
|
||||
];
|
||||
const modules = await fsp.readdir(root('.build/module'));
|
||||
const j = {};
|
||||
for (const m of modules) {
|
||||
try {
|
||||
const d = await fsp.readFile(root(`.build/module/${m}.hydro`));
|
||||
const d = await fsp.readFile(root(`.build/module/${m}`));
|
||||
j[m] = d.toString('base64');
|
||||
} catch (e) {
|
||||
console.error(`Module pack failed: ${m}`);
|
||||
|
||||
+2
-2
@@ -163,9 +163,9 @@ class RoleAlreadyExistError extends ForbiddenError {
|
||||
}
|
||||
|
||||
class ProblemNotFoundError extends NotFoundError {
|
||||
constructor(pid) {
|
||||
constructor(domainId, pid) {
|
||||
super('ProblemNotFoundError');
|
||||
this.params = [pid];
|
||||
this.params = [domainId, pid];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-22
@@ -1,7 +1,9 @@
|
||||
const { ObjectID } = require('bson');
|
||||
const paginate = require('../lib/paginate');
|
||||
const system = require('../model/system');
|
||||
const user = require('../model/user');
|
||||
const discussion = require('../model/discussion');
|
||||
const document = require('../model/document');
|
||||
const { Route, Handler } = require('../service/server');
|
||||
const { DiscussionNotFoundError, DocumentNotFoundError } = require('../error');
|
||||
const {
|
||||
@@ -10,17 +12,24 @@ const {
|
||||
PERM_LOGGEDIN, PERM_CREATE_DISCUSSION, PERM_REPLY_DISCUSSION,
|
||||
} = require('../permission');
|
||||
|
||||
const typeMapper = {
|
||||
problem: document.TYPE_PROBLEM,
|
||||
contest: document.TYPE_CONTEST,
|
||||
node: document.TYPE_DISCUSSION_NODE,
|
||||
training: document.TYPE_TRAINING,
|
||||
homework: document.TYPE_HOMEWORK,
|
||||
};
|
||||
|
||||
class DiscussionHandler extends Handler {
|
||||
async _prepare({
|
||||
domainId, type = 'node', docId, name, did, drid, drrid,
|
||||
domainId, type, name, did, drid, drrid,
|
||||
}) {
|
||||
this.checkPerm(PERM_VIEW_DISCUSSION);
|
||||
docId = docId || name;
|
||||
if (did) {
|
||||
this.ddoc = await discussion.get(domainId, did);
|
||||
if (!this.ddoc) throw new DiscussionNotFoundError(did);
|
||||
type = this.ddoc.parentType;
|
||||
docId = this.ddoc.parentId;
|
||||
type = discussion.typeDisplay[this.ddoc.parentType];
|
||||
name = this.ddoc.parentId;
|
||||
if (drid) {
|
||||
this.drdoc = await discussion.getReply(domainId, drid, did);
|
||||
if (!this.drdoc) throw new DiscussionNotFoundError(drid);
|
||||
@@ -37,7 +46,11 @@ class DiscussionHandler extends Handler {
|
||||
// TODO(twd2): do more visibility check eg. contest
|
||||
// TODO(twd2): exclude problem/contest discussions?
|
||||
// TODO(iceboy): continuation based pagination.
|
||||
this.vnode = await discussion.getVnode(domainId, { parentType: type, parentId: docId }, this);
|
||||
if (ObjectID.isValid(name)) name = new ObjectID(name);
|
||||
this.vnode = await discussion.getVnode(domainId, {
|
||||
parentType: typeMapper[type],
|
||||
parentId: name,
|
||||
}, this);
|
||||
if (this.ddoc) {
|
||||
this.ddoc.parentType = this.ddoc.parentType || this.vnode.type;
|
||||
this.ddoc.parentId = this.ddoc.parentId || this.vnode.id;
|
||||
@@ -45,7 +58,7 @@ class DiscussionHandler extends Handler {
|
||||
}
|
||||
}
|
||||
|
||||
class DiscussionMainHandler extends DiscussionHandler {
|
||||
class DiscussionMainHandler extends Handler {
|
||||
async get({ domainId, page = 1 }) {
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
discussion.getMulti(domainId),
|
||||
@@ -57,20 +70,20 @@ class DiscussionMainHandler extends DiscussionHandler {
|
||||
['Hydro', '/'],
|
||||
['discussion_main', null],
|
||||
];
|
||||
const vndict = await discussion.getListVnodes(domainId, ddocs, this);
|
||||
this.response.template = 'discussion_main_or_node.html';
|
||||
this.response.body = {
|
||||
ddocs, dpcount, udict, page, page_name: 'discussion_main', vnode: {}, path,
|
||||
ddocs, dpcount, udict, page, page_name: 'discussion_main', vndict, vnode: {}, path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class DiscussionNodeHandler extends DiscussionHandler {
|
||||
async get({
|
||||
domainId, type = 'node', docId, name, page = 1,
|
||||
domainId, type, name, page = 1,
|
||||
}) {
|
||||
docId = docId || name;
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
discussion.getMulti(domainId, { parentType: type, parentId: docId }),
|
||||
discussion.getMulti(domainId, { parentType: typeMapper[type], parentId: name }),
|
||||
page,
|
||||
await system.get('DISCUSSION_PER_PAGE'),
|
||||
);
|
||||
@@ -80,6 +93,7 @@ class DiscussionNodeHandler extends DiscussionHandler {
|
||||
['discussion_main', '/discuss'],
|
||||
[this.vnode.title, null, true],
|
||||
];
|
||||
const vndict = { [typeMapper[type]]: { [name]: this.vnode } };
|
||||
this.response.template = 'discussion_main_or_node.html';
|
||||
this.response.body = {
|
||||
ddocs,
|
||||
@@ -87,6 +101,7 @@ class DiscussionNodeHandler extends DiscussionHandler {
|
||||
udict,
|
||||
path,
|
||||
page,
|
||||
vndict,
|
||||
vnode: this.vnode,
|
||||
page_name: 'discussion_node',
|
||||
};
|
||||
@@ -99,12 +114,11 @@ class DiscussionCreateHandler extends DiscussionHandler {
|
||||
this.checkPerm(PERM_CREATE_DISCUSSION);
|
||||
}
|
||||
|
||||
async get({ type = 'node', docId, name }) {
|
||||
docId = docId || name;
|
||||
async get({ type, name }) {
|
||||
const path = [
|
||||
['Hydro', '/'],
|
||||
['discussion_main', '/discuss'],
|
||||
[this.vnode.title, `/discuss/${type}/${docId}`, true],
|
||||
[this.vnode.title, `/discuss/${type}/${name}`, true],
|
||||
['discussion_create', null],
|
||||
];
|
||||
this.response.template = 'discussion_create.html';
|
||||
@@ -112,13 +126,13 @@ class DiscussionCreateHandler extends DiscussionHandler {
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, type = 'node', docId, name, title, content, highlight,
|
||||
domainId, type, name, title, content, highlight,
|
||||
}) {
|
||||
docId = docId || name;
|
||||
this.limitRate('add_discussion', 3600, 30);
|
||||
if (ObjectID.isValid(name)) name = new ObjectID(name);
|
||||
if (highlight) this.checkPerm(PERM_HIGHLIGHT_DISCUSSION);
|
||||
const did = await discussion.add(
|
||||
domainId, type, docId, this.user._id,
|
||||
domainId, typeMapper[type], name, this.user._id,
|
||||
title, content, this.request.ip, highlight,
|
||||
);
|
||||
this.response.body = { did };
|
||||
@@ -136,7 +150,7 @@ class DiscussionDetailHandler extends DiscussionHandler {
|
||||
page,
|
||||
await system.get('REPLY_PER_PAGE'),
|
||||
);
|
||||
const uids = drdocs.map(domainId, (drdoc) => drdoc.owner);
|
||||
const uids = drdocs.map((drdoc) => drdoc.owner);
|
||||
uids.push(this.ddoc.owner);
|
||||
for (const drdoc of drdocs) {
|
||||
if (drdoc.reply) {
|
||||
@@ -149,7 +163,7 @@ class DiscussionDetailHandler extends DiscussionHandler {
|
||||
const path = [
|
||||
['Hydro', '/'],
|
||||
['discussion_main', '/discuss'],
|
||||
[this.vnode.title, `/discuss/${this.ddoc.parentType}`, true],
|
||||
[this.vnode.title, `/discuss/${discussion.typeDisplay[this.ddoc.parentType]}/${this.ddoc.parentId}`, true],
|
||||
[this.ddoc.title, null, true],
|
||||
];
|
||||
this.response.template = 'discussion_detail.html';
|
||||
@@ -275,10 +289,8 @@ async function apply() {
|
||||
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/node/:name', module.exports.DiscussionNodeHandler);
|
||||
Route('/discuss/node/:name/create', module.exports.DiscussionCreateHandler);
|
||||
Route('/discuss/:type/:docId', module.exports.DiscussionNodeHandler);
|
||||
Route('/discuss/:type/:docId/create', module.exports.DiscussionCreateHandler);
|
||||
Route('/discuss/:type/:name', module.exports.DiscussionNodeHandler);
|
||||
Route('/discuss/:type/:name/create', module.exports.DiscussionCreateHandler);
|
||||
}
|
||||
|
||||
global.Hydro.handler.discussion = module.exports = {
|
||||
|
||||
@@ -100,7 +100,6 @@ class HomeSecurityHandler extends Handler {
|
||||
|
||||
async get() {
|
||||
// TODO(iceboy): pagination? or limit session count for uid?
|
||||
// TODO(masnn): UA & IP
|
||||
const sessions = await token.getSessionListByUid(this.user._id);
|
||||
const parsed = [];
|
||||
for (const session of sessions) {
|
||||
@@ -173,8 +172,13 @@ class HomeSettingsHandler extends Handler {
|
||||
}
|
||||
|
||||
async post(args) {
|
||||
// FIXME validation
|
||||
await user.setById(args.domainId, this.user._id, args);
|
||||
const $set = {};
|
||||
for (const key in args) {
|
||||
if (setting.SETTINGS_BY_KEY[args]) {
|
||||
$set[key] = args[key];
|
||||
}
|
||||
}
|
||||
await user.setById(args.domainId, this.user._id, $set);
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -78,10 +78,21 @@ class SystemSettingHandler extends SystemHandler {
|
||||
if (typeof args[key] === 'object') {
|
||||
const subtasks = [];
|
||||
for (const sub in args[key]) {
|
||||
subtasks.push(system.set(`${key}.${sub}`, args[key][sub]));
|
||||
const s = setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`];
|
||||
if (s) {
|
||||
if (s.ui === 'number') args[key][sub] = Number(args[key][sub]);
|
||||
console.log(s);
|
||||
subtasks.push(system.set(`${key}.${sub}`, args[key][sub]));
|
||||
}
|
||||
}
|
||||
tasks.push(Promise.all(subtasks));
|
||||
} else tasks.push(system.set(key, args[key]));
|
||||
} else {
|
||||
const s = setting.SYSTEM_SETTINGS_BY_KEY[key];
|
||||
if (s) {
|
||||
if (s.ui === 'number') args[key] = Number(args[key]);
|
||||
tasks.push(system.set(key, args[key]));
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
this.back();
|
||||
|
||||
@@ -25,9 +25,10 @@ class RecordListHandler extends RecordHandler {
|
||||
];
|
||||
}
|
||||
const rdocs = await record.getMany(domainId, q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE'));
|
||||
console.log(rdocs);
|
||||
const [udict, pdict] = await Promise.all([
|
||||
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
|
||||
problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid)),
|
||||
problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false),
|
||||
]);
|
||||
const path = [
|
||||
['Hydro', '/'],
|
||||
|
||||
+1
-1
@@ -22,6 +22,6 @@ Item('/homework', 'homework_main', 'homework', permission.PERM_VIEW_HOMEWORK);
|
||||
Item('/discuss', 'discussion_main', 'discussion', permission.PERM_VIEW_DISCUSSION);
|
||||
Item('/c', 'contest_main', 'contest', permission.PERM_VIEW_CONTEST);
|
||||
Item('/domain/dashboard', 'domain_dashboard', 'domain', permission.PERM_MANAGE);
|
||||
Item('/manage/dashboard', 'manage_dashboard', 'manage', null, (handler) => handler.user.priv === 1);
|
||||
Item('/manage/dashboard', 'manage', 'manage', null, (handler) => handler.user.priv === 1);
|
||||
|
||||
global.Hydro.lib.nav = module.exports = Item;
|
||||
@@ -242,7 +242,7 @@ async function edit(domainId, tid, $set, type = document.TYPE_CONTEST) {
|
||||
* @param {string} domainId
|
||||
* @param {ObjectID} tid
|
||||
* @param {number} type
|
||||
* @returns {Tdoc}
|
||||
* @returns {Promise<Tdoc>}
|
||||
*/
|
||||
async function get(domainId, tid, type = document.TYPE_CONTEST) {
|
||||
let tdoc;
|
||||
|
||||
@@ -2,9 +2,18 @@ const { ObjectID } = require('bson');
|
||||
const problem = require('./problem');
|
||||
const document = require('./document');
|
||||
const contest = require('./contest');
|
||||
const training = require('./training');
|
||||
const { PERM_VIEW_PROBLEM_HIDDEN } = require('../permission');
|
||||
const { DocumentNotFoundError } = require('../error');
|
||||
|
||||
const typeDisplay = {
|
||||
[document.TYPE_PROBLEM]: 'problem',
|
||||
[document.TYPE_CONTEST]: 'contest',
|
||||
[document.TYPE_DISCUSSION_NODE]: 'node',
|
||||
[document.TYPE_TRAINING]: 'training',
|
||||
[document.TYPE_HOMEWORK]: 'homework',
|
||||
};
|
||||
|
||||
function add(domainId, parentType, parentId, owner, title, content, ip = null, highlight = false) {
|
||||
return document.add(
|
||||
domainId, content, owner, document.TYPE_DISCUSSION,
|
||||
@@ -115,9 +124,7 @@ function getStatus(domainId, did, uid) {
|
||||
}
|
||||
|
||||
function addNode(domainId, _id, category) {
|
||||
return document.add(
|
||||
domainId, _id, 1, document.TYPE_DISCUSSION_NODE, null, null, null, { category },
|
||||
);
|
||||
return document.add(domainId, category, 1, document.TYPE_DISCUSSION_NODE, _id);
|
||||
}
|
||||
|
||||
function getNode(domainId, _id) {
|
||||
@@ -125,17 +132,32 @@ function getNode(domainId, _id) {
|
||||
}
|
||||
|
||||
async function getVnode(domainId, ddoc, handler) {
|
||||
if (ddoc.parentType === 'problem') {
|
||||
if (ddoc.parentType === document.TYPE_PROBLEM) {
|
||||
const pdoc = await problem.getById(domainId, ddoc.parentId);
|
||||
if (!pdoc) return null;
|
||||
if (pdoc.hidden && handler) handler.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
|
||||
return { ...pdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||
} if (ddoc.parentType === 'contest') {
|
||||
}
|
||||
if (ddoc.parentType === document.TYPE_CONTEST) {
|
||||
const tdoc = await contest.get(domainId, ddoc.parentId);
|
||||
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||
} if (ddoc.parentType === 'node') {
|
||||
}
|
||||
if (ddoc.parentType === document.TYPE_DISCUSSION_NODE) {
|
||||
const ndoc = await getNode(domainId, ddoc.parentId);
|
||||
return { title: ndoc._id, type: ddoc.parentType, id: ddoc.parentId };
|
||||
return {
|
||||
...ndoc,
|
||||
title: ddoc.parentId,
|
||||
type: ddoc.parentType,
|
||||
id: ddoc.parentId,
|
||||
};
|
||||
}
|
||||
if (ddoc.parentType === document.TYPE_TRAINING) {
|
||||
const tdoc = await training.get(domainId, ddoc.parentId);
|
||||
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||
}
|
||||
if (ddoc.parentType === document.TYPE_HOMEWORK) {
|
||||
const tdoc = await contest.get(domainId, ddoc.parentId, document.TYPE_HOMEWORK);
|
||||
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
|
||||
}
|
||||
return {
|
||||
title: 'Missing Node',
|
||||
@@ -147,13 +169,15 @@ async function getVnode(domainId, ddoc, handler) {
|
||||
async function getListVnodes(domainId, ddocs, handler) {
|
||||
const res = {};
|
||||
for (const ddoc of ddocs) {
|
||||
if (!res[ddoc.parentType]) res[ddoc.parentType] = {};
|
||||
// FIXME no-await-in-loop
|
||||
res[ddoc._id] = await getVnode(domainId, ddoc, handler); // eslint-disable-line no-await-in-loop
|
||||
res[ddoc.parentType][ddoc.parentId] = await getVnode(domainId, ddoc, handler); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
global.Hydro.model.discussion = module.exports = {
|
||||
typeDisplay,
|
||||
add,
|
||||
get,
|
||||
edit,
|
||||
|
||||
+6
-2
@@ -30,8 +30,12 @@ function del(_id) {
|
||||
}
|
||||
|
||||
async function inc(_id) {
|
||||
const file = await coll.findOne({ _id });
|
||||
file.count++;
|
||||
const doc = await coll.findOneAndUpdate(
|
||||
{ _id },
|
||||
{ $inc: { count: 1 } },
|
||||
{ returnOriginal: false },
|
||||
);
|
||||
return (doc.value || {}).count;
|
||||
}
|
||||
|
||||
async function dec(_id) {
|
||||
|
||||
@@ -14,5 +14,6 @@ module.exports = {
|
||||
task: require('./task'),
|
||||
message: require('./message'),
|
||||
setting: require('./setting'),
|
||||
discussion: require('./discussion'),
|
||||
file: require('./file'),
|
||||
};
|
||||
@@ -45,7 +45,7 @@ async function get(domainId, pid, uid = null) {
|
||||
const pdoc = Number.isInteger(pid)
|
||||
? await document.get(domainId, document.TYPE_PROBLEM, pid)
|
||||
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
|
||||
if (!pdoc) throw new ProblemNotFoundError(pid);
|
||||
if (!pdoc) throw new ProblemNotFoundError(domainId, pid);
|
||||
if (uid) {
|
||||
pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid);
|
||||
}
|
||||
@@ -110,10 +110,15 @@ async function random(domainId, query) {
|
||||
} return null;
|
||||
}
|
||||
|
||||
async function getList(domainId, pids) {
|
||||
async function getList(domainId, pids, doThrow = true) {
|
||||
const r = {};
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
for (const pid of pids) r[pid] = await get(domainId, pid);
|
||||
if (doThrow) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
for (const pid of pids) r[pid] = await get(domainId, pid);
|
||||
} else {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
for (const pid of pids) r[pid] = await get(domainId, pid).catch((e) => e);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
+24
-11
@@ -41,7 +41,7 @@ const ACCOUNT_SETTINGS = [
|
||||
const SETTINGS = [...PREFERENCE_SETTINGS, ...ACCOUNT_SETTINGS];
|
||||
const SETTINGS_BY_KEY = {};
|
||||
|
||||
for (const setting in SETTINGS) SETTINGS_BY_KEY[setting.key] = setting;
|
||||
for (const setting of SETTINGS) SETTINGS_BY_KEY[setting.key] = setting;
|
||||
|
||||
const SYSTEM_SETTINGS = [
|
||||
Setting('setting_basic', 'user.register', null, false, 'checkbox', 'Allow register'),
|
||||
@@ -49,19 +49,32 @@ const SYSTEM_SETTINGS = [
|
||||
Setting('setting_smtp', 'smtp.user', null, null, 'text', 'SMTP Username'),
|
||||
Setting('setting_smtp', 'smtp.pass', null, null, 'password', 'SMTP Password'),
|
||||
Setting('setting_smtp', 'smtp.host', null, null, 'text', 'SMTP Server Host'),
|
||||
Setting('setting_smtp', 'smtp.port', null, 465, 'text', '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.secure', null, false, 'checkbox', 'SSL'),
|
||||
Setting('setting_server', 'listen.port', null, 8888, 'text', 'Server Port', ''),
|
||||
Setting('setting_constant', 'PROBLEM_PER_PAGE', null, 100, 'text', 'Problems per Page'),
|
||||
Setting('setting_constant', 'CONTEST_PER_PAGE', null, 20, 'text', 'Contests per Page'),
|
||||
Setting('setting_constant', 'DISCUSSION_PER_PAGE', null, 50, 'text', 'Discussion per Page'),
|
||||
Setting('setting_constant', 'RECORD_PER_PAGE', null, 100, 'text', 'Record per Page'),
|
||||
Setting('setting_constant', 'SOLUTION_PER_PAGE', null, 20, 'text', 'Solutions per Page'),
|
||||
Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'text', 'Training per Page'),
|
||||
Setting('setting_constant', 'REPLY_PER_PAGE', null, 50, 'text', 'Reply per Page'),
|
||||
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'),
|
||||
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
|
||||
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', 'DISCUSSION_PER_PAGE', null, 50, 'number', 'Discussion per Page'),
|
||||
Setting('setting_constant', 'RECORD_PER_PAGE', null, 100, 'number', 'Record per Page'),
|
||||
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, PREFERENCE_SETTINGS, ACCOUNT_SETTINGS, SETTINGS, SETTINGS_BY_KEY, SYSTEM_SETTINGS,
|
||||
Setting,
|
||||
PREFERENCE_SETTINGS,
|
||||
ACCOUNT_SETTINGS,
|
||||
SETTINGS,
|
||||
SETTINGS_BY_KEY,
|
||||
SYSTEM_SETTINGS,
|
||||
SYSTEM_SETTINGS_BY_KEY,
|
||||
};
|
||||
@@ -19,7 +19,7 @@ const allowFail = async (func, ...args) => {
|
||||
return res;
|
||||
};
|
||||
|
||||
async function run(username, password) {
|
||||
async function run({ username, password } = {}) {
|
||||
const def = {
|
||||
PROBLEM_PER_PAGE: 100,
|
||||
RECORD_PER_PAGE: 100,
|
||||
@@ -39,7 +39,7 @@ async function run(username, password) {
|
||||
'smtp.pass': '',
|
||||
'smtp.secure': false,
|
||||
'db.ver': 1,
|
||||
'listen.port': 8888,
|
||||
'server.port': 8888,
|
||||
'session.keys': ['Hydro'],
|
||||
'session.secure': false,
|
||||
'session.saved_expire_seconds': 3600 * 24,
|
||||
@@ -49,7 +49,10 @@ async function run(username, password) {
|
||||
};
|
||||
let tasks = [];
|
||||
for (const key in def) {
|
||||
tasks.push(system.set(key, def[key]));
|
||||
tasks.push(system.get(key).then((value) => {
|
||||
if (!value) return system.set(key, def[key]);
|
||||
return Promise.resolve();
|
||||
}));
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
tasks = [
|
||||
@@ -91,12 +94,6 @@ async function run(username, password) {
|
||||
}, { upsert: true }),
|
||||
);
|
||||
}
|
||||
for (const category in builtin.DEFAULT_NODES) {
|
||||
const nodes = builtin.DEFAULT_NODES[category];
|
||||
for (const node of nodes) {
|
||||
tasks.push(discussion.addNode(node.name, category));
|
||||
}
|
||||
}
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -10,9 +10,9 @@ mongourl += `${options.host}:${options.port}/${options.name}`;
|
||||
*/
|
||||
let db = null;
|
||||
Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then((Database) => {
|
||||
db = Database.db(options.name);
|
||||
db.Db = Database;
|
||||
.then((Client) => {
|
||||
db = Client.db(options.name);
|
||||
db.Client = Client;
|
||||
bus.publish('system_database_connected', null);
|
||||
});
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ class Handler {
|
||||
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,
|
||||
@@ -193,8 +195,8 @@ class Handler {
|
||||
token.TYPE_SESSION,
|
||||
this._handler.expireSeconds,
|
||||
{
|
||||
update_ip: this.request.ip,
|
||||
update_ua: this.request.headers['user-agent'] || '',
|
||||
updateIp: this.request.ip,
|
||||
updateUa: this.request.headers['user-agent'] || '',
|
||||
},
|
||||
) : { uid: 1 };
|
||||
if (!this.session) this.session = { uid: 1 };
|
||||
@@ -302,6 +304,9 @@ function Route(route, RouteHandler, permission = null) {
|
||||
const args = {
|
||||
...ctx.params, ...ctx.query, ...ctx.request.body, domainId: 'system',
|
||||
};
|
||||
if (h.request.host !== await system.get('server.host')) {
|
||||
args.domainId = h.request.url.split('//')[1].split(`.${await system.get('server.host')}`)[0] || '';
|
||||
}
|
||||
|
||||
if (h.___prepare) await h.___prepare(args);
|
||||
if (permission) h.checkPerm(permission);
|
||||
@@ -437,8 +442,8 @@ async function start() {
|
||||
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
|
||||
app.use(router.routes()).use(router.allowedMethods());
|
||||
Route('*', Handler);
|
||||
server.listen(await system.get('listen.port'));
|
||||
console.log('Server listening at: %s', await system.get('listen.port'));
|
||||
server.listen(await system.get('server.port'));
|
||||
console.log('Server listening at: %s', await system.get('server.port'));
|
||||
}
|
||||
|
||||
global.Hydro.service.server = module.exports = {
|
||||
|
||||
+23
-18
@@ -1,8 +1,8 @@
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const Koa = require('koa');
|
||||
const morgan = require('koa-morgan');
|
||||
const Body = require('koa-body');
|
||||
const Router = require('koa-router');
|
||||
const cache = require('koa-static-cache');
|
||||
@@ -19,20 +19,11 @@ class Loader {
|
||||
}
|
||||
}
|
||||
|
||||
class Nunjucks extends nunjucks.Environment {
|
||||
constructor() {
|
||||
super(new Loader(), { autoescape: true, trimBlocks: true });
|
||||
this.addFilter('json', (self) => JSON.stringify(self), false);
|
||||
this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64'));
|
||||
}
|
||||
}
|
||||
const env = new Nunjucks();
|
||||
const env = new nunjucks.Environment(new Loader(), { autoescape: true, trimBlocks: true });
|
||||
|
||||
function render(name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
env.render(name, {
|
||||
typeof: (o) => typeof o,
|
||||
static_url: (str) => `/${str}`,
|
||||
handler: { renderTitle: (str) => str },
|
||||
_: (str) => str,
|
||||
}, (err, res) => {
|
||||
if (err) reject(err);
|
||||
@@ -40,15 +31,16 @@ function render(name) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function setup() {
|
||||
const app = new Koa();
|
||||
const server = http.createServer(app.callback());
|
||||
const router = new Router();
|
||||
app.keys = ['Hydro'];
|
||||
app.use(cache(path.join(process.cwd(), '.uibuild'), {
|
||||
app.use(cache(path.join(os.tmpdir(), 'hydro', 'builtin'), {
|
||||
maxAge: 365 * 24 * 60 * 60,
|
||||
}));
|
||||
app.use(Body({ formidable: { maxFileSize: 256 * 1024 * 1024 } }));
|
||||
app.use(Body());
|
||||
router.get('/', async (ctx) => {
|
||||
ctx.body = await render('setup.html');
|
||||
ctx.response.type = 'text/html';
|
||||
@@ -66,9 +58,23 @@ async function setup() {
|
||||
useNewUrlParser: true, useUnifiedTopology: true,
|
||||
});
|
||||
const db = Database.db(name);
|
||||
// Check Write Access
|
||||
await db.collection('test').insertOne({ test: 'test' });
|
||||
await db.collection('test').deleteOne({ test: 'test' });
|
||||
await Promise.all([
|
||||
db.collection('system').updateOne(
|
||||
{ _id: 'server.host' },
|
||||
{ $set: { value: ctx.request.host } },
|
||||
{ upsert: true },
|
||||
),
|
||||
db.collection('system').updateOne(
|
||||
{ _id: 'server.hostname' },
|
||||
{ $set: { value: ctx.request.hostname } },
|
||||
{ upsert: true },
|
||||
),
|
||||
db.collection('system').updateOne(
|
||||
{ _id: 'server.url' },
|
||||
{ $set: { value: ctx.request.href } },
|
||||
{ upsert: true },
|
||||
),
|
||||
]);
|
||||
fs.writeFileSync(path.resolve(process.cwd(), 'config.json'), JSON.stringify({
|
||||
host, port, name, username, password,
|
||||
}));
|
||||
@@ -79,7 +85,6 @@ async function setup() {
|
||||
}
|
||||
});
|
||||
});
|
||||
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
|
||||
app.use(router.routes()).use(router.allowedMethods());
|
||||
server.listen(8888);
|
||||
console.log('Server listening at: 8888');
|
||||
|
||||
+2
-1
@@ -307,7 +307,8 @@ Create Domain: 创建域
|
||||
Properties: 属性
|
||||
Access Control: 访问控制
|
||||
domain_main: 首页
|
||||
manage: 管理域
|
||||
manage: 管理
|
||||
manage_domain: 管理域
|
||||
manage_dashboard: 概况
|
||||
manage_discussion: 讨论节点
|
||||
manage_edit: 编辑域资料
|
||||
|
||||
+2
-1
@@ -301,7 +301,8 @@ Create Domain: 創建域
|
||||
Properties: 屬性
|
||||
Access Control: 訪問控制
|
||||
domain_main: 首頁
|
||||
manage: 管理域
|
||||
manage: 管理
|
||||
manage_domain: 管理域
|
||||
manage_dashboard: 概況
|
||||
manage_edit: 編輯域資料
|
||||
manage_join_applications: 加域申請
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const child = require('child_process');
|
||||
|
||||
const appPath = path.resolve(__dirname, '..', 'hydro', 'development.js');
|
||||
|
||||
const app = child.fork(appPath);
|
||||
@@ -1,13 +1,12 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const { mongodb } = global.Hydro.nodeModules;
|
||||
const dst = global.Hydro.service.db;
|
||||
const { file, problem, discussion } = global.Hydro.model;
|
||||
|
||||
const map = {};
|
||||
|
||||
const pid = (id) => {
|
||||
if (map[id.toString()]) return map[id.toString()];
|
||||
if (id.generationTime) return id;
|
||||
id = id.toString();
|
||||
return id;
|
||||
};
|
||||
|
||||
@@ -114,6 +113,8 @@ const tasks = {
|
||||
res[mapper[key]] = doc[key];
|
||||
} else if (typeof mapper[key] === 'object') {
|
||||
res[mapper[key].field] = mapper[key].processer(doc[key]);
|
||||
} else if (mapper[key] === null) {
|
||||
// Ignore this key
|
||||
} else {
|
||||
console.log('Unknown key:', key);
|
||||
}
|
||||
@@ -214,7 +215,7 @@ const tasks = {
|
||||
'fs.chunks': async (doc) => doc,
|
||||
file: async (doc) => ({
|
||||
_id: doc._id,
|
||||
count: doc.metadata.link,
|
||||
count: 0,
|
||||
secret: doc.metadata.secret,
|
||||
size: doc.length,
|
||||
md5: doc.md5,
|
||||
@@ -223,7 +224,7 @@ const tasks = {
|
||||
|
||||
const cursor = {
|
||||
user: (s) => s.collection('user').find(),
|
||||
document: (s) => s.collection('document').find(),
|
||||
document: (s) => s.collection('document').find({ doc_type: { $ne: 20 } }),
|
||||
'document.status': (s) => s.collection('document.status').find(),
|
||||
record: (s) => s.collection('record').find(),
|
||||
'fs.files': (s) => s.collection('fs.files').find(),
|
||||
@@ -231,6 +232,29 @@ const cursor = {
|
||||
file: (s) => s.collection('fs.files').find(),
|
||||
};
|
||||
|
||||
async function discussionNode(src, report) {
|
||||
const count = await src.collection('document').find({ doc_type: 20 }).count();
|
||||
await report({ progress: 1, message: `discussion.node: ${count}` });
|
||||
const total = Math.floor(count / 5);
|
||||
for (let i = 0; i <= total; i++) {
|
||||
const docs = await src.collection('document')
|
||||
.find({ doc_type: 20 }).skip(i * 5).limit(5)
|
||||
.toArray();
|
||||
for (const doc of docs) {
|
||||
const t = [];
|
||||
for (const item of doc.content || []) {
|
||||
const category = item[0];
|
||||
const nodes = item[1];
|
||||
for (const node of nodes || []) {
|
||||
t.push(discussion.addNode(doc.domain_id, node.name, category));
|
||||
}
|
||||
}
|
||||
await Promise.all(t).catch((e) => e);
|
||||
}
|
||||
await report({ progress: Math.round(100 * ((i + 1) / (total + 1))) });
|
||||
}
|
||||
}
|
||||
|
||||
async function fixPid(report) {
|
||||
const count = await dst.collection('document').find({ docType: 10 }).count();
|
||||
await report({ progress: 1, message: `Fix pid: ${count}` });
|
||||
@@ -239,12 +263,28 @@ async function fixPid(report) {
|
||||
const docs = await dst.collection('document')
|
||||
.find({ docType: 10 }).skip(i * 50).limit(50)
|
||||
.toArray();
|
||||
const t = [];
|
||||
for (const doc of docs) {
|
||||
dst.collection('document').updateOne(
|
||||
{ _id: doc._id },
|
||||
{ $set: { pid: doc.pid || doc.docId.toString() } },
|
||||
t.push(
|
||||
dst.collection('document').updateOne(
|
||||
{ _id: doc._id },
|
||||
{ $set: { pid: doc.pid || doc.docId.toString() } },
|
||||
),
|
||||
);
|
||||
if (doc.data && doc.data.generationTime) {
|
||||
t.push(file.inc(doc.data));
|
||||
} else if (doc.data) {
|
||||
const pdoc = await problem.get(doc.data.domain, pid(doc.data.pid));
|
||||
t.push(
|
||||
file.inc(pdoc.data),
|
||||
dst.collection('document').updateOne(
|
||||
{ _id: doc._id },
|
||||
{ $set: { data: pdoc.data } },
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
await Promise.all(t);
|
||||
await report({ progress: Math.round(100 * ((i + 1) / (total + 1))) });
|
||||
}
|
||||
}
|
||||
@@ -273,9 +313,23 @@ async function task(name, src, report) {
|
||||
if (d.domainId && d.docId && d.docType) {
|
||||
const query = { domainId: d.domainId, docId: d.docId, docType: d.docType };
|
||||
if (d.uid) query.uid = d.uid;
|
||||
res.push(dst.collection(name).updateOne(
|
||||
query, { $set: docWithoutDid }, { upsert: true },
|
||||
));
|
||||
res.push((async () => {
|
||||
const data = await dst.collection(name).findOne(query);
|
||||
if (data) {
|
||||
await dst.collection(name).updateOne(query, { $set: docWithoutId });
|
||||
} else if (d._id) {
|
||||
const dat = await dst.collection(name).findOne({ _id: d._id });
|
||||
if (dat) {
|
||||
await dst.collection(name).updateOne({
|
||||
_id: d._id,
|
||||
}, { $set: docWithoutId });
|
||||
} else {
|
||||
await dst.collection(name).insertOne(d);
|
||||
}
|
||||
} else {
|
||||
await dst.collection(name).insertOne(d);
|
||||
}
|
||||
})());
|
||||
} else if (d._id) {
|
||||
res.push(dst.collection(name).updateOne({
|
||||
_id: d._id,
|
||||
@@ -290,7 +344,6 @@ async function task(name, src, report) {
|
||||
lastProgress = progress;
|
||||
}
|
||||
}
|
||||
await fixPid(report);
|
||||
}
|
||||
|
||||
async function migrateVijos({
|
||||
@@ -325,6 +378,8 @@ async function migrateVijos({
|
||||
await dst.collection('user').deleteMany({ _id: { $nin: [0, 1] } });
|
||||
const d = ['user', 'document', 'document.status', 'record', 'file'];
|
||||
for (const i of d) await task(i, src, report);
|
||||
await fixPid(report);
|
||||
await discussionNode(src, report);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{# No Compression #}
|
||||
{% macro form_begin(args) %}
|
||||
{% set label = args.label %}
|
||||
{% set columns = args.columns %}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</a>
|
||||
{% if badge %}
|
||||
<a class="user-profile-badge v-center badge--lv{{ udoc['level']|default(0) }}" href="/u/{{ udoc['_id']|default(0) }}" title="LV {{ udoc['level']|default(0) }}: Top {{ model.builtin.LEVELS[udoc['level']|default(0)]|default('N/A') }}%">LV {{ udoc['level']|default(0) }}</a>
|
||||
{% if modbadge and handler.hasPerm(perm.PERM_MOD_BADGE) %}
|
||||
{% if modbadge and udoc.hasPerm(perm.PERM_MOD_BADGE) %}
|
||||
<span class="user-profile-badge v-center badge--mod" title="Moderator">MOD</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="medium-3 columns">
|
||||
{% if vnode.parentType == 'problem' %}
|
||||
{% if vnode.parentType == model.document.TYPE_PROBLEM %}
|
||||
{% set pdoc = vnode %}
|
||||
{% set owner_udoc = udict[vnode.owner] %}
|
||||
{% include "partials/problem_sidebar.html" %}
|
||||
{% elif vnode.parentType == 'contest' %}
|
||||
{% elif vnode.parentType == model.document.TYPE_CONTEST %}
|
||||
{% set tdoc = vnode %}
|
||||
{% set owner_udoc = udict[vnode.owner] %}
|
||||
{% include "partials/contest_sidebar.html" %}
|
||||
{% elif vnode.parentType == 'node' %}
|
||||
{% elif vnode.parentType == model.document.TYPE_DISCUSSION_NODE %}
|
||||
<div class="section side">
|
||||
{% if vnode.pic %}<div class="discussion-node__bg {{ vnode.pic }}"></div>{% endif %}
|
||||
<div class="section__header">
|
||||
@@ -24,7 +24,7 @@
|
||||
<div class="section__body">
|
||||
{% if vnode %}
|
||||
{% if handler.hasPerm(perm.PERM_CREATE_DISCUSSION) %}
|
||||
<p><a href="/discuss/{{ vnode.parentType }}/{{ vnode.parentId }}" class="expanded primary button">{{ _('Create a Discussion') }}</a></p>
|
||||
<p><a href="/discuss/{{ model.discussion.typeDisplay[vnode.parentType] }}/{{ vnode.parentId }}/create" class="expanded primary button">{{ _('Create a Discussion') }}</a></p>
|
||||
{% else %}
|
||||
{% if not handler.hasPerm(perm.PERM_LOGGEDIN) %}
|
||||
<p><a href="javascript:showSignInDialog();" class="expanded primary button">{{ _('Login to Create a Discussion') }}</a></p>
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
<div class="section">
|
||||
<div class="section__body">
|
||||
<form method="post">
|
||||
{{ form.form_text({
|
||||
label:'ID',
|
||||
name:'id',
|
||||
value:domain._id,
|
||||
disabled:true,
|
||||
required:true
|
||||
}) }}
|
||||
{{ form.form_text({
|
||||
label:'Name',
|
||||
name:'name',
|
||||
|
||||
@@ -6,18 +6,12 @@
|
||||
<h1>{{ _('Add Module') }}</h1>
|
||||
</div></div>
|
||||
<div class="row">
|
||||
<div class="medium-6 columns">
|
||||
<div class="medium-12 columns">
|
||||
<label>
|
||||
{{ _('URL') }}
|
||||
<input name="url" type="text" class="textbox" autocomplete="off" data-autofocus>
|
||||
</label>
|
||||
</div>
|
||||
<div class="medium-6 columns">
|
||||
<label>
|
||||
{{ _('ID (Optional)') }}
|
||||
<input name="id" type="text" class="textbox" autocomplete="off">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,10 +49,9 @@
|
||||
<td class="col--size">{{ size(module.size) if module.size else '?' }}</td>
|
||||
<td class="col--version">{{ module.version|default('?') }}</td>
|
||||
<td class="col--action">
|
||||
<a class="typo-a" href="/manage/module/download/{{ module.filename }}">{{ _('Download') }}</a>
|
||||
<form method="post">
|
||||
<input type="hidden" name="operation" value="delete">
|
||||
<input type="hidden" name="id" value="{{ module.id }}">
|
||||
<input type="hidden" name="id" value="{{ module.filename }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||
<input type="submit" class="typo-a" value="{{ _('Delete') }}">
|
||||
|
||||
@@ -15,18 +15,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="media__body top">
|
||||
<h1 class="discussion__title"><a href="/discuss/{{ ddoc._id }}#{{ handler.datetime_stamp(ddoc.updateAt) if ddoc.update_at else '' }}" data-emoji-enabled>{{ ddoc['title'] }}</a></h1>
|
||||
<h1 class="discussion__title"><a href="/discuss/{{ ddoc._id }}#{{ ddoc.updateAt }}" data-emoji-enabled>{{ ddoc['title'] }}</a></h1>
|
||||
<ul class="supplementary list">
|
||||
<li>
|
||||
<a class="discussion-node-tag" href="/discuss/{{ ddoc.parentType }}/{{ ddoc.parentId }}">
|
||||
{% if ddoc.parentType == 'node' %}
|
||||
<a class="discussion-node-tag" href="/discuss/{{ model.discussion.typeDisplay[ddoc.parentType] }}/{{ ddoc.parentId }}">
|
||||
{% if ddoc.parentType == model.document.TYPE_PROBLEM %}
|
||||
<span class="v-center icon icon-tag"></span>
|
||||
{% elif ddoc.parentType == 'contest' %}
|
||||
{% elif ddoc.parentType == model.document.TYPE_CONTEST %}
|
||||
<span class="v-center icon icon-award"></span>
|
||||
{% else %}
|
||||
<span class="v-center icon icon-flag"></span>
|
||||
{% endif %}
|
||||
{{ vndict[ddoc._id].title|default(_('(missing)')) }}
|
||||
{{ vndict[ddoc.parentType][ddoc.parentId].title|default(_('(missing)')) }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="section__body">
|
||||
{% for setting in family_settings %}
|
||||
{% if setting.ui == 'text' or setting.ui == 'password' %}
|
||||
{% if setting.ui == 'text' or setting.ui == 'password' or setting.ui == 'number' %}
|
||||
{{ form.form_text({
|
||||
type:setting.ui,
|
||||
label:setting.name,
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta name="msapplication-TileColor" content="#579e9a">
|
||||
<meta name="theme-color" content="#56758f">
|
||||
|
||||
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
|
||||
<link rel="stylesheet" media="all" href="/vj4.css">
|
||||
<title>{{ _('Setup') }}</title>
|
||||
<script>
|
||||
var _htmlNode = document.documentElement;
|
||||
@@ -31,25 +30,25 @@
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Port') }}
|
||||
<input name="port" type="number" value="27017" autofocus>
|
||||
<input name="port" type="number" value="27017">
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Name') }}
|
||||
<input name="name" type="text" value="hydro" autofocus>
|
||||
<input name="name" type="text" value="hydro">
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Username') }}
|
||||
<input name="username" type="text" placeholder="{{ _('Leave blank if none') }}" autofocus>
|
||||
<input name="username" type="text" placeholder="{{ _('Leave blank if none') }}">
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
<label class="inverse material textbox">
|
||||
{{ _('Database Password') }}
|
||||
<input name="password" type="password" placeholder="{{ _('Leave blank if none') }}" autofocus>
|
||||
<input name="password" type="password" placeholder="{{ _('Leave blank if none') }}">
|
||||
</label>
|
||||
</div></div>
|
||||
<div class="row"><div class="columns">
|
||||
@@ -62,6 +61,5 @@
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="{{ static_url('vj4.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user