From dafa80f6f2ed1acd0ffb375558ee6c4f883309c3 Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 10 Apr 2021 13:36:14 +0800 Subject: [PATCH 01/11] core: dump config.yaml to database (for #65) --- packages/hydrojudge/src/cases.ts | 118 +++--------------- packages/hydrooj/src/entry/common.ts | 3 +- packages/hydrooj/src/entry/master.ts | 2 +- packages/hydrooj/src/interface.ts | 1 - .../hydrooj/src/lib/testdata.convert.ini.ts | 22 ---- packages/hydrooj/src/lib/testdataConfig.ts | 22 +--- packages/hydrooj/src/upgrade.ts | 33 ++++- packages/utils/lib/cases.ts | 92 ++++++++++++++ packages/utils/lib/utils.ts | 6 + 9 files changed, 152 insertions(+), 147 deletions(-) delete mode 100644 packages/hydrooj/src/lib/testdata.convert.ini.ts create mode 100644 packages/utils/lib/cases.ts diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index afae6a5a0..91534d8ac 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -1,7 +1,9 @@ import fs from 'fs-extra'; import path from 'path'; import yaml from 'js-yaml'; -import { Dictionary, sum } from 'lodash'; +import { sum } from 'lodash'; +import readYamlCases, { convertIniConfig } from '@hydrooj/utils/lib/cases'; +import { changeErrorType } from '@hydrooj/utils/lib/utils'; import { FormatError, SystemError } from './error'; import { parseTimeMS, parseMemoryMB, ensureFile } from './utils'; import { getConfig } from './config'; @@ -193,102 +195,6 @@ async function readAutoCases(folder, { next }, cfg) { return config; } -export async function readYamlCases(folder: string, cfg: Dictionary = {}, args) { - const config: any = { - checker_type: 'default', - count: 0, - subtasks: [], - judge_extra_files: [], - user_extra_files: [], - }; - const next = args.next; - const checkFile = ensureFile(folder); - config.checker_type = cfg.checker_type || 'default'; - if (cfg.checker) config.checker = checkFile(cfg.checker, 'Cannot find checker {0}.'); - if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.'); - if (cfg.judge_extra_files) { - if (typeof cfg.judge_extra_files === 'string') { - config.judge_extra_files = [checkFile(cfg.judge_extra_files, 'Cannot find judge extra file {0}.')]; - } else if (cfg.judge_extra_files instanceof Array) { - for (const file of cfg.judge_extra_files) { - config.judge_extra_files.push(checkFile(file, 'Cannot find judge extra file {0}.')); - } - } else throw new FormatError('Invalid judge_extra_files config.'); - } - if (cfg.user_extra_files) { - if (typeof cfg.user_extra_files === 'string') { - config.user_extra_files = [checkFile(cfg.user_extra_files, 'Cannot find user extra file {0}.')]; - } else if (cfg.user_extra_files instanceof Array) { - for (const file of cfg.user_extra_files) { - config.user_extra_files.push(checkFile(file, 'Cannot find user extra file {0}.')); - } - } else throw new FormatError('Invalid user_extra_files config.'); - } - if (cfg.outputs) { - config.type = 'submit_answer'; - } else if (cfg.cases) { - config.subtasks = [{ - score: parseInt(cfg.score, 10) || Math.floor(100 / cfg.cases.length), - time_limit_ms: parseTimeMS(cfg.time || '1s'), - memory_limit_mb: parseMemoryMB(cfg.memory || '512m'), - cases: [], - type: 'sum', - }]; - for (const c of cfg.cases) { - config.count++; - config.subtasks[0].cases.push({ - input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null', - output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null', - id: config.count, - }); - } - } else if (cfg.subtasks) { - for (const subtask of cfg.subtasks) { - const cases = []; - for (const c of subtask.cases) { - config.count++; - cases.push({ - input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null', - output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null', - id: config.count, - }); - } - config.subtasks.push({ - score: parseInt(subtask.score, 10), - if: subtask.if || [], - cases, - time_limit_ms: parseTimeMS(subtask.time || cfg.time || '1s'), - memory_limit_mb: parseMemoryMB(subtask.memory || cfg.memory || '512m'), - }); - } - } else { - const c = await readAutoCases(folder, { next }, cfg); - config.subtasks = c.subtasks; - config.count = c.count; - } - if (config.type === 'submit_answer' && !cfg.outputs) throw new FormatError('outputs config not found'); - return Object.assign(cfg, config); -} - -function convertIniConfig(ini: string) { - const f = ini.split('\n'); - const count = parseInt(f[0], 10); - const res = { subtasks: [] }; - for (let i = 1; i <= count; i++) { - if (!f[i] || !f[i].trim()) throw new FormatError('Testcada count incorrect.'); - const [input, output, time, score, memory] = f[i].split('|'); - const cur = { - cases: [{ input: `input/${input.toLowerCase()}`, output: `output/${output.toLowerCase()}` }], - score: parseInt(score, 10), - time: `${time}s`, - memory: '512m', - }; - if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; - res.subtasks.push(cur); - } - return res; -} - function isValidConfig(config) { if (config.count > (getConfig('testcases_max') || 100)) { throw new FormatError('Too many testcases. Cancelled.'); @@ -309,9 +215,23 @@ export default async function readCases(folder: string, cfg: Record } else if (fs.existsSync(ymlConfig)) { config = { ...yaml.load(fs.readFileSync(ymlConfig).toString()) as object, ...cfg }; } else if (fs.existsSync(iniConfig)) { - config = { ...convertIniConfig(fs.readFileSync(iniConfig).toString()), ...cfg }; + try { + config = { ...convertIniConfig(fs.readFileSync(iniConfig).toString()), ...cfg }; + } catch (e) { + throw changeErrorType(e, FormatError); + } } else config = cfg; - const result = await readYamlCases(folder, config, args); + let result; + try { + result = await readYamlCases(config, ensureFile(folder)); + } catch (e) { + throw changeErrorType(e, FormatError); + } + if (!(result.outputs || result.subtasks.length)) { + const c = await readAutoCases(folder, args, cfg); + config.subtasks = c.subtasks; + config.count = c.count; + } isValidConfig(result); return result; } diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index 2b12037d8..779ff1434 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -12,8 +12,7 @@ const logger = new Logger('common', true); export const builtinLib = [ 'jwt', 'download', 'i18n', 'mail', 'useragent', 'crypto', 'misc', 'paginate', 'hash.hydro', 'rank', - 'validator', 'ui', 'testdata.convert.ini', 'testdataConfig', 'difficulty', - 'content', + 'validator', 'ui', 'testdataConfig', 'difficulty', 'content', ]; export const builtinModel = [ diff --git a/packages/hydrooj/src/entry/master.ts b/packages/hydrooj/src/entry/master.ts index 6081bf151..407ddae53 100644 --- a/packages/hydrooj/src/entry/master.ts +++ b/packages/hydrooj/src/entry/master.ts @@ -65,7 +65,7 @@ export async function load(call: Entry) { endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge, }; const storage = require('../service/storage'); - storage.start(sopts); + await storage.start(sopts); require('../service/monitor'); for (const i of builtinModel) require(`../model/${i}`); const scripts = require('../upgrade').default; diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 95bfad782..713a90040 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -546,7 +546,6 @@ export interface Lib extends Record { rank: typeof import('./lib/rank'), rating: typeof import('./lib/rating'), testdataConfig: typeof import('./lib/testdataConfig'), - 'testdata.convert.ini': typeof import('./lib/testdata.convert.ini'), useragent: typeof import('./lib/useragent'), validator: typeof import('./lib/validator'), template?: any, diff --git a/packages/hydrooj/src/lib/testdata.convert.ini.ts b/packages/hydrooj/src/lib/testdata.convert.ini.ts deleted file mode 100644 index 976d19928..000000000 --- a/packages/hydrooj/src/lib/testdata.convert.ini.ts +++ /dev/null @@ -1,22 +0,0 @@ -import yaml from 'js-yaml'; - -function convert(ini: string) { - const f = ini.split('\n'); - const count = parseInt(f[0], 10); - const res = { subtasks: [] }; - for (let i = 1; i <= count; i++) { - const [input, output, time, score, memory] = f[i].split('|'); - const cur = { - cases: [{ input: `input/${input}`, output: `output/${output}` }], - score: parseInt(score, 10), - time: `${time}s`, - memory: '512m', - }; - if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; - res.subtasks.push(cur); - } - return yaml.dump(res); -} - -global.Hydro.lib['testdata.convert.ini'] = convert; -export = convert; diff --git a/packages/hydrooj/src/lib/testdataConfig.ts b/packages/hydrooj/src/lib/testdataConfig.ts index b858c9d75..226d96b99 100644 --- a/packages/hydrooj/src/lib/testdataConfig.ts +++ b/packages/hydrooj/src/lib/testdataConfig.ts @@ -1,23 +1,7 @@ -import Zip from 'adm-zip'; +import readYamlCases from '@hydrooj/utils/lib/cases'; import { load } from 'js-yaml'; import type { ProblemConfig } from '../interface'; -export async function readConfig(file: string | Buffer) { - const data = new Zip(file); - const entries = data.getEntries(); - for (const entry of entries) { - if (entry.name.toLowerCase() === 'config.yaml') { - return entry.getData().toString(); - } - if (entry.name.toLowerCase() === 'config.ini') { - const ini = entry.getData().toString(); - const conv = global.Hydro.lib['testdata.convert.ini']; - return conv(ini); - } - } - return ''; -} - interface ParseResult { count: number, memoryMax: number, @@ -37,7 +21,7 @@ export async function parseConfig(config: string | ProblemConfig) { let cfg: ProblemConfig = {}; if (typeof config === 'string') { // TODO should validate here? - cfg = load(config) as ProblemConfig; + cfg = readYamlCases(load(config) as Record) as ProblemConfig; } else cfg = config; if (cfg.cases) { for (const c of cfg.cases) { @@ -57,4 +41,4 @@ export async function parseConfig(config: string | ProblemConfig) { } } -global.Hydro.lib.testdataConfig = { readConfig, parseConfig }; +global.Hydro.lib.testdataConfig = { parseConfig }; diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index bd3ddc2b1..a49235f5f 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -4,21 +4,21 @@ import { FilterQuery, ObjectID } from 'mongodb'; import AdmZip from 'adm-zip'; import Queue from 'p-queue'; import yaml from 'js-yaml'; +import { convertIniConfig } from '@hydrooj/utils/lib/cases'; import { Progress } from './ui'; +import { Rdoc } from './interface'; import { Logger } from './logger'; import { streamToBuffer } from './utils'; import { iterateAllDomain, iterateAllProblem } from './pipelineUtils'; import gridfs from './service/gridfs'; import storage from './service/storage'; import db from './service/db'; +import difficultyAlgorithm from './lib/difficulty'; import problem from './model/problem'; import domain from './model/domain'; import * as document from './model/document'; import * as system from './model/system'; -import difficultyAlgorithm from './lib/difficulty'; import { STATUS } from './model/builtin'; -import RecordModel from './model/record'; -import { Rdoc } from './interface'; const logger = new Logger('upgrade'); type UpgradeScript = void | (() => Promise); @@ -262,6 +262,33 @@ const scripts: UpgradeScript[] = [ } return true; }, + async function _20_21() { + await iterateAllProblem([], async (pdoc) => { + let config: string; + try { + const file = await storage.get(`problem/${pdoc.domainId}/${pdoc.docId}/testdata/config.yaml`); + config = (await streamToBuffer(file)).toString('utf-8'); + logger.info(`Loaded config for ${pdoc.domainId}/${pdoc.docId} from config.yaml`); + } catch (e) { + try { + const file = await storage.get(`problem/${pdoc.domainId}/${pdoc.docId}/testdata/config.yml`); + config = (await streamToBuffer(file)).toString('utf-8'); + logger.info(`Loaded config for ${pdoc.domainId}/${pdoc.docId} from config.yml`); + } catch (err) { + try { + const file = await storage.get(`problem/${pdoc.domainId}/${pdoc.docId}/testdata/config.ini`); + config = yaml.dump(convertIniConfig((await streamToBuffer(file)).toString('utf-8'))); + logger.info(`Loaded config for ${pdoc.domainId}/${pdoc.docId} from config.ini`); + } catch (error) { + logger.warn('Config for %s/%s(%s) not found', pdoc.domainId, pdoc.docId, pdoc.pid || pdoc.docId); + // no config found + } + } + } + if (config) await problem.edit(pdoc.domainId, pdoc.docId, { config }); + }); + return true; + }, ]; export default scripts; diff --git a/packages/utils/lib/cases.ts b/packages/utils/lib/cases.ts new file mode 100644 index 000000000..b13089d4c --- /dev/null +++ b/packages/utils/lib/cases.ts @@ -0,0 +1,92 @@ +import { parseTimeMS, parseMemoryMB } from './utils'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export default async function readYamlCases(cfg: Record = {}, checkFile = (s: string, errMsg: string) => s) { + const config: any = { + checker_type: 'default', + count: 0, + subtasks: [], + judge_extra_files: [], + user_extra_files: [], + }; + config.checker_type = cfg.checker_type || 'default'; + if (cfg.checker) config.checker = checkFile(cfg.checker, 'Cannot find checker {0}.'); + if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.'); + if (cfg.judge_extra_files) { + if (typeof cfg.judge_extra_files === 'string') { + config.judge_extra_files = [checkFile(cfg.judge_extra_files, 'Cannot find judge extra file {0}.')]; + } else if (cfg.judge_extra_files instanceof Array) { + for (const file of cfg.judge_extra_files) { + config.judge_extra_files.push(checkFile(file, 'Cannot find judge extra file {0}.')); + } + } else throw new Error('Invalid judge_extra_files config.'); + } + if (cfg.user_extra_files) { + if (typeof cfg.user_extra_files === 'string') { + config.user_extra_files = [checkFile(cfg.user_extra_files, 'Cannot find user extra file {0}.')]; + } else if (cfg.user_extra_files instanceof Array) { + for (const file of cfg.user_extra_files) { + config.user_extra_files.push(checkFile(file, 'Cannot find user extra file {0}.')); + } + } else throw new Error('Invalid user_extra_files config.'); + } + if (cfg.outputs) { + config.type = 'submit_answer'; + } else if (cfg.cases) { + config.subtasks = [{ + score: parseInt(cfg.score, 10) || Math.floor(100 / cfg.cases.length), + time_limit_ms: parseTimeMS(cfg.time || '1s'), + memory_limit_mb: parseMemoryMB(cfg.memory || '512m'), + cases: [], + type: 'sum', + }]; + for (const c of cfg.cases) { + config.count++; + config.subtasks[0].cases.push({ + input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null', + output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null', + id: config.count, + }); + } + } else if (cfg.subtasks) { + for (const subtask of cfg.subtasks) { + const cases = []; + for (const c of subtask.cases) { + config.count++; + cases.push({ + input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null', + output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null', + id: config.count, + }); + } + config.subtasks.push({ + score: parseInt(subtask.score, 10), + if: subtask.if || [], + cases, + time_limit_ms: parseTimeMS(subtask.time || cfg.time || '1s'), + memory_limit_mb: parseMemoryMB(subtask.memory || cfg.memory || '512m'), + }); + } + } + if (config.type === 'submit_answer' && !cfg.outputs) throw new Error('outputs config not found'); + return Object.assign(cfg, config); +} + +export function convertIniConfig(ini: string) { + const f = ini.split('\n'); + const count = parseInt(f[0], 10); + const res = { subtasks: [] }; + for (let i = 1; i <= count; i++) { + if (!f[i] || !f[i].trim()) throw new Error('Testcada count incorrect.'); + const [input, output, time, score, memory] = f[i].split('|'); + const cur = { + cases: [{ input: `input/${input.toLowerCase()}`, output: `output/${output.toLowerCase()}` }], + score: parseInt(score, 10), + time: `${time}s`, + memory: '512m', + }; + if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; + res.subtasks.push(cur); + } + return res; +} diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index 195762943..64f8a9b4a 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -297,6 +297,12 @@ export function formatSeconds(_seconds = '0') { ); } +export function changeErrorType(err: Error, Err: any) { + const e = new Err(err.message); + e.stack = err.stack; + return e; +} + export async function retry(func: Function, ...args: any[]): Promise; export async function retry(times: number, func: Function, ...args: any[]): Promise; // eslint-disable-next-line consistent-return From 1c89596cb33d6d5f07ea06f4feb23549d260066c Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 10 Apr 2021 18:44:07 +0800 Subject: [PATCH 02/11] =?UTF-8?q?core:=20=E6=94=AF=E6=8C=81=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=97=B6=E7=A9=BA=E9=99=90=E5=88=B6=20(#65)=20?= =?UTF-8?q?=E8=99=BD=E7=84=B6=E6=9C=89=E7=82=B9=E4=B8=91=EF=BC=8C=E4=BD=86?= =?UTF-8?q?=E6=98=AF=E8=83=BD=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/hydrojudge/package.json | 2 +- packages/hydrojudge/src/cases.ts | 14 ++++++------ packages/hydrojudge/src/judge/default.ts | 8 +++---- packages/hydrojudge/src/judge/hack.ts | 4 ++-- packages/hydrojudge/src/judge/interactive.ts | 12 +++++----- packages/hydrojudge/src/judge/run.ts | 4 ++-- packages/hydrojudge/src/sandbox.ts | 12 +++++----- packages/hydrooj/package.json | 2 +- packages/hydrooj/src/handler/problem.ts | 7 ++++++ packages/hydrooj/src/interface.ts | 1 - packages/hydrooj/src/lib/testdataConfig.ts | 22 ++++++++++--------- packages/hydrooj/src/model/problem.ts | 4 +--- packages/migrate-vijos/package.json | 2 +- packages/migrate-vijos/script.ts | 1 - packages/ui-default/package.json | 2 +- .../partials/problem_sidebar_normal.html | 4 ---- .../ui-default/templates/problem_detail.html | 22 ++++++++++++++++++- packages/utils/lib/cases.ts | 12 +++++----- packages/utils/lib/utils.ts | 6 +++-- packages/utils/package.json | 2 +- 20 files changed, 84 insertions(+), 59 deletions(-) diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 998ae9079..52297e21e 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "2.4.19", + "version": "2.5.0", "os": [ "linux" ], diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index 91534d8ac..c93cb6237 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -88,8 +88,8 @@ async function read0(folder: string, files: string[], checkFile, cfg) { const config = { count: 0, subtasks: [{ - time_limit_ms: parseTimeMS(cfg.time || '1s'), - memory_limit_mb: parseMemoryMB(cfg.memory || '256m'), + time: parseTimeMS(cfg.time || '1s'), + memory: parseMemoryMB(cfg.memory || '256m'), type: 'sum', cases: [], score: Math.floor(100 / cases.length), @@ -105,8 +105,8 @@ async function read0(folder: string, files: string[], checkFile, cfg) { } if (extra < cases.length) { config.subtasks.push({ - time_limit_ms: parseTimeMS(cfg.time || '1s'), - memory_limit_mb: parseMemoryMB(cfg.memory || '256m'), + time: parseTimeMS(cfg.time || '1s'), + memory: parseMemoryMB(cfg.memory || '256m'), type: 'sum', cases: [], score: Math.floor(100 / cases.length) + 1, @@ -134,8 +134,8 @@ async function read1(folder: string, files: string[], checkFile, cfg) { if (fs.existsSync(path.resolve(folder, c.output))) { if (!subtask[REG.subtask(data)]) { subtask[REG.subtask(data)] = [{ - time_limit_ms: parseTimeMS(cfg.time || '1s'), - memory_limit_mb: parseMemoryMB(cfg.memory || '256m'), + time: parseTimeMS(cfg.time || '1s'), + memory: parseMemoryMB(cfg.memory || '256m'), type: 'min', cases: [c], }]; @@ -199,7 +199,7 @@ function isValidConfig(config) { if (config.count > (getConfig('testcases_max') || 100)) { throw new FormatError('Too many testcases. Cancelled.'); } - const total_time = sum(config.subtasks.map((subtask) => subtask.time_limit_ms * subtask.cases.length)); + const total_time = sum(config.subtasks.map((subtask) => subtask.time * subtask.cases.length)); if (total_time > (getConfig('total_time_limit') || 60) * 1000) { throw new FormatError('Total time limit longer than {0}s. Cancelled.', [+getConfig('total_time_limit') || 60]); } diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index 7fbdf154d..3e4a6b2c0 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -50,8 +50,8 @@ function judgeCase(c, sid) { stderr, copyIn, copyOut, - time_limit_ms: ctxSubtask.subtask.time_limit_ms * ctx.execute.time, - memory_limit_mb: ctxSubtask.subtask.memory_limit_mb, + time: ctxSubtask.subtask.time * ctx.execute.time, + memory: ctxSubtask.subtask.memory, }, ); const { code, time_usage_ms, memory_usage_kb } = res; @@ -62,9 +62,9 @@ function judgeCase(c, sid) { let message: any = ''; let score = 0; if (status === STATUS.STATUS_ACCEPTED) { - if (time_usage_ms > ctxSubtask.subtask.time_limit_ms * ctx.execute.time) { + if (time_usage_ms > ctxSubtask.subtask.time * ctx.execute.time) { status = STATUS.STATUS_TIME_LIMIT_EXCEEDED; - } else if (memory_usage_kb > ctxSubtask.subtask.memory_limit_mb * 1024) { + } else if (memory_usage_kb > ctxSubtask.subtask.memory * 1024) { status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED; } else { [status, score, message] = await check({ diff --git a/packages/hydrojudge/src/judge/hack.ts b/packages/hydrojudge/src/judge/hack.ts index 8b9e56330..8e81be8dc 100644 --- a/packages/hydrojudge/src/judge/hack.ts +++ b/packages/hydrojudge/src/judge/hack.ts @@ -77,8 +77,8 @@ export const judge = async (ctx) => { stderr, copyIn: execute.copyIn, copyOut, - time_limit_ms: 1000, - memory_limit_mb: 512, + time: 1000, + memory: 512, }, ); const { code, time_usage_ms, memory_usage_kb } = res; diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index 008658acd..47711f894 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -24,21 +24,21 @@ function judgeCase(c) { { execute: ctx.executeUser.execute.replace(/\$\{name\}/g, 'code'), copyIn: ctx.executeUser.copyIn, - time_limit_ms: ctxSubtask.subtask.time_limit_ms, - memory_limit_mb: ctxSubtask.subtask.memory_limit_mb, + time: ctxSubtask.subtask.time, + memory: ctxSubtask.subtask.memory, }, { execute: `${ctx.executeInteractor.execute.replace(/\$\{name\}/g, 'interactor')} /w/in /w/tout`, copyIn: ctx.executeInteractor.copyIn, - time_limit_ms: ctxSubtask.subtask.time_limit_ms * 2, - memory_limit_mb: ctxSubtask.subtask.memory_limit_mb * 2, + time: ctxSubtask.subtask.time * 2, + memory: ctxSubtask.subtask.memory * 2, }, ]); let status; let score = 0; let message: any = ''; - if (time_usage_ms > ctxSubtask.subtask.time_limit_ms) { + if (time_usage_ms > ctxSubtask.subtask.time) { status = STATUS.STATUS_TIME_LIMIT_EXCEEDED; - } else if (memory_usage_kb > ctxSubtask.subtask.memory_limit_mb * 1024) { + } else if (memory_usage_kb > ctxSubtask.subtask.memory * 1024) { status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED; } else if (code) { status = STATUS.STATUS_RUNTIME_ERROR; diff --git a/packages/hydrojudge/src/judge/run.ts b/packages/hydrojudge/src/judge/run.ts index 6cd04efd6..f80fd4f3e 100644 --- a/packages/hydrojudge/src/judge/run.ts +++ b/packages/hydrojudge/src/judge/run.ts @@ -69,8 +69,8 @@ export const judge = async (ctx) => { stderr, copyIn, copyOut, - time_limit_ms: parseTimeMS(ctx.config.time || '1s'), - memory_limit_mb: parseMemoryMB(ctx.config.memory || '128m'), + time: parseTimeMS(ctx.config.time || '1s'), + memory: parseMemoryMB(ctx.config.memory || '128m'), }, ); const { code, time_usage_ms, memory_usage_kb } = res; diff --git a/packages/hydrojudge/src/sandbox.ts b/packages/hydrojudge/src/sandbox.ts index 64a2e1674..7da948604 100644 --- a/packages/hydrojudge/src/sandbox.ts +++ b/packages/hydrojudge/src/sandbox.ts @@ -25,8 +25,8 @@ const statusMap = { function proc({ execute = '', - time_limit_ms = 16000, - memory_limit_mb = 1024, + time = 16000, + memory = 1024, process_limit = 32, stdin = '', copyIn = {}, copyOut = [], copyOutCached = [], } = {}) { @@ -39,9 +39,9 @@ function proc({ { name: 'stdout', max: 1024 * 1024 * size }, { name: 'stderr', max: 1024 * 1024 * size }, ], - cpuLimit: time_limit_ms * 1000 * 1000, - realCpuLimit: time_limit_ms * 3000 * 1000, - memoryLimit: memory_limit_mb * 1024 * 1024, + cpuLimit: time * 1000 * 1000, + realCpuLimit: time * 3000 * 1000, + memoryLimit: memory * 1024 * 1024, procLimit: process_limit, copyIn, copyOut, @@ -58,7 +58,7 @@ async function adaptResult(result, params) { files: result.files, code: result.exitStatus, }; - if (ret.time_usage_ms >= (params.time_limit_ms || 16000)) { + if (ret.time_usage_ms >= (params.time || 16000)) { ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED; } ret.files = result.files || {}; diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 95d3427f1..43ad1ba17 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.21.19", + "version": "2.22.0", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 50584f275..da6d501cc 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -11,6 +11,7 @@ import { import paginate from '../lib/paginate'; import { isPid } from '../lib/validator'; import difficultyAlgorithm from '../lib/difficulty'; +import { parseConfig } from '../lib/testdataConfig'; import * as system from '../model/system'; import problem from '../model/problem'; import record from '../model/record'; @@ -189,6 +190,12 @@ export class ProblemDetailHandler extends ProblemHandler { // e.g. ![img](a.jpg) will navigate to ![img](./pid/file/a.jpg) this.response.body.pdoc.content = this.response.body.pdoc.content .replace(/\(file:\/\//g, `(./${this.pdoc.docId}/file/`); + // Get time and memory limit + try { + this.response.body.pdoc.config = await parseConfig(this.pdoc.config); + } catch (e) { + this.response.body.pdoc.config = `Cannot parse: ${e.message}`; + } } @param('pid', Types.UnsignedInt) diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 713a90040..ae6e19ff2 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -189,7 +189,6 @@ declare module './model/problem' { data: FileInfo[], additional_file: FileInfo[], hidden: boolean, - acMsg?: string, html?: boolean, difficulty?: number, /** @deprecated */ diff --git a/packages/hydrooj/src/lib/testdataConfig.ts b/packages/hydrooj/src/lib/testdataConfig.ts index 226d96b99..7d16e4d4b 100644 --- a/packages/hydrooj/src/lib/testdataConfig.ts +++ b/packages/hydrooj/src/lib/testdataConfig.ts @@ -21,24 +21,26 @@ export async function parseConfig(config: string | ProblemConfig) { let cfg: ProblemConfig = {}; if (typeof config === 'string') { // TODO should validate here? - cfg = readYamlCases(load(config) as Record) as ProblemConfig; + cfg = await readYamlCases(load(config) as Record) as ProblemConfig; } else cfg = config; - if (cfg.cases) { - for (const c of cfg.cases) { - result.memoryMax = Math.max(result.memoryMax, c.memory); - result.memoryMin = Math.min(result.memoryMin, c.memory); - result.timeMax = Math.max(result.timeMax, c.time); - result.timeMin = Math.min(result.timeMin, c.time); - result.count++; - } - } else if (cfg.subtasks) { + if (cfg.subtasks.length) { for (const subtask of cfg.subtasks) { result.memoryMax = Math.max(result.memoryMax, subtask.memory); result.memoryMin = Math.min(result.memoryMin, subtask.memory); result.timeMax = Math.max(result.timeMax, subtask.time); result.timeMin = Math.min(result.timeMin, subtask.time); } + } else if (cfg.time || cfg.memory) { + if (cfg.time) result.timeMax = result.timeMin = cfg.time as unknown as number; + if (cfg.memory) result.memoryMax = result.memoryMin = cfg.memory as unknown as number; } + if (result.memoryMax < result.memoryMin) { + result.memoryMax = result.memoryMin = 256; + } + if (result.timeMax < result.timeMin) { + result.timeMax = result.timeMin = 1000; + } + return result; } global.Hydro.lib.testdataConfig = { parseConfig }; diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index 60e9239f8..0645752c2 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -28,8 +28,7 @@ export class ProblemModel { static PROJECTION_PUBLIC: Field[] = [ ...ProblemModel.PROJECTION_LIST, - 'content', 'html', 'data', 'config', 'acMsg', - 'additional_file', + 'content', 'html', 'data', 'config', 'additional_file', ]; static extend(getter: Getter) { @@ -258,7 +257,6 @@ ProblemModel.extend((docId, pid) => ({ additional_file: [], hidden: true, config: '', - acMsg: '', difficulty: 0, })); diff --git a/packages/migrate-vijos/package.json b/packages/migrate-vijos/package.json index f29552922..5db7ddb9b 100644 --- a/packages/migrate-vijos/package.json +++ b/packages/migrate-vijos/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/migrate-vijos", - "version": "1.4.0", + "version": "1.4.1", "main": "package.json", "repository": "git@github.com:hydro-dev/Hydro.git", "author": "undefined ", diff --git a/packages/migrate-vijos/script.ts b/packages/migrate-vijos/script.ts index cef6a06ce..ed64baff6 100644 --- a/packages/migrate-vijos/script.ts +++ b/packages/migrate-vijos/script.ts @@ -74,7 +74,6 @@ const tasks = { return res; }, }, - ac_msg: 'acMsg', parent_doc_id: { field: 'parentId', processer: (parentId, doc) => { diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 44a6391bd..0e7fcb90a 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.5.14", + "version": "4.5.15", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/templates/partials/problem_sidebar_normal.html b/packages/ui-default/templates/partials/problem_sidebar_normal.html index c6b72068a..a8c21f984 100644 --- a/packages/ui-default/templates/partials/problem_sidebar_normal.html +++ b/packages/ui-default/templates/partials/problem_sidebar_normal.html @@ -126,10 +126,6 @@ {% else %} {{ _('(None)') }} {% endif %} - {% if pdoc['acMsg'] and pdoc['psdoc']['status'] == STATUS.STATUS_ACCEPTED %} -
{{ _('AC Message') }}
-
{{ pdoc['acMsg'] }}
- {% endif %} {% endif %} {% endif %} diff --git a/packages/ui-default/templates/problem_detail.html b/packages/ui-default/templates/problem_detail.html index e2218d718..7130a9538 100644 --- a/packages/ui-default/templates/problem_detail.html +++ b/packages/ui-default/templates/problem_detail.html @@ -25,7 +25,27 @@
-

{{ pdoc.title }}

+

+ {%- if rdoc -%} + + + {{ rdoc.score }} + + {%- endif %} + {{ pdoc.title }} +

+
+
+ {% if rdoc %} +

+ + + {{ rdoc.score }} + +

+ {% endif %} + {{ _('Time Limit') }}: {{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms + {{ _('Memory Limit') }}: {{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB
{% if tdoc %} diff --git a/packages/utils/lib/cases.ts b/packages/utils/lib/cases.ts index b13089d4c..431ef2f1d 100644 --- a/packages/utils/lib/cases.ts +++ b/packages/utils/lib/cases.ts @@ -35,8 +35,8 @@ export default async function readYamlCases(cfg: Record = {}, check } else if (cfg.cases) { config.subtasks = [{ score: parseInt(cfg.score, 10) || Math.floor(100 / cfg.cases.length), - time_limit_ms: parseTimeMS(cfg.time || '1s'), - memory_limit_mb: parseMemoryMB(cfg.memory || '512m'), + time: parseTimeMS(cfg.time || '1s'), + memory: parseMemoryMB(cfg.memory || '256m'), cases: [], type: 'sum', }]; @@ -63,11 +63,13 @@ export default async function readYamlCases(cfg: Record = {}, check score: parseInt(subtask.score, 10), if: subtask.if || [], cases, - time_limit_ms: parseTimeMS(subtask.time || cfg.time || '1s'), - memory_limit_mb: parseMemoryMB(subtask.memory || cfg.memory || '512m'), + time: parseTimeMS(subtask.time || cfg.time || '1s'), + memory: parseMemoryMB(subtask.memory || cfg.memory || '256m'), }); } } + if (cfg.time) config.time = parseTimeMS(cfg.time); + if (cfg.memory) config.memory = parseMemoryMB(cfg.memory); if (config.type === 'submit_answer' && !cfg.outputs) throw new Error('outputs config not found'); return Object.assign(cfg, config); } @@ -83,7 +85,7 @@ export function convertIniConfig(ini: string) { cases: [{ input: `input/${input.toLowerCase()}`, output: `output/${output.toLowerCase()}` }], score: parseInt(score, 10), time: `${time}s`, - memory: '512m', + memory: '256m', }; if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`; res.subtasks.push(cur); diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index 64f8a9b4a..197133026 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -161,13 +161,15 @@ const TIME_UNITS = { '': 1000, m: 1, u: 0.001 }; const MEMORY_RE = /^([0-9]+(?:\.[0-9]*)?)([kmg])b?$/i; const MEMORY_UNITS = { k: 1 / 1024, m: 1, g: 1024 }; -export function parseTimeMS(str: string) { +export function parseTimeMS(str: string | number) { + if (typeof str === 'number') return str; const match = TIME_RE.exec(str); if (!match) throw new Error(`${str} error parsing time`); return Math.floor(parseFloat(match[1]) * TIME_UNITS[match[2]]); } -export function parseMemoryMB(str: string) { +export function parseMemoryMB(str: string | number) { + if (typeof str === 'number') return str; const match = MEMORY_RE.exec(str); if (!match) throw new Error(`${str} error parsing memory`); return Math.ceil(parseFloat(match[1]) * MEMORY_UNITS[match[2]]); diff --git a/packages/utils/package.json b/packages/utils/package.json index 38c12437c..5142b2fef 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/utils", - "version": "1.0.1", + "version": "1.0.2", "description": "hydrooj utils", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git", From 90443546c8a0b63c8b1821dcbab5079fa6caac5a Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 11 Apr 2021 18:22:47 +0800 Subject: [PATCH 03/11] ui: add ProblemSelectAutoComplete --- packages/hydrooj/src/handler/problem.ts | 10 +++++ packages/hydrooj/src/model/problem.ts | 9 +++- packages/hydrooj/src/model/user.ts | 3 +- packages/hydrooj/test/db.ts | 2 +- .../autocomplete/ProblemSelectAutoComplete.js | 41 +++++++++++++++++++ .../autocomplete/autoComplete.page.js | 12 ++++++ .../problemselectautocomplete.page.styl | 6 +++ 7 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js create mode 100644 packages/ui-default/components/autocomplete/autoComplete.page.js create mode 100644 packages/ui-default/components/autocomplete/problemselectautocomplete.page.styl diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index da6d501cc..8f9f3dd2f 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -581,6 +581,15 @@ export class ProblemCreateHandler extends Handler { } } +export class ProblemPrefixListHandler extends Handler { + @param('prefix', Types.Name) + async get(domainId: string, prefix: string) { + this.response.body = { + pdocs: await problem.getPrefixList(domainId, prefix), + }; + } +} + export async function apply() { Route('problem_main', '/p', ProblemMainHandler, PERM.PERM_VIEW_PROBLEM); Route('problem_category', '/p/category/:category', ProblemCategoryHandler, PERM.PERM_VIEW_PROBLEM); @@ -596,6 +605,7 @@ export async function apply() { Route('problem_solution_raw', '/p/:pid/solution/:psid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM); Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM); Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM); + Route('problem_prefix_list', '/problem/list', ProblemPrefixListHandler, PERM.PERM_VIEW_PROBLEM); Connection('problem_pretest_conn', '/conn/pretest', ProblemPretestConnectionHandler, PERM.PERM_SUBMIT_PROBLEM); } diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index 0645752c2..27e630c07 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -1,6 +1,6 @@ import type { Readable } from 'stream'; import { ObjectID, FilterQuery } from 'mongodb'; -import { Dictionary, pick } from 'lodash'; +import { Dictionary, escapeRegExp, pick } from 'lodash'; import { STATUS } from './builtin'; import * as document from './document'; import domain from './domain'; @@ -208,6 +208,13 @@ export class ProblemModel { return Object.assign(r, l); } + static async getPrefixList(domainId: string, prefix: string) { + prefix = prefix.toLowerCase(); + const $regex = new RegExp(`\\A${escapeRegExp(prefix)}`, 'gmi'); + const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }] }; + return await document.getMulti(domainId, document.TYPE_PROBLEM, filter, ['domainId', 'docId', 'pid', 'title']).toArray(); + } + static async getListStatus(domainId: string, uid: number, pids: number[]) { const psdocs = await ProblemModel.getMultiStatus( domainId, { uid, docId: { $in: Array.from(new Set(pids)) } }, diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 407a2cc72..75373d6a0 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -1,4 +1,5 @@ import { Collection } from 'mongodb'; +import { escapeRegExp } from 'lodash'; import * as system from './system'; import token from './token'; import * as setting from './setting'; @@ -224,7 +225,7 @@ class UserModel { @ArgMethod static async getPrefixList(domainId: string, prefix: string, limit: number = 50) { prefix = prefix.toLowerCase(); - const $regex = new RegExp(`\\A\\Q${prefix}\\E`, 'gmi'); + const $regex = new RegExp(`\\A${escapeRegExp(prefix)}`, 'gmi'); const uids = await coll.find({ unameLower: { $regex } }) .limit(limit).map((doc) => doc._id).toArray(); const users = []; diff --git a/packages/hydrooj/test/db.ts b/packages/hydrooj/test/db.ts index 9812e8c99..cb58498c7 100644 --- a/packages/hydrooj/test/db.ts +++ b/packages/hydrooj/test/db.ts @@ -11,7 +11,7 @@ export async function connect() { while (dbVer < expected) { const func = scripts[dbVer]; dbVer++; - if (func.toString().includes('_FRESH_INSTALL_IGNORE')) continue; + if (typeof func !== 'function' || func.toString().includes('_FRESH_INSTALL_IGNORE')) continue; // eslint-disable-next-line no-await-in-loop await func(); } diff --git a/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js b/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js new file mode 100644 index 000000000..1eb3c194a --- /dev/null +++ b/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js @@ -0,0 +1,41 @@ +import _ from 'lodash'; +import tpl from 'vj/utils/tpl'; +import request from 'vj/utils/request'; +import DOMAttachedObject from 'vj/components/DOMAttachedObject'; +import AutoComplete from '.'; + +function getText(pdoc) { + return pdoc.docId; +} + +function getItems(val) { + return request.get('/problem/list', { q: val }); +} + +function renderItem(pdoc) { + return tpl` +
+
+
${pdoc.pid ? `${pdoc.pid}` : ''}${pdoc.title}
+
ID = ${pdoc.docId}
+
+
+ `; +} + +export default class ProblemSelectAutoComplete extends AutoComplete { + static DOMAttachKey = 'vjProblemSelectAutoCompleteInstance'; + + constructor($dom, options) { + super($dom, { + classes: 'problem-select', + items: getItems, + render: renderItem, + text: getText, + ...options, + }); + } +} + +_.assign(ProblemSelectAutoComplete, DOMAttachedObject); +window.Hydro.components.UserSelectAutoComplete = ProblemSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/autoComplete.page.js b/packages/ui-default/components/autocomplete/autoComplete.page.js new file mode 100644 index 000000000..5183d43fc --- /dev/null +++ b/packages/ui-default/components/autocomplete/autoComplete.page.js @@ -0,0 +1,12 @@ +import { AutoloadPage } from 'vj/misc/Page'; +import UserSelectAutoComplete from './UserSelectAutoComplete'; +import DomainSelectAutoComplete from './DomainSelectAutoComplete'; +import ProblemSelectAutoComplete from './ProblemSelectAutoComplete'; + +const page = new AutoloadPage('autocomplete', () => { + UserSelectAutoComplete.getOrConstruct($('[autocomplete="user"]')); + DomainSelectAutoComplete.getOrConstruct($('[autocomplete="domain"]')); + ProblemSelectAutoComplete.getOrConstruct($('[autocomplete="problem"]')); +}); + +export default page; diff --git a/packages/ui-default/components/autocomplete/problemselectautocomplete.page.styl b/packages/ui-default/components/autocomplete/problemselectautocomplete.page.styl new file mode 100644 index 000000000..d7910b617 --- /dev/null +++ b/packages/ui-default/components/autocomplete/problemselectautocomplete.page.styl @@ -0,0 +1,6 @@ +.menu.problem-select + width: rem(200px) + +.problem-select__uid + font-size: rem($font-size-small) + color: $userselect-uid-color From 5bd851028cfdc2610627c7e1587dcda33d5261ed Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Apr 2021 13:01:41 +0800 Subject: [PATCH 04/11] ui: move limits display to sidebar --- packages/hydrooj/src/handler/problem.ts | 11 ++++++++-- packages/ui-default/backendlib/template.js | 2 +- .../partials/problem_sidebar_normal.html | 4 ++++ .../ui-default/templates/problem_detail.html | 22 +++++-------------- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 8f9f3dd2f..1bad852ea 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -6,7 +6,7 @@ import { SolutionNotFoundError, ProblemNotFoundError, BadRequestError, } from '../error'; import { - Pdoc, User, Rdoc, PathComponent, + Pdoc, User, Rdoc, PathComponent, ProblemStatusDoc, } from '../interface'; import paginate from '../lib/paginate'; import { isPid } from '../lib/validator'; @@ -157,8 +157,12 @@ export class ProblemRandomHandler extends ProblemHandler { } } +export interface PdocWithPsdoc extends Pdoc { + psdoc?: ProblemStatusDoc +} + export class ProblemDetailHandler extends ProblemHandler { - pdoc: Pdoc; + pdoc: PdocWithPsdoc; udoc: User; @route('pid', Types.Name, true, null, parsePid) @@ -196,6 +200,9 @@ export class ProblemDetailHandler extends ProblemHandler { } catch (e) { this.response.body.pdoc.config = `Cannot parse: ${e.message}`; } + if (this.pdoc.psdoc) { + this.response.body.rdoc = await record.get(this.domainId, this.pdoc.psdoc.rid); + } } @param('pid', Types.UnsignedInt) diff --git a/packages/ui-default/backendlib/template.js b/packages/ui-default/backendlib/template.js index 5bbc511e2..ac2159461 100644 --- a/packages/ui-default/backendlib/template.js +++ b/packages/ui-default/backendlib/template.js @@ -88,7 +88,7 @@ class Loader extends nunjucks.Loader { }; } let fullpath = null; - if (typeof argv.template !== 'string') argv.template = path.resolve(__dirname, '..', 'template'); + if (typeof argv.template !== 'string') argv.template = path.resolve(__dirname, '..', 'templates'); const p = path.resolve(process.cwd(), argv.template, name); if (fs.existsSync(p)) fullpath = p; if (!fullpath) { diff --git a/packages/ui-default/templates/partials/problem_sidebar_normal.html b/packages/ui-default/templates/partials/problem_sidebar_normal.html index a8c21f984..81bb96ec3 100644 --- a/packages/ui-default/templates/partials/problem_sidebar_normal.html +++ b/packages/ui-default/templates/partials/problem_sidebar_normal.html @@ -114,6 +114,10 @@
{{ _('ID') }}
{{ pdoc.docId }}
+
{{ _('Time') }}
+
{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms
+
{{ _('Memory') }}
+
{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %} {% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
{{ _('Submission') }}
diff --git a/packages/ui-default/templates/problem_detail.html b/packages/ui-default/templates/problem_detail.html index 7130a9538..114271b51 100644 --- a/packages/ui-default/templates/problem_detail.html +++ b/packages/ui-default/templates/problem_detail.html @@ -24,28 +24,18 @@
-
-

- {%- if rdoc -%} - - - {{ rdoc.score }} - - {%- endif %} - {{ pdoc.title }} -

-
-
+
{% if rdoc %} -

+

- {{ rdoc.score }} + {{ 'Accepted' if rdoc.status == model.builtin.STATUS.STATUS_ACCEPTED else rdoc.score }}

{% endif %} - {{ _('Time Limit') }}: {{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms - {{ _('Memory Limit') }}: {{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB +

+ {{ pdoc.title }} +

{% if tdoc %} From b88b6ca046a3a9018ed438f59cb84fb7b5514856 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Apr 2021 13:22:13 +0800 Subject: [PATCH 05/11] ui: add ProblemSelectAutoComplete for contest_edit --- packages/hydrooj/src/handler/problem.ts | 4 +-- packages/ui-default/.eslintrc.js | 15 ++++---- .../autocomplete/ProblemSelectAutoComplete.js | 34 +++++++++---------- .../autocomplete/autoComplete.page.js | 12 ------- .../ui-default/pages/contest_edit.page.js | 9 +++++ .../ui-default/templates/components/form.html | 2 ++ .../ui-default/templates/contest_edit.html | 4 ++- 7 files changed, 39 insertions(+), 41 deletions(-) delete mode 100644 packages/ui-default/components/autocomplete/autoComplete.page.js create mode 100644 packages/ui-default/pages/contest_edit.page.js diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 1bad852ea..efab4ce4f 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -591,9 +591,7 @@ export class ProblemCreateHandler extends Handler { export class ProblemPrefixListHandler extends Handler { @param('prefix', Types.Name) async get(domainId: string, prefix: string) { - this.response.body = { - pdocs: await problem.getPrefixList(domainId, prefix), - }; + this.response.body = await problem.getPrefixList(domainId, prefix); } } diff --git a/packages/ui-default/.eslintrc.js b/packages/ui-default/.eslintrc.js index c542fe1b3..9c72d66ba 100644 --- a/packages/ui-default/.eslintrc.js +++ b/packages/ui-default/.eslintrc.js @@ -50,19 +50,18 @@ module.exports = { rules: { // FIXME A bug with eslint-parser 'template-curly-spacing': 'off', - indent: 'off', 'comma-dangle': [ 'error', 'always-multiline', ], - // indent: [ - // 'error', - // 2, - // { - // SwitchCase: 0, - // }, - // ], + indent: [ + 'error', + 2, + { + SwitchCase: 0, + }, + ], 'max-len': ['error', 150], quotes: 'warn', 'class-methods-use-this': 'off', diff --git a/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js b/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js index 1eb3c194a..6a3d89586 100644 --- a/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js +++ b/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.js @@ -2,21 +2,21 @@ import _ from 'lodash'; import tpl from 'vj/utils/tpl'; import request from 'vj/utils/request'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; -import AutoComplete from '.'; +import AutoComplete from './index'; function getText(pdoc) { - return pdoc.docId; + return pdoc.docId; } -function getItems(val) { - return request.get('/problem/list', { q: val }); +function getItems(prefix) { + return request.get('/problem/list', { prefix }); } function renderItem(pdoc) { - return tpl` + return tpl`
-
${pdoc.pid ? `${pdoc.pid}` : ''}${pdoc.title}
+
${pdoc.pid ? `${pdoc.pid} ` : ''}${pdoc.title}
ID = ${pdoc.docId}
@@ -24,18 +24,18 @@ function renderItem(pdoc) { } export default class ProblemSelectAutoComplete extends AutoComplete { - static DOMAttachKey = 'vjProblemSelectAutoCompleteInstance'; + static DOMAttachKey = 'vjProblemSelectAutoCompleteInstance'; - constructor($dom, options) { - super($dom, { - classes: 'problem-select', - items: getItems, - render: renderItem, - text: getText, - ...options, - }); - } + constructor($dom, options) { + super($dom, { + classes: 'problem-select', + items: getItems, + render: renderItem, + text: getText, + ...options, + }); + } } _.assign(ProblemSelectAutoComplete, DOMAttachedObject); -window.Hydro.components.UserSelectAutoComplete = ProblemSelectAutoComplete; +window.Hydro.components.ProblemSelectAutoComplete = ProblemSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/autoComplete.page.js b/packages/ui-default/components/autocomplete/autoComplete.page.js deleted file mode 100644 index 5183d43fc..000000000 --- a/packages/ui-default/components/autocomplete/autoComplete.page.js +++ /dev/null @@ -1,12 +0,0 @@ -import { AutoloadPage } from 'vj/misc/Page'; -import UserSelectAutoComplete from './UserSelectAutoComplete'; -import DomainSelectAutoComplete from './DomainSelectAutoComplete'; -import ProblemSelectAutoComplete from './ProblemSelectAutoComplete'; - -const page = new AutoloadPage('autocomplete', () => { - UserSelectAutoComplete.getOrConstruct($('[autocomplete="user"]')); - DomainSelectAutoComplete.getOrConstruct($('[autocomplete="domain"]')); - ProblemSelectAutoComplete.getOrConstruct($('[autocomplete="problem"]')); -}); - -export default page; diff --git a/packages/ui-default/pages/contest_edit.page.js b/packages/ui-default/pages/contest_edit.page.js new file mode 100644 index 000000000..f0649d573 --- /dev/null +++ b/packages/ui-default/pages/contest_edit.page.js @@ -0,0 +1,9 @@ +import { NamedPage } from 'vj/misc/Page'; +import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete'; + +const page = new NamedPage(['contest_edit', 'contest_create'], () => { + console.log($('[name="pids"]')); + ProblemSelectAutoComplete.getOrConstruct($('[name="pids"]')); +}); + +export default page; diff --git a/packages/ui-default/templates/components/form.html b/packages/ui-default/templates/components/form.html index 9be28d33c..226e5a185 100644 --- a/packages/ui-default/templates/components/form.html +++ b/packages/ui-default/templates/components/form.html @@ -47,6 +47,7 @@ {% set disabled = args.disabled or false %} {% set required = args.required or false %} {% set autofocus = args.autofocus or false %} +{% set noAutocomplete = args.noAutocomplete or false %} {% set extra_style = args.extra_style or false %} {% set date = args.date or false %} {% set time = args.time or false %} @@ -54,6 +55,7 @@ {% if disabled %}disabled{% endif %} {% if required %}required{% endif %} {% if autofocus %}autofocus data-autofocus{% endif %} + {% if noAutocomplete %}autocomplete="off"{% endif %} {% if date %}data-pick-date{% endif %} {% if time %}data-pick-time{% endif %} {% if extra_style %}style="{{extra_style}}"{% endif %} diff --git a/packages/ui-default/templates/contest_edit.html b/packages/ui-default/templates/contest_edit.html index 4cff1e971..c884467ba 100644 --- a/packages/ui-default/templates/contest_edit.html +++ b/packages/ui-default/templates/contest_edit.html @@ -62,7 +62,9 @@ columns:null, label:'Problems', name:'pids', - value:pids + value:pids, + noAutocomplete:true, + placeholder:_("Seperated with ','") }) }} {{ form.form_textarea({ columns:null, From e5c959dc2801a76ff2279a6384cd62d75678f73b Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Apr 2021 22:18:47 +0800 Subject: [PATCH 06/11] =?UTF-8?q?install:=20=E4=BD=BF=E7=94=A8=E6=9D=A5?= =?UTF-8?q?=E8=87=AAgitee=E7=9A=84nvm=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/centos-7.sh | 2 +- install/ubuntu-1604.sh | 2 +- install/ubuntu-1804.sh | 2 +- install/ubuntu-2004.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/install/centos-7.sh b/install/centos-7.sh index 7a69d2648..31b8488b6 100644 --- a/install/centos-7.sh +++ b/install/centos-7.sh @@ -30,7 +30,7 @@ sysctl -p # Install NodeJS echo "Installing NodeJS" -curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | bash +curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | sed 's/github.com\/nvm-sh\/nvm.git/gitee.com\/imirror\/nvm/g' | bash export NVM_DIR=/root/.nvm [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" nvm install 14 diff --git a/install/ubuntu-1604.sh b/install/ubuntu-1604.sh index ba97fe414..a540e1c37 100644 --- a/install/ubuntu-1604.sh +++ b/install/ubuntu-1604.sh @@ -17,7 +17,7 @@ mkdir -p /data/db /data/file # Install NodeJS echo "Installing NodeJS" -curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | bash +curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | sed 's/github.com\/nvm-sh\/nvm.git/gitee.com\/imirror\/nvm/g' | bash export NVM_DIR=/root/.nvm [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" NVM_NODEJS_ORG_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/nodejs-release nvm install 14 diff --git a/install/ubuntu-1804.sh b/install/ubuntu-1804.sh index 1dc8844cf..9e8803e5d 100644 --- a/install/ubuntu-1804.sh +++ b/install/ubuntu-1804.sh @@ -17,7 +17,7 @@ mkdir -p /data/db /data/file # Install NodeJS echo "Installing NodeJS" -curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | bash +curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | sed 's/github.com\/nvm-sh\/nvm.git/gitee.com\/imirror\/nvm/g' | bash export NVM_DIR=/root/.nvm [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" NVM_NODEJS_ORG_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/nodejs-release nvm install 14 diff --git a/install/ubuntu-2004.sh b/install/ubuntu-2004.sh index ea65ba33a..272309be2 100644 --- a/install/ubuntu-2004.sh +++ b/install/ubuntu-2004.sh @@ -17,7 +17,7 @@ mkdir -p /data/db /data/file # Install NodeJS echo "Installing NodeJS" -curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | bash +curl -o- https://cdn.jsdelivr.net/gh/nvm-sh/nvm@v0.36.0/install.sh | sed 's/raw.githubusercontent.com\/nvm-sh\/nvm\//cdn.jsdelivr.net\/gh\/nvm-sh\/nvm@/g' | sed 's/github.com\/nvm-sh\/nvm.git/gitee.com\/imirror\/nvm/g' | bash export NVM_DIR=/root/.nvm [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" NVM_NODEJS_ORG_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/nodejs-release nvm install 14 From 260261fcfe2f315828a9fafe1293f5a6f1012f8a Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Apr 2021 22:32:57 +0800 Subject: [PATCH 07/11] core: cli: allow calling with Arg::ObjectId --- packages/hydrooj/src/entry/cli.ts | 8 +++++++- packages/hydrooj/src/entry/master.ts | 8 +------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/hydrooj/src/entry/cli.ts b/packages/hydrooj/src/entry/cli.ts index f50efdddc..d93cde2f9 100644 --- a/packages/hydrooj/src/entry/cli.ts +++ b/packages/hydrooj/src/entry/cli.ts @@ -1,6 +1,7 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable import/no-dynamic-require */ import { argv } from 'yargs'; +import { ObjectID } from 'mongodb'; import { lib, service, model, builtinLib, builtinModel, @@ -21,7 +22,7 @@ function parseParameters(fn: Function) { } async function cli() { - const [, modelName, func, ...args] = argv._; + const [, modelName, func, ...args] = argv._ as [string, string, string, ...any[]]; if (!global.Hydro.model[modelName]) { return console.error(`Model ${modelName} doesn't exist.`); } @@ -45,6 +46,11 @@ async function cli() { console.error(`Too few arguments. Min ${parameterMin}`); return console.error(parameters.join(', ')); } + for (let i = 0; i < args.length; i++) { + if (ObjectID.isValid(args[i])) { + args[i] = new ObjectID(args[i]); + } + } let result = global.Hydro.model[modelName][func](...args); if (result instanceof Promise) result = await result; return console.log(result); diff --git a/packages/hydrooj/src/entry/master.ts b/packages/hydrooj/src/entry/master.ts index 407ddae53..6bca3532a 100644 --- a/packages/hydrooj/src/entry/master.ts +++ b/packages/hydrooj/src/entry/master.ts @@ -75,16 +75,10 @@ export async function load(call: Entry) { while (dbVer < expected) { logger.info('Upgrading database: from %d to %d', dbVer, expected); const func = scripts[dbVer]; - if (typeof func !== 'function') { + if (typeof func !== 'function' || (isFresh && func.toString().includes('_FRESH_INSTALL_IGNORE'))) { dbVer++; continue; } - if (isFresh) { - if (func.toString().includes('_FRESH_INSTALL_IGNORE')) { - dbVer++; - continue; - } - } const result = await func(); if (!result) break; dbVer++; From a2edf18737b57e7154bf3e6f332c5cee0c04a87a Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Apr 2021 23:02:53 +0800 Subject: [PATCH 08/11] core: allow run script via cli --- packages/hydrooj/src/entry/cli.ts | 34 +++++++++++++++++-------- packages/hydrooj/src/entry/master.ts | 9 +------ packages/hydrooj/src/entry/worker.ts | 9 +------ packages/hydrooj/src/model/system.ts | 2 +- packages/hydrooj/src/service/bus.ts | 2 +- packages/hydrooj/src/service/storage.ts | 19 +++++++------- 6 files changed, 36 insertions(+), 39 deletions(-) diff --git a/packages/hydrooj/src/entry/cli.ts b/packages/hydrooj/src/entry/cli.ts index d93cde2f9..1a4d7d806 100644 --- a/packages/hydrooj/src/entry/cli.ts +++ b/packages/hydrooj/src/entry/cli.ts @@ -3,10 +3,11 @@ import { argv } from 'yargs'; import { ObjectID } from 'mongodb'; import { - lib, service, model, - builtinLib, builtinModel, + lib, service, model, script, + builtinLib, builtinModel, builtinScript, } from './common'; import options from '../options'; +import { validate } from '../lib/validator'; import * as bus from '../service/bus'; import db from '../service/db'; @@ -21,8 +22,23 @@ function parseParameters(fn: Function) { return result === null ? [] : result; } +async function runScript(name: string, arg: any) { + if (!global.Hydro.script[name]) return console.error('Script %s not found.', name); + validate(global.Hydro.script[name].validate, arg); + return await global.Hydro.script[name].run(arg, console.info); +} + async function cli() { const [, modelName, func, ...args] = argv._ as [string, string, string, ...any[]]; + if (modelName === 'script') { + let arg: any; + try { + arg = JSON.parse(args.join(' ')); + } catch (e) { + return console.error('Invalid argument'); + } + return await runScript(func, arg); + } if (!global.Hydro.model[modelName]) { return console.error(`Model ${modelName} doesn't exist.`); } @@ -65,22 +81,18 @@ export async function load() { require('../options'); const opts = options(); await db.start(opts); - const modelSystem = require('../model/system'); - const [endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge] = modelSystem.getMany([ - 'file.endPoint', 'file.accessKey', 'file.secretKey', 'file.bucket', 'file.region', - 'file.endPointForUser', 'file.endPointForJudge', - ]); - const sopts = { - endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge, - }; const storage = require('../service/storage'); - await storage.start(sopts); + await storage.start(); for (const i of builtinLib) require(`../lib/${i}`); await lib(pending, fail); + const systemModel = require('../model/system'); + await systemModel.runConfig(); require('../service/gridfs'); await service(pending, fail); for (const i of builtinModel) require(`../model/${i}`); await model(pending, fail); + for (const i of builtinScript) require(`../script/${i}`); + await script(pending, fail, []); await bus.parallel('app/started'); await cli(); } diff --git a/packages/hydrooj/src/entry/master.ts b/packages/hydrooj/src/entry/master.ts index 6bca3532a..b6b19b252 100644 --- a/packages/hydrooj/src/entry/master.ts +++ b/packages/hydrooj/src/entry/master.ts @@ -57,15 +57,8 @@ export async function load(call: Entry) { await modelSystem.set('file.secretKey', process.env.MINIO_SECRET_KEY); await modelSystem.set('file.endPoint', 'http://localhost:9000/'); } - const [endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge] = modelSystem.getMany([ - 'file.endPoint', 'file.accessKey', 'file.secretKey', 'file.bucket', 'file.region', - 'file.endPointForUser', 'file.endPointForJudge', - ]); - const sopts = { - endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge, - }; const storage = require('../service/storage'); - await storage.start(sopts); + await storage.start(); require('../service/monitor'); for (const i of builtinModel) require(`../model/${i}`); const scripts = require('../upgrade').default; diff --git a/packages/hydrooj/src/entry/worker.ts b/packages/hydrooj/src/entry/worker.ts index 6b690c38d..2f4e5caf3 100644 --- a/packages/hydrooj/src/entry/worker.ts +++ b/packages/hydrooj/src/entry/worker.ts @@ -34,15 +34,8 @@ export async function load() { const modelSystem = require('../model/system'); await modelSystem.runConfig(); if (argv.loaderDetail) logger.info('finish: config'); - const [endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge] = modelSystem.getMany([ - 'file.endPoint', 'file.accessKey', 'file.secretKey', 'file.bucket', 'file.region', - 'file.endPointForUser', 'file.endPointForJudge', - ]); - const sopts = { - endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge, - }; const storage = require('../service/storage'); - storage.start(sopts); + await storage.start(); if (argv.loaderDetail) logger.info('finish: storage.connect'); for (const i of builtinLib) { let t; diff --git a/packages/hydrooj/src/model/system.ts b/packages/hydrooj/src/model/system.ts index 077b8eab8..9c1a41327 100644 --- a/packages/hydrooj/src/model/system.ts +++ b/packages/hydrooj/src/model/system.ts @@ -67,7 +67,7 @@ export async function runConfig() { } const config = await coll.find().toArray(); for (const i of config) cache[i._id] = i.value; - bus.emit('database/config'); + await bus.emit('database/config'); } bus.on('system/setting', (args) => { diff --git a/packages/hydrooj/src/service/bus.ts b/packages/hydrooj/src/service/bus.ts index 9aa051167..902b3cb3f 100644 --- a/packages/hydrooj/src/service/bus.ts +++ b/packages/hydrooj/src/service/bus.ts @@ -38,7 +38,7 @@ export interface EventMap extends Record { 'message/run': (command: string) => VoidReturn 'database/connect': (db: Db) => void - 'database/config': () => void + 'database/config': () => VoidReturn 'system/setting': (args: Record) => VoidReturn diff --git a/packages/hydrooj/src/service/storage.ts b/packages/hydrooj/src/service/storage.ts index 318ce3693..80d2cda9c 100644 --- a/packages/hydrooj/src/service/storage.ts +++ b/packages/hydrooj/src/service/storage.ts @@ -79,9 +79,15 @@ class StorageService { private opts: StorageOptions; private replaceWithAlternativeUrlFor: Record<'user' | 'judge', (originalUrl: string) => string>; - async start(opts: StorageOptions) { + async start() { try { - this.opts = opts; + const [endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge] = system.getMany([ + 'file.endPoint', 'file.accessKey', 'file.secretKey', 'file.bucket', 'file.region', + 'file.endPointForUser', 'file.endPointForJudge', + ]); + this.opts = { + endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge, + }; this.client = new Client({ ...parseMainEndpointUrl(this.opts.endPoint), accessKey: this.opts.accessKey, @@ -103,14 +109,7 @@ class StorageService { logger.warn('Storage init fail. will retry later.'); this.error = e.toString(); setTimeout(async () => { - const [endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge] = system.getMany([ - 'file.endPoint', 'file.accessKey', 'file.secretKey', 'file.bucket', 'file.region', - 'file.endPointForUser', 'file.endPointForJudge', - ]); - const sopts = { - endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge, - }; - await this.start(sopts); + await this.start(); }, 10000); } } From f21d1b0327f8ec342abfaf90357f654be2f80a78 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Apr 2021 23:15:25 +0800 Subject: [PATCH 09/11] ui: allow multi-select --- packages/ui-default/.eslintrc.js | 15 +++++------ .../components/autocomplete/index.js | 25 +++++++++---------- .../ui-default/pages/contest_edit.page.js | 3 +-- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/packages/ui-default/.eslintrc.js b/packages/ui-default/.eslintrc.js index 9c72d66ba..c542fe1b3 100644 --- a/packages/ui-default/.eslintrc.js +++ b/packages/ui-default/.eslintrc.js @@ -50,18 +50,19 @@ module.exports = { rules: { // FIXME A bug with eslint-parser 'template-curly-spacing': 'off', + indent: 'off', 'comma-dangle': [ 'error', 'always-multiline', ], - indent: [ - 'error', - 2, - { - SwitchCase: 0, - }, - ], + // indent: [ + // 'error', + // 2, + // { + // SwitchCase: 0, + // }, + // ], 'max-len': ['error', 150], quotes: 'warn', 'class-methods-use-this': 'off', diff --git a/packages/ui-default/components/autocomplete/index.js b/packages/ui-default/components/autocomplete/index.js index c9ca90a0e..6dc0c1f7f 100644 --- a/packages/ui-default/components/autocomplete/index.js +++ b/packages/ui-default/components/autocomplete/index.js @@ -22,6 +22,7 @@ export default class AutoComplete extends DOMAttachedObject { clearDefaultValue: true, position: 'bottom left', classes: '', + multi: false, ...options, }; this.clear(this.options.clearDefaultValue); @@ -46,9 +47,7 @@ export default class AutoComplete extends DOMAttachedObject { } clear(clearValue = true) { - if (clearValue) { - this.$dom.val(''); - } + if (clearValue) this.$dom.val(''); this._value = null; this.lastText = null; } @@ -80,19 +79,14 @@ export default class AutoComplete extends DOMAttachedObject { } onKeyUp(ev) { - if (ev.which === 27) { - // ESC + if (ev.which === 27 /* ESC */) { this.close(); return; } - if (this.$dom.val() === this.lastText) { - return; - } + if (this.$dom.val() === this.lastText) return; this.lastText = this.$dom.val(); this.updateOpenState(); - if (this.isOpen) { - this.renderList(); - } + if (this.isOpen) this.renderList(); } onMenuClick(ev) { @@ -109,7 +103,8 @@ export default class AutoComplete extends DOMAttachedObject { this._value = item; this.$dom.trigger('vjAutoCompleteSelect', item); if (text != null) { - this.$dom.val(text); + if (!this.options.multi) this.$dom.val(text); + else this.$dom.val(`${this.$dom.val() + text}, `); } this.close(); } @@ -141,7 +136,11 @@ export default class AutoComplete extends DOMAttachedObject { } async renderList() { - const val = this.$dom.val(); + let val = this.$dom.val(); + if (this.options.multi) { + const elems = val.split(',').map((i) => i.trim()); + val = elems[elems.length - 1]; + } const items = await this.getItems(val); const html = this.getHtml(items); this.currentItems = items; diff --git a/packages/ui-default/pages/contest_edit.page.js b/packages/ui-default/pages/contest_edit.page.js index f0649d573..a2ca69d19 100644 --- a/packages/ui-default/pages/contest_edit.page.js +++ b/packages/ui-default/pages/contest_edit.page.js @@ -2,8 +2,7 @@ import { NamedPage } from 'vj/misc/Page'; import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete'; const page = new NamedPage(['contest_edit', 'contest_create'], () => { - console.log($('[name="pids"]')); - ProblemSelectAutoComplete.getOrConstruct($('[name="pids"]')); + ProblemSelectAutoComplete.getOrConstruct($('[name="pids"]'), { multi: true }); }); export default page; From 1bb340f27abce8bdc4d0fb2577aa3898e39bf929 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Apr 2021 23:18:09 +0800 Subject: [PATCH 10/11] bump version --- packages/hydrojudge/package.json | 2 +- packages/hydrooj/package.json | 2 +- packages/import-qduoj/package.json | 4 ++-- packages/ui-default/package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 52297e21e..faa9d9f6e 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "2.5.0", + "version": "2.5.1", "os": [ "linux" ], diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 43ad1ba17..94beae1fd 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.22.0", + "version": "2.22.1", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/import-qduoj/package.json b/packages/import-qduoj/package.json index ba3622670..aec5ff718 100644 --- a/packages/import-qduoj/package.json +++ b/packages/import-qduoj/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/import-qduoj", - "version": "1.1.6", + "version": "1.1.7", "description": "Import QDUOJ problem export", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git", @@ -11,4 +11,4 @@ "decode-html": "^2.0.0", "fs-extra": "^9.1.0" } -} \ No newline at end of file +} diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 0e7fcb90a..8aec11bfe 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.5.15", + "version": "4.5.16", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", From 8a8e312ca41472e6e70eb265c92d80a477dd556c Mon Sep 17 00:00:00 2001 From: masnn Date: Tue, 13 Apr 2021 16:08:08 +0000 Subject: [PATCH 11/11] ui: allow deleting contest/homework via edit page --- packages/hydrooj/src/handler/contest.ts | 8 ++++++ packages/hydrooj/src/handler/homework.ts | 8 ++++++ .../ui-default/pages/contest_edit.page.js | 28 ++++++++++++++++++- .../ui-default/templates/contest_edit.html | 5 ++++ .../ui-default/templates/homework_edit.html | 5 ++++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 2660ec52d..209d373c0 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -96,6 +96,14 @@ class ContestDetailHandler extends ContestHandler { await contest.attend(domainId, tid, this.user._id); this.back(); } + + @param('tid', Types.ObjectID) + async postDelete(domainId: string, tid: ObjectID) { + const tdoc = await contest.get(domainId, tid); + if (tdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_CONTEST); + await contest.del(domainId, tid); + this.response.redirect = this.url('contest_main'); + } } class ContestBoardcastHandler extends ContestHandler { diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 72c5fc688..70026eb4d 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -108,6 +108,14 @@ class HomeworkDetailHandler extends HomeworkHandler { await contest.attend(domainId, tdoc.docId, this.user._id, document.TYPE_HOMEWORK); this.back(); } + + @param('tid', Types.ObjectID) + async postDelete(domainId: string, tid: ObjectID) { + const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK); + if (tdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_HOMEWORK); + await contest.del(domainId, tid, document.TYPE_HOMEWORK); + this.response.redirect = this.url('homework_main'); + } } class HomeworkDetailProblemHandler extends HomeworkHandler { diff --git a/packages/ui-default/pages/contest_edit.page.js b/packages/ui-default/pages/contest_edit.page.js index a2ca69d19..6aeeb3e67 100644 --- a/packages/ui-default/pages/contest_edit.page.js +++ b/packages/ui-default/pages/contest_edit.page.js @@ -1,8 +1,34 @@ import { NamedPage } from 'vj/misc/Page'; +import { ConfirmDialog } from 'vj/components/dialog'; import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete'; +import tpl from 'vj/utils/tpl'; +import i18n from 'vj/utils/i18n'; +import request from 'vj/utils/request'; -const page = new NamedPage(['contest_edit', 'contest_create'], () => { +const page = new NamedPage(['contest_edit', 'contest_create', 'homework_create', 'homework_edit'], (pagename) => { ProblemSelectAutoComplete.getOrConstruct($('[name="pids"]'), { multi: true }); + if (pagename.endsWith('edit')) { + let confirmed = false; + $(document).on('click', '[name="operation"]', (ev) => { + ev.preventDefault(); + if (confirmed) { + return request.post('.', { operation: 'delete' }).then((res) => { + window.location.href = res.url; + }); + } + const message = `Confirm deleting this ${pagename.split('_')[0]}? Its status will be deleted as well.`; + return new ConfirmDialog({ + $body: tpl` +
+

${i18n(message)}

+
`, + }).open().then((action) => { + if (action !== 'yes') return; + confirmed = true; + ev.target.click(); + }); + }); + } }); export default page; diff --git a/packages/ui-default/templates/contest_edit.html b/packages/ui-default/templates/contest_edit.html index c884467ba..7ca52e245 100644 --- a/packages/ui-default/templates/contest_edit.html +++ b/packages/ui-default/templates/contest_edit.html @@ -79,6 +79,11 @@ + {% if tdoc.owner == handler.user._id or handler.user.hasPerm(perm.PERM_EDIT_CONTEST) %} + + {% endif %} {% else %} + {% if tdoc.owner == handler.user._id or handler.user.hasPerm(perm.PERM_EDIT_HOMEWORK) %} + + {% endif %} {% else %}