mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core&ui: support contest record projection
This commit is contained in:
4 files changed
+104
-45
No files matched your search
@@ -85,7 +85,7 @@ class RecordListHandler extends ContestDetailBaseHandler {
|
|||||||
let cursor = record.getMulti(all ? '' : domainId, q).sort('_id', -1);
|
let cursor = record.getMulti(all ? '' : domainId, q).sort('_id', -1);
|
||||||
if (!full) cursor = cursor.project(buildProjection(record.PROJECTION_LIST));
|
if (!full) cursor = cursor.project(buildProjection(record.PROJECTION_LIST));
|
||||||
const limit = full ? 10 : system.get('pagination.record');
|
const limit = full ? 10 : system.get('pagination.record');
|
||||||
const rdocs = invalid
|
let rdocs = invalid
|
||||||
? [] as RecordDoc[]
|
? [] as RecordDoc[]
|
||||||
: await cursor.skip((page - 1) * limit).limit(limit).toArray();
|
: await cursor.skip((page - 1) * limit).limit(limit).toArray();
|
||||||
const canViewProblem = tid || this.user.hasPerm(PERM.PERM_VIEW_PROBLEM);
|
const canViewProblem = tid || this.user.hasPerm(PERM.PERM_VIEW_PROBLEM);
|
||||||
@@ -97,6 +97,9 @@ class RecordListHandler extends ContestDetailBaseHandler {
|
|||||||
? problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), canViewHiddenProblem, false, problem.PROJECTION_LIST)
|
? problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), canViewHiddenProblem, false, problem.PROJECTION_LIST)
|
||||||
: Object.fromEntries(uniqBy(rdocs, 'pid').map((rdoc) => [rdoc.pid, { ...problem.default, pid: rdoc.pid }])),
|
: Object.fromEntries(uniqBy(rdocs, 'pid').map((rdoc) => [rdoc.pid, { ...problem.default, pid: rdoc.pid }])),
|
||||||
]);
|
]);
|
||||||
|
if (this.tdoc && !this.user.own(this.tdoc) && !this.user.hasPerm(PERM.PERM_EDIT_CONTEST)) {
|
||||||
|
rdocs = rdocs.map((i) => contest.applyProjection(tdoc, i, this.user));
|
||||||
|
}
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
page,
|
page,
|
||||||
rdocs,
|
rdocs,
|
||||||
@@ -157,6 +160,9 @@ class RecordDetailHandler extends ContestDetailBaseHandler {
|
|||||||
if (!canView && rdoc.uid !== this.user._id) throw new PermissionError(rid);
|
if (!canView && rdoc.uid !== this.user._id) throw new PermissionError(rid);
|
||||||
canViewDetail = canView;
|
canViewDetail = canView;
|
||||||
this.args.tid = this.tdoc.docId;
|
this.args.tid = this.tdoc.docId;
|
||||||
|
if (!this.user.own(this.tdoc) && !this.user.hasPerm(PERM.PERM_EDIT_CONTEST)) {
|
||||||
|
this.rdoc = contest.applyProjection(this.tdoc, this.rdoc, this.user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
@@ -237,6 +243,7 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
status: number;
|
status: number;
|
||||||
pretest = false;
|
pretest = false;
|
||||||
tdoc: Tdoc<30>;
|
tdoc: Tdoc<30>;
|
||||||
|
applyProjection = false;
|
||||||
|
|
||||||
@param('tid', Types.ObjectId, true)
|
@param('tid', Types.ObjectId, true)
|
||||||
@param('pid', Types.ProblemId, true)
|
@param('pid', Types.ProblemId, true)
|
||||||
@@ -253,6 +260,9 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
if (!this.tdoc) throw new ContestNotFoundError(domainId, tid);
|
if (!this.tdoc) throw new ContestNotFoundError(domainId, tid);
|
||||||
if (pretest || contest.canShowScoreboard.call(this, this.tdoc, true)) this.tid = tid.toHexString();
|
if (pretest || contest.canShowScoreboard.call(this, this.tdoc, true)) this.tid = tid.toHexString();
|
||||||
else throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
else throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||||
|
if (!this.user.own(this.tdoc) && !this.user.hasPerm(PERM.PERM_EDIT_CONTEST)) {
|
||||||
|
this.applyProjection = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (pretest) {
|
if (pretest) {
|
||||||
this.pretest = true;
|
this.pretest = true;
|
||||||
@@ -312,6 +322,7 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
}
|
}
|
||||||
if (this.pretest) this.send({ rdoc: omit(rdoc, ['code', 'input']) });
|
if (this.pretest) this.send({ rdoc: omit(rdoc, ['code', 'input']) });
|
||||||
else {
|
else {
|
||||||
|
if (this.applyProjection) rdoc = contest.applyProjection(tdoc, rdoc, this.user);
|
||||||
this.send({
|
this.send({
|
||||||
html: await this.renderHTML('record_main_tr.html', {
|
html: await this.renderHTML('record_main_tr.html', {
|
||||||
rdoc, udoc, pdoc, tdoc, all: this.all,
|
rdoc, udoc, pdoc, tdoc, all: this.all,
|
||||||
@@ -323,20 +334,25 @@ class RecordMainConnectionHandler extends ConnectionHandler {
|
|||||||
|
|
||||||
class RecordDetailConnectionHandler extends ConnectionHandler {
|
class RecordDetailConnectionHandler extends ConnectionHandler {
|
||||||
pdoc: ProblemDoc;
|
pdoc: ProblemDoc;
|
||||||
|
tdoc?: Tdoc<30>;
|
||||||
rid: string = '';
|
rid: string = '';
|
||||||
disconnectTimeout: NodeJS.Timeout;
|
disconnectTimeout: NodeJS.Timeout;
|
||||||
throttleSend: any;
|
throttleSend: any;
|
||||||
|
applyProjection = false;
|
||||||
|
|
||||||
@param('rid', Types.ObjectId)
|
@param('rid', Types.ObjectId)
|
||||||
async prepare(domainId: string, rid: ObjectId) {
|
async prepare(domainId: string, rid: ObjectId) {
|
||||||
const rdoc = await record.get(domainId, rid);
|
const rdoc = await record.get(domainId, rid);
|
||||||
if (!rdoc) return;
|
if (!rdoc) return;
|
||||||
if (rdoc.contest && rdoc.input === undefined) {
|
if (rdoc.contest && rdoc.input === undefined) {
|
||||||
const tdoc = await contest.get(domainId, rdoc.contest);
|
this.tdoc = await contest.get(domainId, rdoc.contest);
|
||||||
let canView = this.user.own(tdoc);
|
let canView = this.user.own(this.tdoc);
|
||||||
canView ||= contest.canShowRecord.call(this, tdoc);
|
canView ||= contest.canShowRecord.call(this, this.tdoc);
|
||||||
canView ||= this.user._id === rdoc.uid && contest.canShowSelfRecord.call(this, tdoc);
|
canView ||= this.user._id === rdoc.uid && contest.canShowSelfRecord.call(this, this.tdoc);
|
||||||
if (!canView) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
if (!canView) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||||
|
if (!this.user.own(this.tdoc) && !this.user.hasPerm(PERM.PERM_EDIT_CONTEST)) {
|
||||||
|
this.applyProjection = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const [pdoc, self] = await Promise.all([
|
const [pdoc, self] = await Promise.all([
|
||||||
problem.get(rdoc.domainId, rdoc.pid),
|
problem.get(rdoc.domainId, rdoc.pid),
|
||||||
@@ -378,6 +394,7 @@ class RecordDetailConnectionHandler extends ConnectionHandler {
|
|||||||
clearTimeout(this.disconnectTimeout);
|
clearTimeout(this.disconnectTimeout);
|
||||||
this.disconnectTimeout = null;
|
this.disconnectTimeout = null;
|
||||||
}
|
}
|
||||||
|
if (this.applyProjection && rdoc.input === undefined) rdoc = contest.applyProjection(this.tdoc, rdoc, this.user);
|
||||||
// TODO: frontend doesn't support incremental update
|
// TODO: frontend doesn't support incremental update
|
||||||
// if ($set) this.send({ $set, $push });
|
// if ($set) this.send({ $set, $push });
|
||||||
if (![STATUS.STATUS_WAITING, STATUS.STATUS_JUDGING, STATUS.STATUS_COMPILING, STATUS.STATUS_FETCHED].includes(rdoc.status)) {
|
if (![STATUS.STATUS_WAITING, STATUS.STATUS_JUDGING, STATUS.STATUS_COMPILING, STATUS.STATUS_FETCHED].includes(rdoc.status)) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
ContestScoreboardHiddenError, ValidationError,
|
ContestScoreboardHiddenError, ValidationError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
import {
|
import {
|
||||||
BaseUserDict, ContestRule, ContestRules, ProblemDict,
|
BaseUserDict, ContestRule, ContestRules, ProblemDict, RecordDoc,
|
||||||
ScoreboardConfig, ScoreboardNode, ScoreboardRow, SubtaskResult, Tdoc,
|
ScoreboardConfig, ScoreboardNode, ScoreboardRow, SubtaskResult, Tdoc,
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
import ranked from '../lib/rank';
|
import ranked from '../lib/rank';
|
||||||
@@ -32,6 +32,43 @@ interface AcmDetail extends AcmJournal {
|
|||||||
real: number;
|
real: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isNew(tdoc: Tdoc, days = 1) {
|
||||||
|
const readyAt = tdoc.beginAt.getTime();
|
||||||
|
return Date.now() < readyAt - days * Time.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUpcoming(tdoc: Tdoc, days = 7) {
|
||||||
|
const now = Date.now();
|
||||||
|
const readyAt = tdoc.beginAt.getTime();
|
||||||
|
return (now > readyAt - days * Time.day && now < readyAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNotStarted(tdoc: Tdoc) {
|
||||||
|
return (new Date()) < tdoc.beginAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOngoing(tdoc: Tdoc, tsdoc?: any) {
|
||||||
|
const now = new Date();
|
||||||
|
if (tsdoc && tdoc.duration && tsdoc.startAt <= new Date(Date.now() - Math.floor(tdoc.duration * Time.hour))) return false;
|
||||||
|
return (tdoc.beginAt <= now && now < tdoc.endAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDone(tdoc: Tdoc, tsdoc?: any) {
|
||||||
|
if (tdoc.endAt <= new Date()) return true;
|
||||||
|
if (tsdoc && tdoc.duration && tsdoc.startAt <= new Date(Date.now() - Math.floor(tdoc.duration * Time.hour))) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLocked(tdoc: Tdoc, time = new Date()) {
|
||||||
|
if (!tdoc.lockAt) return false;
|
||||||
|
return tdoc.lockAt < time && !tdoc.unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isExtended(tdoc: Tdoc) {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
return tdoc.penaltySince.getTime() <= now && now < tdoc.endAt.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
function buildContestRule<T>(def: ContestRule<T>): ContestRule<T>;
|
function buildContestRule<T>(def: ContestRule<T>): ContestRule<T>;
|
||||||
function buildContestRule<T>(def: Partial<ContestRule<T>>, baseRule: ContestRule<T>): ContestRule<T>;
|
function buildContestRule<T>(def: Partial<ContestRule<T>>, baseRule: ContestRule<T>): ContestRule<T>;
|
||||||
function buildContestRule<T>(def: Partial<ContestRule<T>>, baseRule: ContestRule<T> = {} as any) {
|
function buildContestRule<T>(def: Partial<ContestRule<T>>, baseRule: ContestRule<T> = {} as any) {
|
||||||
@@ -217,6 +254,16 @@ const acm = buildContestRule({
|
|||||||
async ranked(tdoc, cursor) {
|
async ranked(tdoc, cursor) {
|
||||||
return await ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time);
|
return await ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time);
|
||||||
},
|
},
|
||||||
|
applyProjection(tdoc, rdoc) {
|
||||||
|
if (isDone(tdoc)) return rdoc;
|
||||||
|
delete rdoc.time;
|
||||||
|
delete rdoc.memory;
|
||||||
|
rdoc.testCases = [];
|
||||||
|
rdoc.judgeTexts = [];
|
||||||
|
delete rdoc.subtasks;
|
||||||
|
delete rdoc.score;
|
||||||
|
return rdoc;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const oi = buildContestRule({
|
const oi = buildContestRule({
|
||||||
@@ -367,6 +414,18 @@ const oi = buildContestRule({
|
|||||||
async ranked(tdoc, cursor) {
|
async ranked(tdoc, cursor) {
|
||||||
return await ranked(cursor, (a, b) => a.score === b.score);
|
return await ranked(cursor, (a, b) => a.score === b.score);
|
||||||
},
|
},
|
||||||
|
applyProjection(tdoc, rdoc) {
|
||||||
|
if (isDone(tdoc)) return rdoc;
|
||||||
|
delete rdoc.status;
|
||||||
|
rdoc.compilerTexts = [];
|
||||||
|
rdoc.judgeTexts = [];
|
||||||
|
delete rdoc.memory;
|
||||||
|
delete rdoc.time;
|
||||||
|
delete rdoc.score;
|
||||||
|
rdoc.testCases = [];
|
||||||
|
delete rdoc.subtasks;
|
||||||
|
return rdoc;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ioi = buildContestRule({
|
const ioi = buildContestRule({
|
||||||
@@ -376,6 +435,9 @@ const ioi = buildContestRule({
|
|||||||
showRecord: (tdoc, now) => now > tdoc.endAt && !isLocked(tdoc),
|
showRecord: (tdoc, now) => now > tdoc.endAt && !isLocked(tdoc),
|
||||||
showSelfRecord: () => true,
|
showSelfRecord: () => true,
|
||||||
showScoreboard: (tdoc, now) => now > tdoc.beginAt,
|
showScoreboard: (tdoc, now) => now > tdoc.beginAt,
|
||||||
|
applyProjection(_, rdoc) {
|
||||||
|
return rdoc;
|
||||||
|
},
|
||||||
}, oi);
|
}, oi);
|
||||||
|
|
||||||
const strictioi = buildContestRule({
|
const strictioi = buildContestRule({
|
||||||
@@ -501,6 +563,9 @@ const ledo = buildContestRule({
|
|||||||
}
|
}
|
||||||
return row;
|
return row;
|
||||||
},
|
},
|
||||||
|
applyProjection(_, rdoc) {
|
||||||
|
return rdoc;
|
||||||
|
},
|
||||||
}, oi);
|
}, oi);
|
||||||
|
|
||||||
const homework = buildContestRule({
|
const homework = buildContestRule({
|
||||||
@@ -650,6 +715,9 @@ const homework = buildContestRule({
|
|||||||
async ranked(tdoc, cursor) {
|
async ranked(tdoc, cursor) {
|
||||||
return await ranked(cursor, (a, b) => a.score === b.score);
|
return await ranked(cursor, (a, b) => a.score === b.score);
|
||||||
},
|
},
|
||||||
|
applyProjection(_, rdoc) {
|
||||||
|
return rdoc;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RULES: ContestRules = {
|
export const RULES: ContestRules = {
|
||||||
@@ -780,44 +848,6 @@ export function getMultiStatus(domainId: string, query: any) {
|
|||||||
return document.getMultiStatus(domainId, document.TYPE_CONTEST, query);
|
return document.getMultiStatus(domainId, document.TYPE_CONTEST, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNew(tdoc: Tdoc, days = 1) {
|
|
||||||
const now = new Date().getTime();
|
|
||||||
const readyAt = tdoc.beginAt.getTime();
|
|
||||||
return (now < readyAt - days * Time.day);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isUpcoming(tdoc: Tdoc, days = 7) {
|
|
||||||
const now = Date.now();
|
|
||||||
const readyAt = tdoc.beginAt.getTime();
|
|
||||||
return (now > readyAt - days * Time.day && now < readyAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNotStarted(tdoc: Tdoc) {
|
|
||||||
return (new Date()) < tdoc.beginAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isOngoing(tdoc: Tdoc, tsdoc?: any) {
|
|
||||||
const now = new Date();
|
|
||||||
if (tsdoc && tdoc.duration && tsdoc.startAt <= new Date(Date.now() - Math.floor(tdoc.duration * Time.hour))) return false;
|
|
||||||
return (tdoc.beginAt <= now && now < tdoc.endAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDone(tdoc: Tdoc, tsdoc?: any) {
|
|
||||||
if (tdoc.endAt <= new Date()) return true;
|
|
||||||
if (tsdoc && tdoc.duration && tsdoc.startAt <= new Date(Date.now() - Math.floor(tdoc.duration * Time.hour))) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isLocked(tdoc: Tdoc, time = new Date()) {
|
|
||||||
if (!tdoc.lockAt) return false;
|
|
||||||
return tdoc.lockAt < time && !tdoc.unlocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isExtended(tdoc: Tdoc) {
|
|
||||||
const now = new Date().getTime();
|
|
||||||
return tdoc.penaltySince.getTime() <= now && now < tdoc.endAt.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setStatus(domainId: string, tid: ObjectId, uid: number, $set: any) {
|
export function setStatus(domainId: string, tid: ObjectId, uid: number, $set: any) {
|
||||||
return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set);
|
return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set);
|
||||||
}
|
}
|
||||||
@@ -942,6 +972,11 @@ export function getMultiClarification(domainId: string, tid: ObjectId, owner = 0
|
|||||||
).sort('_id', -1).toArray();
|
).sort('_id', -1).toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyProjection(tdoc: Tdoc<30>, rdoc: RecordDoc, udoc: User) {
|
||||||
|
if (!RULES[tdoc.rule]) return rdoc;
|
||||||
|
return RULES[tdoc.rule].applyProjection(tdoc, rdoc, udoc);
|
||||||
|
}
|
||||||
|
|
||||||
export const statusText = (tdoc: Tdoc, tsdoc?: any) => (
|
export const statusText = (tdoc: Tdoc, tsdoc?: any) => (
|
||||||
isNew(tdoc)
|
isNew(tdoc)
|
||||||
? 'New'
|
? 'New'
|
||||||
@@ -990,5 +1025,6 @@ global.Hydro.model.contest = {
|
|||||||
isDone,
|
isDone,
|
||||||
isLocked,
|
isLocked,
|
||||||
isExtended,
|
isExtended,
|
||||||
|
applyProjection,
|
||||||
statusText,
|
statusText,
|
||||||
};
|
};
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
emulateAnchorClick, i18n, mongoId, substitute,
|
emulateAnchorClick, i18n, mongoId, substitute,
|
||||||
} from 'vj/utils';
|
} from 'vj/utils';
|
||||||
|
|
||||||
const shouldShowDetail = (data) => recordEnum.STATUS_SCRATCHPAD_SHOW_DETAIL_FLAGS[data.status];
|
const shouldShowDetail = (data) => recordEnum.STATUS_SCRATCHPAD_SHOW_DETAIL_FLAGS[data.status] && data.testCases?.length;
|
||||||
|
|
||||||
const getRecordDetail = (data) => {
|
const getRecordDetail = (data) => {
|
||||||
if (!shouldShowDetail(data)) {
|
if (!shouldShowDetail(data)) {
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<dl class="large horizontal" id="summary">
|
<dl class="large horizontal" id="summary">
|
||||||
|
{% if typeof(rdoc['score']) == 'number' %}
|
||||||
<dt>{{ _('Score') }}</dt>
|
<dt>{{ _('Score') }}</dt>
|
||||||
<dd>{{ rdoc['score'] }}</dd>
|
<dd>{{ rdoc['score'] }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
{% if rdoc['time'] %}
|
||||||
<dt>{{ _('Total Time') }}</dt>
|
<dt>{{ _('Total Time') }}</dt>
|
||||||
<dd>{% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ rdoc.time|round|int }}ms</dd>
|
<dd>{% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ rdoc.time|round|int }}ms</dd>
|
||||||
|
{% endif %}
|
||||||
|
{% if rdoc['memory'] %}
|
||||||
<dt>{{ _('Peak Memory') }}</dt>
|
<dt>{{ _('Peak Memory') }}</dt>
|
||||||
<dd>{% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ size(rdoc.memory, 1024) }}</dd>
|
<dd>{% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ size(rdoc.memory, 1024) }}</dd>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
Reference in new issue
Block a user