Browse Source

Finish submission content.

master
t123yh 7 years ago
parent
commit
61c4d350e7
  1. 8
      models/judge_state.js
  2. 101
      modules/submission.js
  3. 43
      views/status_label.ejs
  4. 2
      views/submission.ejs
  5. 234
      views/submission_content.ejs
  6. 32
      views/submissions.ejs
  7. 33
      views/submissions_item.ejs

8
models/judge_state.js

@ -92,11 +92,11 @@ class JudgeState extends Model {
pending: true, pending: true,
score: 0, score: null,
total_time: 0, total_time: null,
max_memory: 0, max_memory: null,
status: 'Waiting', status: 'Waiting',
result: '{ "status": "Waiting", "total_time": 0, "max_memory": 0, "score": 0, "case_num": 0, "compiler_output": "", "pending": true }' result: null
}, val))); }, val)));
} }

101
modules/submission.js

@ -25,6 +25,25 @@ let Contest = syzoj.model('contest');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
// s is JudgeState
const getSubmissionInfo = (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,
submitTime: syzoj.utils.formatDate(s.submit_time),
});
const getRoughResult = (x) => (x.pending ? null : {
result: x.status,
time: x.total_time,
memory: x.max_memory,
score: x.score
});
app.get('/submissions', async (req, res) => { app.get('/submissions', async (req, res) => {
try { try {
let user = await User.fromName(req.query.submitter || ''); let user = await User.fromName(req.query.submitter || '');
@ -80,24 +99,13 @@ app.get('/submissions', async (req, res) => {
res.render('submissions', { res.render('submissions', {
// judge_state: judge_state, // judge_state: judge_state,
items: judge_state.map((s) => ({ items: judge_state.map(x => ({
taskId: s.id, info: getSubmissionInfo(x),
user: s.user.username, token: x.pending ? jwt.sign({
userId: s.user_id, taskId: x.id,
problemName: s.problem.title, type: 'rough'
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, }, syzoj.config.judge_token) : null,
result: getRoughResult(x),
running: false running: false
})), })),
paginate: paginate, paginate: paginate,
@ -111,54 +119,6 @@ app.get('/submissions', async (req, res) => {
} }
}); });
app.get('/submissions/:ids/ajax', async (req, res) => {
try {
let ids = req.params.ids.split(','), rendered = {};
for (let id of ids) {
let judge_state = await JudgeState.fromID(id);
if (!judge_state) throw new ErrorMessage('无此提交记录。');
await judge_state.loadRelationships();
judge_state.allowedSeeCode = await judge_state.isAllowedSeeCodeBy(res.locals.user);
judge_state.allowedSeeData = await judge_state.isAllowedSeeDataBy(res.locals.user);
let contest;
if (judge_state.type === 1) {
contest = await Contest.fromID(judge_state.type_info);
contest.ended = await contest.isEnded();
let problems_id = await contest.getProblems();
judge_state.problem_id = problems_id.indexOf(judge_state.problem_id) + 1;
judge_state.problem.title = syzoj.utils.removeTitleTag(judge_state.problem.title);
if (contest.type === 'noi' && !contest.ended && !await judge_state.problem.isAllowedEditBy(res.locals.user)) {
if (!['Compile Error', 'Waiting', 'Compiling'].includes(judge_state.status)) {
judge_state.status = 'Submitted';
}
}
}
let o = { pending: judge_state.pending, html: null, status: judge_state.status };
o.html = await require('util').promisify(app.render).bind(app)('submissions_item', {
contest: contest,
judge: judge_state
});
rendered[id] = o;
}
res.send(rendered);
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/submission/:id', async (req, res) => { app.get('/submission/:id', async (req, res) => {
try { try {
let id = parseInt(req.params.id); let id = parseInt(req.params.id);
@ -199,9 +159,14 @@ app.get('/submission/:id', async (req, res) => {
} }
res.render('submission', { res.render('submission', {
hideScore, hideScore, info: getSubmissionInfo(judge),
contest: contest, roughResult: getRoughResult(judge),
judge: judge code: judge.code.toString("utf8"),
detailResult: judge.result,
socketToken: judge.pending ? jwt.sign({
taskId: judge.id,
type: 'detail'
}, syzoj.config.judge_token) : null
}); });
} catch (e) { } catch (e) {
syzoj.log(e); syzoj.log(e);

43
views/status_label.ejs

@ -0,0 +1,43 @@
<script>
const iconList = {
'Accepted': 'checkmark',
'Wrong Answer': 'remove',
'Runtime Error': 'bomb',
'Invalid Interaction': 'ban',
'Time Limit Exceeded': 'clock',
'Memory Limit Exceeded': 'microchip',
'Output Limit Exceeded': 'print',
'File Error': 'file outline',
'Waiting': 'hourglass half',
'Running': 'spinner',
'Compile Error': 'code',
'Submitted': 'checkmark', // NOI contests
'System Error': 'server',
'No Testdata': 'folder open outline',
'Partially Correct': 'minus',
'Judgement Failed': 'server',
'Skipped': 'ban'
};
Vue.component('status-label', {
template: '#statusIconTemplate',
props: ['status'],
computed: {
icon() {
if (this.status in iconList) {
return iconList[this.status];
} else {
return 'man';
}
},
colorClass() {
return this.status.toLowerCase().split(' ').join('_');
}
}
})
</script>
<script type="text/x-template" id="statusIconTemplate">
<span class="status" :class="colorClass">
<i class="icon" :class="icon"></i>
{{ status }}
</span>
</script>

2
views/submission.ejs

@ -1,4 +1,4 @@
<% this.title = '提交记录 #' + judge.id %> <% this.title = '提交记录 #' + info.taskId %>
<% include header %> <% include header %>
<script src="/textFit.js"></script> <script src="/textFit.js"></script>
<span id="submission_content"><% include submission_content %></span> <span id="submission_content"><% include submission_content %></span>

234
views/submission_content.ejs

@ -1,5 +1,235 @@
<% include util %> <% include util %>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
<div class="padding" id="vueApp">
<table class="ui very basic center aligned table" id="status_table">
<thead>
<tr>
<th>编号</th>
<th>题目</th>
<th>状态</th>
<th>分数</th>
<th>总时间</th>
<th>内存</th>
<th>代码 / 答案文件</th>
<th>提交者</th>
<th>提交时间</th>
</tr>
</thead>
<tbody>
<tr is="submission-item" v-bind:data="roughData"></tr>
</tbody>
</table>
<code-box :escape="false" v-bind:content="code"></code-box>
<code-box v-if="detailResult && detailResult.compile" :escape="false" title="编译信息" v-bind:content="detailResult.compile.message"></code-box>
<code-box v-if="detailResult" title="系统信息" :escape="true" v-bind:content="detailResult.systemMessage"></code-box>
<div class="ui styled fluid accordion" v-if="detailResult && detailResult.judge && detailResult.judge.subtasks">
<template v-for="subtask, $index in detailResult.judge.subtasks">
<div class="title">
<div class="ui grid">
<div class="three wide column">
<i class="dropdown icon"></i>
子任务 #{{ $index + 1 }}
</div>
<div class="four wide column">
<status-label :status="getSubtaskResult(subtask)"></status-label>
</div>
<div class="three wide column">得分
<span style="font-weight: normal; ">{{ subtask.score }}</span>
</div>
</div>
</div>
<div class="content">
<div class="accordion">
<template v-for="curCase, $caseIndex in subtask.cases">
<div class="title">
<div class="ui grid">
<div class="three wide column">
<i class="dropdown icon"></i>
测试点 #{{ $caseIndex + 1 }}
</div>
<div class="four wide column">
<status-label :status="getTestcaseStatus(curCase)"></status-label>
</div>
<template v-if="checkTestcaseOK(curCase)">
<div class="three wide column">得分率
<span style="font-weight: normal; ">{{ (curCase.result.scoringRate * 100).toFixed(1) }}</span>
</div>
<div class="three wide column">用时
<span style="font-weight: normal; ">{{ curCase.result.time }} ms</span>
</div>
<div class="three wide column">内存
<span style="font-weight: normal; ">{{ curCase.result.memory }} KiB</span>
</div>
</template>
</div>
</div>
<div class="content">
<template v-if="checkTestcaseOK(curCase)">
<code-box v-if="curCase.result.input" :title="'输入文件('+ curCase.result.input.name +')'" :content="curCase.result.input.content"></code-box>
<code-box v-if="curCase.result.output" :title="'答案文件('+ curCase.result.output.name +')'" :content="curCase.result.output.content"></code-box>
<code-box title="用户输出" :content="curCase.result.userOutput"></code-box>
<code-box title="标准错误流" :content="curCase.result.userError"></code-box>
<code-box title="Special Judge 信息" :content="curCase.result.spjMessage"></code-box>
<code-box title="系统信息" :content="curCase.result.systemMessage"></code-box>
</template>
<code-box title="错误信息" :content="curCase.errorMessage"></code-box>
</div>
</template>
</div>
</div>
</template>
</div>
</div>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.0.3/socket.io.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> <script src="https://cdn.bootcss.com/jsondiffpatch/0.2.4/jsondiffpatch.js"></script>
<% include submissions_item %>
<script type="text/x-template" id="codeBoxTemplate">
<div style="" v-if="content != null && content !== ''">
<div></div>
<h3 v-if="title" class="ui header">{{ title }}</h3>
<div class="ui existing segment">
<pre v-if="escape" style="margin-top: 0; margin-bottom: 0; "><code>{{ content }}</code></pre>
<pre v-if="!escape" style="margin-top: 0; margin-bottom: 0; "><code v-html="content"></code></pre>
</div>
</div>
</script>
<script>
Vue.component("code-box", {
template: "#codeBoxTemplate",
props: ['title', 'content', 'escape'],
});
const socketUrl = <%- syzoj.utils.judgeServer('detail') %>;
const token = <%- JSON.stringify(socketToken) %>;
const TestcaseResultType = {};
(function (TestcaseResultType) {
TestcaseResultType[TestcaseResultType["Accepted"] = 1] = "Accepted";
TestcaseResultType[TestcaseResultType["WrongAnswer"] = 2] = "WrongAnswer";
TestcaseResultType[TestcaseResultType["PartiallyCorrect"] = 3] = "PartiallyCorrect";
TestcaseResultType[TestcaseResultType["MemoryLimitExceeded"] = 4] = "MemoryLimitExceeded";
TestcaseResultType[TestcaseResultType["TimeLimitExceeded"] = 5] = "TimeLimitExceeded";
TestcaseResultType[TestcaseResultType["OutputLimitExceeded"] = 6] = "OutputLimitExceeded";
TestcaseResultType[TestcaseResultType["FileError"] = 7] = "FileError";
TestcaseResultType[TestcaseResultType["RuntimeError"] = 8] = "RuntimeError";
TestcaseResultType[TestcaseResultType["JudgementFailed"] = 9] = "JudgementFailed";
TestcaseResultType[TestcaseResultType["InvalidInteraction"] = 10] = "InvalidInteraction";
})(TestcaseResultType);
const statusToString = {};
statusToString[TestcaseResultType.Accepted] = "Accepted";
statusToString[TestcaseResultType.WrongAnswer] = "Wrong Answer";
statusToString[TestcaseResultType.PartiallyCorrect] = "Partially Correct";
statusToString[TestcaseResultType.MemoryLimitExceeded] = "Memory Limit Exceeded";
statusToString[TestcaseResultType.TimeLimitExceeded] = "Time Limit Exceeded";
statusToString[TestcaseResultType.OutputLimitExceeded] = "Output Limit Exceeded";
statusToString[TestcaseResultType.RuntimeError] = "Runtime Error";
statusToString[TestcaseResultType.FileError] = "File Error";
statusToString[TestcaseResultType.JudgementFailed] = "Judgement Failed";
statusToString[TestcaseResultType.InvalidInteraction] = "Invalid Interaction";
const TaskStatus = {};
(function (TaskStatus) {
TaskStatus[TaskStatus["Waiting"] = 0] = "Waiting";
TaskStatus[TaskStatus["Running"] = 1] = "Running";
TaskStatus[TaskStatus["Done"] = 2] = "Done";
TaskStatus[TaskStatus["Failed"] = 3] = "Failed";
TaskStatus[TaskStatus["Skipped"] = 4] = "Skipped";
})(TaskStatus);
const vueApp = new Vue({
el: '#vueApp',
data: {
roughData: {
info: <%- JSON.stringify(info) %>,
result: <%- JSON.stringify(roughResult) %>,
running: false
},
code: <%- JSON.stringify(code) %>,
detailResult: <%- JSON.stringify(detailResult) %>
},
methods: {
getStatusString(statusCode) {
return statusToString[statusCode];
},
firstNonAC(t) {
if (t.every(v => v === TestcaseResultType.Accepted)) {
return TestcaseResultType.Accepted;
} else {
return t.find(r => r !== TestcaseResultType.Accepted);
}
},
getSubtaskResult(t) {
if (t.cases.some(c => c.status === TaskStatus.Running)) {
return "Running";
} else if (t.cases.some(c => c.status === TaskStatus.Waiting)) {
return "Waiting";
} else if (t.cases.every(c => c.status === TaskStatus.Done)) {
return this.getStatusString(this.firstNonAC(t.cases.map(c => c.result.type)));
} else {
return "System Error";
}
},
getTestcaseStatus(c) {
if (c.status === TaskStatus.Done) {
return this.getStatusString(c.result.type);
} else if (c.status === TaskStatus.Waiting) {
return "Waiting";
} else if (c.status === TaskStatus.Running) {
return "Running";
} else {
return "System Error";
}
},
checkTestcaseOK(c) {
return c.status === TaskStatus.Done;
}
},
mounted() {
console.log("mounted");
setTimeout(() => $('.ui.accordion').accordion(), 500);
},
updated() {
console.log("updated");
$('.ui.accordion').accordion("refresh");
}
});
if (token != null) {
const socket = io(socketUrl);
socket.on('connect', () => {
socket.on('start', () => {
vueApp.roughData.running = true;
});
socket.on('update', (p) => {
console.log("Updating");
console.log("Delta: " + JSON.stringify(p));
jsondiffpatch.patch(vueApp.detailResult, p.delta);
});
socket.on('finish', (p) => {
vueApp.roughData.running = false;
vueApp.roughData.result = p.roughResult;
vueApp.detailResult = p.result;
});
socket.emit('join', token, (data) => {
if (data && data.ok) {
if (data.finished) {
alert('This submission is finished but not reported to the server. Please try again later.');
} else {
if (data.current.running) {
vueApp.roughData.running = true;
}
vueApp.detailResult = data.current.current;
}
} else {
alert("ERROR: " + JSON.stringify(data));
}
})
});
}
</script>

32
views/submissions.ejs

@ -78,14 +78,10 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="item in items" is='submission-item' v-bind:data="item"> <tr v-for="item in items" :data="item" is='submission-item'>
<fake a/> <%# <-- a temporary workaround for a syntaxing bug by Visual Studio Code ejs plugin %>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<script>
// document.getElementById('table').style.visibility = '';
</script>
<br> <br>
<% include page %> <% include page %>
</div> </div>
@ -113,6 +109,19 @@ if (itemList.some(t => t.token != null)) {
const socket = io(socketUrl); const socket = io(socketUrl);
socket.on('connect', () => { socket.on('connect', () => {
for (let x of itemList.filter(x => x.token != null)){ for (let x of itemList.filter(x => x.token != null)){
const getItem = (id) => itemList.find(x => x.info.taskId === id);
socket.on('start', (data) => {
getItem(data.taskId).running = true;
});
socket.on('finish', (data) => {
getItem(data.taskId).running = false;
getItem(data.taskId).result = data.result;
if (itemList.every(x => x.result)) {
socket.close();
}
});
socket.emit('join', x.token, (data) => { socket.emit('join', x.token, (data) => {
if (data && data.ok) { if (data && data.ok) {
if (data.running) { if (data.running) {
@ -125,19 +134,6 @@ if (itemList.some(t => t.token != null)) {
alert("ERROR: " + JSON.stringify(data)); alert("ERROR: " + JSON.stringify(data));
} }
}); });
const getItem = (id) => itemList.find(x => x.taskId === id);
socket.on('start', (data) => {
getItem(data.taskId).running = true;
});
socket.on('finish', (data) => {
getItem(data.taskId).running = false;
getItem(data.taskId).result = data.result;
if (itemList.every(x => x.result)) {
socket.close();
}
});
} }
}) })
} }

33
views/submissions_item.ejs

@ -1,17 +1,29 @@
<% include util %> <% include util %>
<% include status_label %>
<script> <script>
const submissionUrl = <%- JSON.stringify(syzoj.utils.makeUrl(['submission', 'VanDarkholme'])) %>;
const problemUrl = <%- JSON.stringify(syzoj.utils.makeUrl(['problem', 'VanDarkholme'])) %>;
Vue.component('submission-item', { Vue.component('submission-item', {
template: '#submissionItemTemplate', template: '#submissionItemTemplate',
props: ['data'], props: ['data'],
computed: { computed: {
statusString: function() { statusString() {
const data = this.data; const data = this.data;
if (data.result) { if (data.result) {
return data.result.result; return data.result.result;
} }
else if (data.running) return 'Running'; else if (data.running) return 'Running';
else return 'Waiting'; else return 'Waiting';
},
submissionLink() {
return submissionUrl.replace('VanDarkholme', this.data.info.taskId);
},
problemLink() {
return problemUrl.replace('VanDarkholme', this.data.info.problemId);
},
scoreClass() {
return "score_" + (parseInt(this.data.result.score / 10) || 0).toString();
} }
} }
}); });
@ -19,19 +31,20 @@ Vue.component('submission-item', {
<script id="submissionItemTemplate" type="text/x-template"> <script id="submissionItemTemplate" type="text/x-template">
<tr> <tr>
<td>#{{ data.taskId }}</td> <td><a :href="submissionLink">#{{ data.info.taskId }}</a></td>
<td>#{{ data.problemId }}. {{ data.problemName }}</td> <td><a :href="problemLink">#{{ data.info.problemId }}. {{ data.info.problemName }}</a></td>
<td>{{ statusString }}</td> <td><status-label :status="statusString"></status-label></td>
<template v-if="data.result"> <template v-if="data.result">
<td>{{ (data.result.score != null && data.result.score !== NaN) ? Math.floor(data.result.score) : '' }}</td> <td><span class="score" :class="scoreClass">{{ (data.result.score != null && data.result.score !== NaN) ? Math.floor(data.result.score) : '' }}</span></td>
<td>{{ (data.result.time != null && data.result.time !== NaN) ? data.result.time.toString() + ' ms' : '' }}</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> <td>{{ (data.result.memory != null && data.result.memory !== NaN) ? data.result.memory.toString() + ' KiB' : '' }}</td>
</template> </template> <template v-else>
<template v-else>
<td /> <td /> <td /> <td /> <td /> <td />
</template> </template>
<td>{{ data.language != null ? data.language + ' / ' : '' }}{{ data.codeSize }}</td>
<td>{{ data.user }}</td> <td>{{ data.info.language != null ? data.info.language + ' / ' : '' }}{{ data.info.codeSize }}</td>
<td>{{ data.submitTime }}</td> <td>{{ data.info.user }}</td>
<td>{{ data.info.submitTime }}</td>
</tr> </tr>
</script> </script>
Loading…
Cancel
Save