Browse Source

Finish submission page.

master
t123yh 7 years ago
parent
commit
df8ce50a7d
  1. 30
      modules/submission.js
  2. 4054
      package-lock.json
  3. 1
      package.json
  4. 3
      utility.js
  5. 287
      views/submission_content.ejs
  6. 68
      views/submissions.ejs
  7. 67
      views/submissions_item.ejs
  8. 1
      views/util.ejs

30
modules/submission.js

@ -23,6 +23,8 @@ let JudgeState = syzoj.model('judge_state');
let User = syzoj.model('user');
let Contest = syzoj.model('contest');
const jwt = require('jsonwebtoken');
app.get('/submissions', async (req, res) => {
try {
let user = await User.fromName(req.query.submitter || '');
@ -31,16 +33,18 @@ app.get('/submissions', async (req, res) => {
else if (req.query.submitter) where.user_id = -1;
let minScore = parseInt(req.query.min_score);
if (isNaN(minScore)) minScore = 0;
let maxScore = parseInt(req.query.max_score);
if (isNaN(maxScore)) maxScore = 100;
if (!isNaN(minScore) || !isNaN(maxScore)) {
if (isNaN(minScore)) minScore = 0;
if (isNaN(maxScore)) maxScore = 100;
where.score = {
$and: {
$gte: parseInt(minScore),
$lte: parseInt(maxScore)
}
};
}
if (req.query.language) {
if (req.query.language === 'submit-answer') where.language = '';
@ -75,7 +79,27 @@ app.get('/submissions', async (req, res) => {
await judge_state.forEachAsync(async obj => obj.allowedSeeData = await obj.isAllowedSeeDataBy(res.locals.user));
res.render('submissions', {
judge_state: judge_state,
// judge_state: judge_state,
items: judge_state.map((s) => ({
taskId: s.id,
user: s.user.username,
userId: s.user_id,
problemName: s.problem.title,
problemId: s.problem.id,
language: s.language != null ? syzoj.config.languages[s.language].show : null,
codeSize: s.allowedSeeCode ? syzoj.utils.formatSize(s.code.length) : null,
result: s.pending ? null : {
result: s.status,
time: s.total_time,
memory: s.max_memory,
score: s.score
},
submitTime: syzoj.utils.formatDate(s.submit_time),
token: s.pending ? jwt.sign({
taskId: s.id
}, syzoj.config.judge_token) : null,
running: false
})),
paginate: paginate,
form: req.query
});

4054
package-lock.json generated

File diff suppressed because it is too large Load Diff

1
package.json

@ -38,6 +38,7 @@
"fs-extra": "^2.1.2",
"gravatar": "^1.5.2",
"js-yaml": "^3.9.0",
"jsonwebtoken": "^7.4.3",
"moemark-renderer": "^1.2.6",
"moment": "^2.15.0",
"multer": "^1.2.0",

3
utility.js

@ -170,6 +170,9 @@ module.exports = {
else res.suffix = res.suffix.replace('iB', '');
return res.fixed + ' ' + res.suffix;
},
judgeServer(suffix) {
return JSON.stringify(url.resolve(syzoj.config.judge_server_addr, suffix));
},
parseDate(s) {
return parseInt(+new Date(s) / 1000);
},

287
views/submission_content.ejs

@ -1,286 +1,5 @@
<% include util %>
<%
// Sanitize judge results for backward compatibility and clarity
if (!judge.result.subtasks) {
judge.result.subtasks = [
{ case_num: judge.result.case_num, status: judge.status, score: judge.result.score }
];
for (let i = 0; i < judge.result.case_num; ++i) {
judge.result.subtasks[0][i] = judge.result[i];
}
}
let runningFound = false;
for (let s of judge.result.subtasks) {
s.pending = (s.status === 'Waiting' || s.status.startsWith('Running'));
for (let i = 0; i < s.case_num; ++i) if (!s[i]) {
s[i] = {
pending: true,
status: runningFound ? 'Waiting' : 'Running'
};
if (!runningFound) s.isActiveSubtask = true;
runningFound = true;
}
}
%>
<%
let problemUrl;
if (typeof contest !== 'undefined') problemUrl = syzoj.utils.makeUrl(['contest', contest.id, judge.problem_id]);
else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
%>
<div class="padding">
<table class="ui very basic center aligned table" id="status_table" style="visibility: hidden; ">
<thead>
<tr>
<th>编号</th>
<th>题目名称</th>
<th>状态</th>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && contest.type === 'acm') && !hideScore) { %>
<th>分数</th>
<% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<% if (!hideScore) { %>
<th>总时间</th>
<th>内存</th>
<% } %>
<th>代码</th>
<% } else { %>
<th>文件大小</th>
<% } %>
<th>提交者</th>
<th>提交时间</th>
<% if (judge.allowedRejudge) { %>
<th>重新评测</th>
<% } %>
</tr>
</thead>
<tbody>
<tr>
<td>#<%= judge.id %></td>
<td><a style="width: 230px; height: 22px; display: block; margin: 0 auto; line-height: 22px; " id="text-fit" href="<%= problemUrl %>"><%= (typeof contest !== 'undefined' && contest) ? this.alpha(judge.problem_id) : ('#' + judge.problem_id) %>. <%= judge.problem.title %></a></td>
<td class="status <%= getStatusMeta(judge.status).toLowerCase().split(' ').join('_') %>">
<i class="<%= icon[getStatusMeta(judge.status)] || 'remove' %> icon"></i>
<%= judge.status %>
</td>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && contest.type === 'acm') && !hideScore) { %>
<td class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></td>
<% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<% if (!hideScore) { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% } %>
<% if (judge.allowedSeeCode) { %>
<td><%= syzoj.config.languages[judge.language].show %> / <%= syzoj.utils.formatSize(judge.codeLength) %></td>
<% } else { %>
<td><%= syzoj.config.languages[judge.language].show %> / 隐藏 %></td>
<% } %>
<% } else { %>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %></td>
<% if (judge.allowedRejudge) { %>
<td>
<a id="rejudge-button" onclick="check_rejudge()" style="color: #000; " href="#"><i class="repeat icon"></i></a>
<div class="ui basic modal" id="modal-rejudge">
<div class="ui icon header">
<i class="retweet icon"></i>
<p style="margin-top: 15px; ">重新评测</p>
</div>
<div class="content" style="text-align: center; ">
<p>确认重新评测该提交记录吗?</p>
<p id="warning_pending"><strong>警告:只有管理员可以重新评测一个未评测完成的记录,<br>这种情况一般发生在评测服务中断后,如果一个提交正在被评测,<br>则将其重新评测会导致系统错乱!</strong></p>
<script>
var pending = <%= judge.pending %>;
function check_rejudge() {
if (pending) {
$('#warning_pending').css('display', '');
} else {
$('#warning_pending').css('display', 'none');
}
$('#modal-rejudge').modal('show');
}
</script>
</div>
<div class="actions">
<div class="ui red basic cancel inverted button">
<i class="remove icon"></i>
</div>
<a class="ui green ok inverted button" href-post="<%= syzoj.utils.makeUrl(['submission', judge.id, 'rejudge']) %>">
<i class="checkmark icon"></i>
</a>
</div>
</div>
</td>
<% } %>
</tr>
</tbody>
</table>
<script>
window.applyTextFit = function () {
var e = document.getElementById('text-fit');
textFit(e, { maxFontSize: 14 });
document.getElementById('status_table').style.visibility = '';
}
window.applyTextFit();
</script>
<% if (judge.problem.type !== 'submit-answer' && judge.allowedSeeCode) { %>
<div class="ui existing segment" style="position: relative; ">
<pre style="margin-top: 0; margin-bottom: 0; "><code id="code"><%- judge.code %></code></pre>
</div>
<script>
document.addEventListener('keydown', function (event) {
if ((event.ctrlKey || event.metaKey) && event.key === 'a') {
var sel = window.getSelection();
var rg = document.createRange()
rg.selectNodeContents(document.getElementById('code'));
sel.removeAllRanges();
sel.addRange(rg);
event.preventDefault();
}
});
</script>
<% } %>
<% if (judge.problem.type !== 'submit-answer' && judge.result.compiler_output && judge.status === 'Compile Error' && judge.allowedSeeCode) { %>
<h3 class="ui header">编译信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- syzoj.utils.ansiToHTML(judge.result.compiler_output) %></code></pre></div>
<% } else if (judge.result.message) { %>
<h3 class="ui header">系统调试信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= judge.result.message %></code></pre></div>
<% } else if (judge.result.spj_compiler_output) { %>
<h3 class="ui header"><%= judge.problem.type === 'interaction' ? '交互程序' : 'Special Judge ' %>编译信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- syzoj.utils.ansiToHTML(judge.result.spj_compiler_output) %></code></pre></div>
<% } else if (judge.allowedSeeCase && judge.result.subtasks && (judge.result.subtasks.length !== 1 || judge.result.subtasks[0].case_num)) { %>
<div class="ui styled fluid accordion" id="subtasks_list">
<% let subtask_count = 0; %>
<% for (let subtask_cases of (judge.result.subtasks || [])) { %>
<% if (judge.result.subtasks.length !== 1) { %>
<div class="title auto_update auto_update_activation<% if (subtask_cases.isActiveSubtask) { %> active<% } %>">
<div class="ui grid">
<div class="three wide column"><i class="dropdown icon"></i>子任务 #<%= ++subtask_count %></div>
<div class="four wide column status status_detail <%= getStatusMeta(subtask_cases.status).toLowerCase().split(' ').join('_') %>">
<i class="<%= icon[getStatusMeta(subtask_cases.status)] || 'remove' %> icon"></i>
<%= subtask_cases.status %></div>
<% if (!subtask_cases.pending) { %>
<div class="three wide column">得分:<span style="font-weight: normal; "><%= parseFloat(subtask_cases.score.toFixed(2)).toString() %></span></div>
<% } %>
</div>
</div>
<div class="content auto_update_activation<% if (subtask_cases.isActiveSubtask) { %> active<% } %>">
<div class="accordion" style="margin-top: 0; ">
<% } %>
<% for (let i = 0; i < subtask_cases.case_num; i++) { %>
<% let testcase = subtask_cases[i]; %>
<div class="title<% if (testcase.pending || !judge.allowedSeeData || testcase.status === 'Skipped') { %> pending<% } %> auto_update"<% if (testcase.pending || !judge.allowedSeeData) { %> style="cursor: auto; "<% } %>>
<div class="ui grid">
<div class="<%= judge.problem.type === 'submit-answer' ? 'four' : 'three' %> wide column"><i class="dropdown icon"></i>测试点 #<%= i + 1 %></div>
<div class="four wide column status status_detail <%= getStatusMeta(testcase.status).toLowerCase().split(' ').join('_') %>">
<i class="<%= icon[getStatusMeta(testcase.status)] || 'remove' %> icon"></i>
<%= testcase.status %></div>
<% if (!testcase.pending && testcase.status !== 'Skipped') { %>
<% if (!testcase.score) testcase.score = testcase.status === 'Accepted' ? 100 : 0; %>
<% if (judge.problem.type === 'submit-answer') { %>
<% if (testcase.length != null) { %>
<div class="three wide column">文件大小:<span style="font-weight: normal; "><%= syzoj.utils.formatSize(testcase.length) %></span></div>
<% } %>
<% } else { %>
<div class="three wide column">得分:<span style="font-weight: normal; "><%= parseFloat(testcase.score.toFixed(2)).toString() %></span></div>
<div class="three wide column">用时:<span style="font-weight: normal; "><%= testcase.time_used %> ms</span></div>
<div class="three wide column">内存:<span style="font-weight: normal; "><%= testcase.memory_used %> KiB</span></div>
<% } %>
<% } %>
</div>
</div>
<div class="content auto_update">
<% if (!testcase.pending && judge.allowedSeeData && testcase.status !== 'Skipped') { %>
<p>
<strong>输入文件<% if (testcase.input_file_name) { %>(<span style="font-family: monospace; "><%= testcase.input_file_name %></span>)<% } %></strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.input %></code></pre></div>
<% if (judge.problem.type !== 'interaction') { %>
<strong>输出文件<% if (testcase.output_file_name) { %>(<span style="font-family: monospace; "><%= testcase.output_file_name %></span>)<% } %></strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.answer %></code></pre></div>
<strong>选手输出</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.user_out %></code></pre></div>
<% } %>
<% if (testcase.spj_message) { %>
<strong><%= judge.problem.type === 'interaction' ? '交互程序' : 'Special Judge ' %>信息</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.spj_message %></code></pre></div>
<% } %>
<% if (testcase.user_err) { %>
<strong>标准错误</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.user_err %></code></pre></div>
<% } %>
<% if (testcase.runner_message && judge.allowedManage) { %>
<strong>调试信息</strong>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%= testcase.runner_message %></code></pre></div>
<% } %>
</p>
<% } %>
</div>
<% } %>
<% if (judge.result.subtasks.length !== 1) { %>
</div></div>
<% } %>
<% } %>
</div>
<% } %>
</div>
<script>
$(function() {
$('.ui.accordion').accordion({
selector: {
trigger: '.title:not(.pending)'
}
});
});
</script>
<% if (!isPending(judge.status)) { %><div><div id="not_pending"></div></div><% } %>
<script>
<% if (isPending(judge.status)) { %>
document.addEventListener('mousedown', function (event) {
var list = $('#submission_content').find('div.auto_update_activation');
for (var i = 0; i < list.length; ++i) {
$(list[i]).removeClass('auto_update_activation');
}
});
function update_submission() {
setTimeout(function () {
$.get('/submission/<%= judge.id %>/ajax', function (data) {
var e = $('#submission_content'), x = $($.parseHTML(data));
if (e.find('td.status').text().trim() != x.find('td.status').text().trim()) {
var a = e.find('div.auto_update');
if (!a.length) {
e.html(data);
window.applyTextFit();
} else {
e.find('#status_table').html(x.find('#status_table').html());
var b = x.find('div.auto_update');
for (var i = 0; i < a.length; i++) {
a[i].innerHTML = b[i].innerHTML;
if (!$(b[i]).hasClass('pending')) $(a[i]).removeClass('pending');
if (!$(b[i]).attr('style')) $(a[i]).attr('style', '');
}
a = e.find('div.auto_update_activation');
b = x.find('div.auto_update_activation');
for (var i = 0; i < a.length; i++) {
if ($(b[i]).hasClass('active')) $(a[i]).addClass('active'); else $(a[i]).removeClass('active');
}
window.applyTextFit();
if (!x.find('#not_pending').length) update_submission();
else pending = false;
}
}
else if (!x.find('#not_pending').length) update_submission();
});
}, 500);
}
update_submission();
<% } %>
</script>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js"></script>
<script src="https://cdn.bootcss.com/jsondiffpatch/0.2.4/jsondiffpatch.js"></script>

68
views/submissions.ejs

@ -63,7 +63,7 @@
<% } %>
</div>
</form>
<table class="ui very basic center aligned table" style="white-space: nowrap; visibility: hidden; " id="table">
<table id="vueApp" class="ui very basic center aligned table" style="white-space: nowrap; " id="table">
<thead>
<tr>
<th>编号</th>
@ -78,44 +78,68 @@
</tr>
</thead>
<tbody>
<% let ids = []; %>
<% for (let judge of judge_state) { %>
<% if (judge.pending) ids.push(judge.id); %>
<tr id="submissions_<%= judge.id %>"><% include submissions_item %></tr>
<% } %>
<tr v-for="item in items" is='submission-item' v-bind:data="item">
<fake a/> <%# <-- a temporary workaround for a syntaxing bug by Visual Studio Code ejs plugin %>
</tr>
</tbody>
</table>
<script>
document.getElementById('table').style.visibility = '';
// document.getElementById('table').style.visibility = '';
</script>
<br>
<% include page %>
</div>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js"></script>
<% include submissions_item %>
<script>
$(function () {
$('#select_language').dropdown();
$('#select_status').dropdown();
});
const itemList = <%- JSON.stringify(items) %>;
const socketUrl = <%- syzoj.utils.judgeServer('rough') %>;
const vueApp = new Vue({
el: '#vueApp',
data: {
items: itemList
},
});
var ids = <%= JSON.stringify(ids) %>;
function update() {
setTimeout(function () {
$.get('/submissions/' + ids.join(',') + '/ajax', function (data) {
var newIDs = [];
for (var id in data) {
if (data[id].pending) newIDs.push(id);
var e = $('#submissions_' + id);
if (e.find('span.status').text().trim() !== data[id].status) e.html(data[id].html);
if (itemList.some(t => t.token != null)) {
const socket = io(socketUrl);
socket.on('connect', () => {
for (let x of itemList.filter(x => x.token != null)){
socket.emit('join', x.token, (data) => {
if (data && data.ok) {
if (data.running) {
x.running = true;
} else if (data.finished) {
x.running = false;
x.result = data.result;
}
} else {
alert("ERROR: " + JSON.stringify(data));
}
});
const getItem = (id) => itemList.find(x => x.taskId === id);
socket.on('start', (data) => {
getItem(data.taskId).running = true;
});
if (newIDs.length > 0) {
ids = newIDs;
update();
socket.on('finish', (data) => {
getItem(data.taskId).running = false;
getItem(data.taskId).result = data.result;
if (itemList.every(x => x.result)) {
socket.close();
}
});
}, 500);
}
update();
})
}
</script>
<% include footer %>

67
views/submissions_item.ejs

@ -1,36 +1,37 @@
<% include util %>
<%
let problemUrl;
if (typeof contest !== 'undefined' && contest) problemUrl = syzoj.utils.makeUrl(['contest', contest.id, judge.problem_id]);
else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
%>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>">#<%= judge.id %></a></td>
<td><a style="width: 230px; height: 22px; display: block; margin: 0 auto; line-height: 22px; " id="text-fit-<%= judge.id %>" href="<%= problemUrl %>"><%= (typeof contest !== 'undefined' && contest) ? this.alpha(judge.problem_id) : ('#' + judge.problem_id) %>. <%= judge.problem.title %></a></td>
<script>
var e = document.getElementById('text-fit-<%= judge.id %>');
textFit(e, { maxFontSize: 14 });
Vue.component('submission-item', {
template: '#submissionItemTemplate',
props: ['data'],
computed: {
statusString: function() {
const data = this.data;
if (data.result) {
return data.result.result;
}
else if (data.running) return 'Running';
else return 'Waiting';
}
}
});
</script>
<script id="submissionItemTemplate" type="text/x-template">
<tr>
<td>#{{ data.taskId }}</td>
<td>#{{ data.problemId }}. {{ data.problemName }}</td>
<td>{{ statusString }}</td>
<template v-if="data.result">
<td>{{ (data.result.score != null && data.result.score !== NaN) ? Math.floor(data.result.score) : '' }}</td>
<td>{{ (data.result.time != null && data.result.time !== NaN) ? data.result.time.toString() + ' ms' : '' }}</td>
<td>{{ (data.result.memory != null && data.result.memory !== NaN) ? data.result.memory.toString() + ' KiB' : '' }}</td>
</template>
<template v-else>
<td /> <td /> <td />
</template>
<td>{{ data.language != null ? data.language + ' / ' : '' }}{{ data.codeSize }}</td>
<td>{{ data.user }}</td>
<td>{{ data.submitTime }}</td>
</tr>
</script>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>">
<span class="status <%= getStatusMeta(judge.status).toLowerCase().split(' ').join('_') %>">
<i class="<%= icon[getStatusMeta(judge.status)] || 'remove' %> icon"></i>
<%= judge.status %>
</span>
</a></td>
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.score / 10) || 0 %>"><%= judge.score %></span></a></td>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.total_time %> ms</td>
<td><%= parseInt(judge.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<% } else { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / 隐藏</td>
<% } %>
<% } else { %>
<td>-</td><td>-</td>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %>
</td>

1
views/util.ejs

@ -43,4 +43,5 @@ this.iconHidden = [
'Success',
'Submitted'
];
%>

Loading…
Cancel
Save