mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-18 01:36:08 +08:00
core&ui: support bulk rejudge task
This commit is contained in:
10 files changed
+248
-1
No files matched your search
@@ -45,6 +45,7 @@ homework_main: Homework
|
||||
homework_scoreboard: Scoreboard
|
||||
judge_playground: Judge Playground
|
||||
main: Home
|
||||
manage_rejudge: Rejudge Management
|
||||
manage_config: System Configurations
|
||||
manage_dashboard: System
|
||||
manage_module: Module Management
|
||||
|
||||
@@ -465,6 +465,7 @@ Logout: 登出
|
||||
Lost Password: 忘记密码
|
||||
Lucky: 手气不错
|
||||
Mail From: 发件人
|
||||
manage_rejudge: 重测管理
|
||||
manage_dashboard: 控制面板
|
||||
manage_join_applications: 加域申请
|
||||
manage_module: 管理模块
|
||||
|
||||
@@ -252,6 +252,7 @@ Logout All Sessions: 登出所有會話
|
||||
Logout This Session: 登出該會話
|
||||
Logout: 登出
|
||||
Lost Password: 忘記密碼
|
||||
manage_rejudge: 重測管理
|
||||
manage_dashboard: 概況
|
||||
manage_domain: 管理域
|
||||
manage_edit: 編輯域資料
|
||||
|
||||
@@ -354,6 +354,72 @@ class SystemUserPrivHandler extends SystemHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class SystemRejudgeHandler extends SystemHandler {
|
||||
async get() {
|
||||
const rrdocs = await record.getMultiRejudgeTask({});
|
||||
this.response.body.rrdocs = rrdocs;
|
||||
this.response.template = 'manage_rejudge.html';
|
||||
}
|
||||
|
||||
@param('domainId', Types.String, true)
|
||||
@param('pid', Types.Int, true)
|
||||
@param('uid', Types.Int, true)
|
||||
@param('contest', Types.String, true)
|
||||
@param('lang', Types.String, true)
|
||||
@param('status', Types.Int, true)
|
||||
@param('apply', Types.Boolean)
|
||||
async post(domainId: string, pid: number, uid: number, contest: string, lang: string, status: number, _apply = false) {
|
||||
const rid = await record.add(domainId, -1, this.user._id, '-', 'rejudge', false, {
|
||||
input: JSON.stringify({
|
||||
pid, uid, contest, lang, status, apply: _apply,
|
||||
}),
|
||||
type: 'rejudge',
|
||||
});
|
||||
const args = global.Hydro.script['rejudge'].validate({
|
||||
rrid: rid.toHexString(),
|
||||
domainId,
|
||||
uid,
|
||||
pid,
|
||||
contest,
|
||||
lang,
|
||||
status,
|
||||
apply: _apply,
|
||||
});
|
||||
const report = (data) => judge.next({ domainId, rid, ...data });
|
||||
report({ message: 'Start rejudge', status: STATUS.STATUS_JUDGING });
|
||||
const start = Date.now();
|
||||
// Maybe async?
|
||||
global.Hydro.script['rejudge'].run(args, report)
|
||||
.then((ret: any) => {
|
||||
const time = new Date().getTime() - start;
|
||||
judge.end({
|
||||
domainId,
|
||||
rid: rid.toHexString(),
|
||||
status: STATUS.STATUS_ACCEPTED,
|
||||
message: inspect(ret, false, 10, true),
|
||||
judger: 1,
|
||||
time,
|
||||
memory: 0,
|
||||
});
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
const time = new Date().getTime() - start;
|
||||
logger.error(err);
|
||||
judge.end({
|
||||
domainId,
|
||||
rid: rid.toHexString(),
|
||||
status: STATUS.STATUS_SYSTEM_ERROR,
|
||||
message: `${err.message} \n${(err as any).params || []} \n${err.stack} `,
|
||||
judger: 1,
|
||||
time,
|
||||
memory: 0,
|
||||
});
|
||||
});
|
||||
this.response.body = { rid };
|
||||
this.response.redirect = this.url('record_detail', { rid });
|
||||
}
|
||||
}
|
||||
|
||||
export const inject = ['config', 'check'];
|
||||
export async function apply(ctx) {
|
||||
ctx.Route('manage', '/manage', SystemMainHandler);
|
||||
@@ -363,5 +429,6 @@ export async function apply(ctx) {
|
||||
ctx.Route('manage_config', '/manage/config', SystemConfigHandler);
|
||||
ctx.Route('manage_user_import', '/manage/userimport', SystemUserImportHandler);
|
||||
ctx.Route('manage_user_priv', '/manage/userpriv', SystemUserPrivHandler);
|
||||
ctx.Route('manage_rejudge', '/manage/rejudge', SystemRejudgeHandler);
|
||||
ctx.Connection('manage_check', '/manage/check-conn', SystemCheckConnHandler);
|
||||
}
|
||||
@@ -228,6 +228,25 @@ export interface RecordStatDoc {
|
||||
lang: string;
|
||||
}
|
||||
|
||||
export interface RecordRejudgeDoc {
|
||||
_id: ObjectId;
|
||||
owner: number;
|
||||
domainId: string;
|
||||
uid?: number;
|
||||
pid?: number;
|
||||
contest?: ObjectId;
|
||||
lang?: string;
|
||||
status?: number;
|
||||
apply: boolean;
|
||||
message: string;
|
||||
finishAt: Date;
|
||||
changes: {
|
||||
rid: ObjectId;
|
||||
old: number;
|
||||
new: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ScoreboardNode {
|
||||
type: 'string' | 'rank' | 'user' | 'email' | 'record' | 'records' | 'problem' | 'solved' | 'time' | 'total_score';
|
||||
value: string; // 显示分数
|
||||
@@ -534,6 +553,7 @@ declare module './service/db' {
|
||||
'record': RecordDoc;
|
||||
'record.stat': RecordStatDoc;
|
||||
'record.history': RecordHistoryDoc;
|
||||
'record.rejudge': RecordRejudgeDoc;
|
||||
'document': any;
|
||||
'document.status': StatusDocBase & {
|
||||
[K in keyof DocStatusType]: { docType: K } & DocStatusType[K];
|
||||
|
||||
@@ -63,6 +63,7 @@ inject('ControlPanel', 'manage_dashboard');
|
||||
inject('ControlPanel', 'manage_script');
|
||||
inject('ControlPanel', 'manage_user_import');
|
||||
inject('ControlPanel', 'manage_user_priv');
|
||||
inject('ControlPanel', 'manage_bulk_rejudge');
|
||||
inject('ControlPanel', 'manage_setting');
|
||||
inject('ControlPanel', 'manage_config');
|
||||
inject('DomainManage', 'domain_dashboard', { family: 'Properties', icon: 'info' });
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { ProblemConfigFile } from '@hydrooj/common';
|
||||
import { Context } from '../context';
|
||||
import { ProblemNotFoundError } from '../error';
|
||||
import { JudgeMeta, RecordDoc } from '../interface';
|
||||
import { JudgeMeta, RecordDoc, RecordRejudgeDoc } from '../interface';
|
||||
import db from '../service/db';
|
||||
import { MaybeArray, NumberKeys } from '../typeutils';
|
||||
import { ArgMethod, buildProjection, Time } from '../utils';
|
||||
@@ -21,6 +21,7 @@ export default class RecordModel {
|
||||
static coll = db.collection('record');
|
||||
static collStat = db.collection('record.stat');
|
||||
static collHistory = db.collection('record.history');
|
||||
static collRejudge = db.collection('record.rejudge');
|
||||
static PROJECTION_LIST: (keyof RecordDoc)[] = [
|
||||
'_id', 'score', 'time', 'memory', 'lang',
|
||||
'uid', 'pid', 'rejudged', 'progress', 'domainId',
|
||||
@@ -280,6 +281,26 @@ export default class RecordModel {
|
||||
for (const rdoc of rdocs) r[rdoc._id.toHexString()] = rdoc;
|
||||
return r;
|
||||
}
|
||||
|
||||
static async getMultiRejudgeTask(query: Filter<RecordRejudgeDoc>) {
|
||||
return RecordModel.collRejudge.find(query).toArray();
|
||||
}
|
||||
|
||||
static async getRejudgeTask(_id: ObjectId) {
|
||||
return RecordModel.collRejudge.findOne({ _id });
|
||||
}
|
||||
|
||||
static async addRejudgeTask(doc: RecordRejudgeDoc) {
|
||||
await RecordModel.collRejudge.insertOne(doc);
|
||||
}
|
||||
|
||||
static async pushRejudgeResult(rrid: ObjectId, result: { rid: ObjectId, old: number, new: number }) {
|
||||
await RecordModel.collRejudge.updateOne({ _id: rrid }, {
|
||||
$push: {
|
||||
changes: result,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply(ctx: Context) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Filter, ObjectId } from 'mongodb';
|
||||
import Schema from 'schemastery';
|
||||
import { Context } from '../context';
|
||||
import { RecordDoc } from '../interface';
|
||||
import { STATUS, STATUS_SHORT_TEXTS } from '../model/builtin';
|
||||
import record from '../model/record';
|
||||
|
||||
export const apply = (ctx: Context) => ctx.addScript(
|
||||
'rejudge', 'rejudge with filter',
|
||||
Schema.object({
|
||||
rrid: Schema.string(),
|
||||
domainId: Schema.string(),
|
||||
uid: Schema.number(),
|
||||
pid: Schema.number(),
|
||||
contest: Schema.string(),
|
||||
lang: Schema.string(),
|
||||
status: Schema.number(),
|
||||
apply: Schema.boolean(),
|
||||
}),
|
||||
async (arg, report) => {
|
||||
const q: Filter<RecordDoc> = { 'files.hack': { $exists: false } };
|
||||
for (const key of ['domainId', 'uid', 'pid', 'contest', 'lang', 'status']) {
|
||||
if (arg[key]) q[key] = arg[key];
|
||||
}
|
||||
q.contest ||= { $nin: [record.RECORD_GENERATE, record.RECORD_PRETEST] };
|
||||
q.status ||= { $ne: STATUS.STATUS_CANCELED };
|
||||
const rdocs = await record.getMulti(arg.domainId, q).project({ _id: 1, contest: 1, status: 1 }).toArray();
|
||||
const rdict = new Map(rdocs.map((rdoc) => [rdoc._id, rdoc.status]));
|
||||
report({ message: `Found ${rdocs.length} records` });
|
||||
ctx.on('record/change', async (rdoc: RecordDoc) => {
|
||||
if (rdict.has(rdoc._id)) {
|
||||
rdict.delete(rdoc._id);
|
||||
report({ message: `Rejudged ${rdoc._id}, ${STATUS_SHORT_TEXTS[rdict.get(rdoc._id)]} -> ${STATUS_SHORT_TEXTS[rdoc.status]}` });
|
||||
await record.pushRejudgeResult(new ObjectId(arg.rrid), { rid: rdoc._id, old: rdict.get(rdoc._id), new: rdoc.status });
|
||||
}
|
||||
});
|
||||
if (rdocs.length) {
|
||||
const priority = await record.submissionPriority(1, -10000 - rdocs.length * 5 - 50);
|
||||
if (arg.apply) {
|
||||
await record.reset(arg.domainId, rdocs.map((rdoc) => rdoc._id), true);
|
||||
}
|
||||
await Promise.all([
|
||||
record.judge(arg.domainId, rdocs.filter((i) => i.contest).map((i) => i._id),
|
||||
priority, { detail: false }, { rejudge: arg.apply ? true : 'controlled' }),
|
||||
record.judge(arg.domainId, rdocs.filter((i) => !i.contest).map((i) => i._id),
|
||||
priority, {}, { rejudge: arg.apply ? true : 'controlled' }),
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import $ from 'jquery';
|
||||
import CustomSelectAutoComplete from 'vj/components/autocomplete/CustomSelectAutoComplete';
|
||||
import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete';
|
||||
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
|
||||
import { NamedPage } from 'vj/misc/Page';
|
||||
|
||||
const page = new NamedPage('manage_bulk_rejudge', async () => {
|
||||
UserSelectAutoComplete.getOrConstruct($('[name="uidOrName"]'), {
|
||||
clearDefaultValue: false,
|
||||
});
|
||||
ProblemSelectAutoComplete.getOrConstruct($('[name="pid"]'), {
|
||||
clearDefaultValue: false,
|
||||
});
|
||||
const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
||||
const langs = Object.keys(window.LANGS).filter((i) => !prefixes.has(i)).map((i) => (
|
||||
{ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display}/` : ''}${window.LANGS[i].display}`, _id: i }
|
||||
));
|
||||
CustomSelectAutoComplete.getOrConstruct($('[name=lang]'), { multi: false, data: langs });
|
||||
});
|
||||
|
||||
export default page;
|
||||
@@ -0,0 +1,63 @@
|
||||
{% import "components/user.html" as user %}
|
||||
{% extends "manage_base.html" %}
|
||||
{% block manage_content %}
|
||||
<div class="section">
|
||||
<div class="section__header">
|
||||
<h1 class="section__title" data-heading>{{ _('Bulk Rejudge') }}</h1>
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="medium-6 columns">
|
||||
<label class="filter-user">
|
||||
{{ _('By Username / UID') }}
|
||||
<input name="uidOrName" type="text" class="textbox" value="" autocomplete="off" data-autofocus>
|
||||
</label>
|
||||
</div>
|
||||
<div class="medium-6 columns">
|
||||
<label>
|
||||
{{ _('By Problem') }}
|
||||
<input name="pid" type="text" class="textbox" value="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="medium-6 columns">
|
||||
<label>
|
||||
{{ _('By Contest') }}
|
||||
<input name="tid" type="text" class="textbox" value="">
|
||||
</label>
|
||||
</div>
|
||||
<div class="medium-6 columns">
|
||||
<label>
|
||||
{{ _('By Language') }}
|
||||
<input name="lang" type="text" class="textbox" value="">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="medium-6 columns">
|
||||
<label>
|
||||
{{ _('By Status') }}
|
||||
</label>
|
||||
<select class="select" name="status">
|
||||
<option value="" selected>{{ _('All Submissions') }}</option>
|
||||
{%- for k,v in utils.status.STATUS_TEXTS -%}
|
||||
<option value="{{k}}">{{v}}</option>
|
||||
{%- endfor -%}
|
||||
</select>
|
||||
</div>
|
||||
<div class="medium-6 columns">
|
||||
<label>
|
||||
{{ _('Apply') }}
|
||||
<input name="apply" type="checkbox" class="checkbox" value="1">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row"><div class="columns">
|
||||
<button name="submit" class="rounded primary button">{{ _('Rejudge') }}</button>
|
||||
</div></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user