Browse Source

Merge pull request #107 from syzoj/restart-service

Add restart service option for admins
master
Menci 6 years ago committed by GitHub
parent
commit
7e21744048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.en.md
  2. 2
      README.md
  3. 57
      app.js
  4. 32
      modules/admin.js
  5. 1
      package.json
  6. 46
      views/admin_restart.ejs
  7. 1
      views/header.ejs

2
README.en.md

@ -48,3 +48,5 @@ ALTER TABLE `user` ADD `prefer_formatted_code` TINYINT(1) NOT NULL DEFAULT 1 AFT
To make code formatting work, `clang-format` needs to be installed. [migrates/format-old-codes.js](migrates/format-old-codes.js) may help formating old submissions' codes.
Who upgraded from a commit BEFORE [c192e8001ac81cab132ae033b39f09a094587633](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633) (Mar 23, 2019) **MUST** install `redis-server` and [pygments](http://pygments.org/) on the web server. Markdown contents may be broken by switching to new renderer, [migrates/html-table-merge-cell-to-md.js](migrates/html-table-merge-cell-to-md.js) may help the migration。
Who upgraded from a commit BEFORE [7b03706821c604f59fe8263286203d57d634c421](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633) (Mar 27, 2019) **MUST** add `RemainAfterExit=yes` to the systemd config file `syzoj-web.service`'s `[Service]` section to make sure that restart service can work properly.

2
README.md

@ -48,3 +48,5 @@ ALTER TABLE `user` ADD `prefer_formatted_code` TINYINT(1) NOT NULL DEFAULT 1 AFT
为使代码格式化功能正常工作,`clang-format` 需要被安装。[migrates/format-old-codes.js](migrates/format-old-codes.js) 可能对格式化旧提交记录的代码有帮助。
从该 commit [c192e8001ac81cab132ae033b39f09a094587633](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633)(2019 年 3 月 23 日)前更新的用户**必须**在网站服务器上安装 `redis-server` 与 [pygments](http://pygments.org/)。旧的 Markdown 内容可能因切换到新渲染器被破坏,[migrates/html-table-merge-cell-to-md.js](migrates/html-table-merge-cell-to-md.js) 可能对迁移有所帮助。
从该 commit [7b03706821c604f59fe8263286203d57d634c421](https://github.com/syzoj/syzoj/commit/c192e8001ac81cab132ae033b39f09a094587633)(2019 年 3 月 27 日)前更新的用户**必须**在其 systemd 配置文件 `syzoj-web.service` 中的 `[Service]` 中加入一行 `RemainAfterExit=yes`,以使得重启服务功能正常工作。

57
app.js

@ -2,6 +2,7 @@ let fs = require('fs'),
path = require('path'),
util = require('util');
const serializejs = require('serialize-javascript');
const UUID = require('uuid');
const commandLineArgs = require('command-line-args');
const optionDefinitions = [
@ -18,6 +19,7 @@ global.syzoj = {
models: [],
modules: [],
db: null,
serviceID: UUID(),
log(obj) {
if (obj instanceof ErrorMessage) return;
console.log(obj);
@ -38,15 +40,6 @@ global.syzoj = {
let winstonLib = require('./libs/winston');
winstonLib.configureWinston(!syzoj.production);
app.server = require('http').createServer(app);
if (!module.parent) {
// Loaded by `require()`, not node CLI.
app.server.listen(parseInt(syzoj.config.port), syzoj.config.hostname, () => {
this.log(`SYZOJ is listening on ${syzoj.config.hostname}:${parseInt(syzoj.config.port)}...`);
});
}
// Set assets dir
app.use(Express.static(__dirname + '/static', { maxAge: syzoj.production ? '1y' : 0 }));
@ -79,9 +72,7 @@ global.syzoj = {
})());
await this.connectDatabase();
if (!module.parent) {
await this.lib('judger').connect();
}
this.loadModules();
// redis and redisCache is for syzoj-renderer
const redis = require('redis');
@ -91,7 +82,40 @@ global.syzoj = {
set: util.promisify(this.redis.set).bind(this.redis)
};
this.loadModules();
if (!module.parent) {
// Loaded by node CLI, not by `require()`.
if (process.send) {
// if it's started by child_process.fork(), it must be requested to restart
// wait until parent process quited.
await new Promise((resolve, reject) => {
process.on('message', message => {
if (message === 'quited') resolve();
});
process.send('quit');
});
}
await this.lib('judger').connect();
app.server = require('http').createServer(app);
app.server.listen(parseInt(syzoj.config.port), syzoj.config.hostname, () => {
this.log(`SYZOJ is listening on ${syzoj.config.hostname}:${parseInt(syzoj.config.port)}...`);
});
}
},
restart() {
console.log('Will now fork a new process.');
const child = require('child_process').fork(__filename, ['-c', options.config]);
child.on('message', (message) => {
if (message !== 'quit') return;
console.log('Child process requested "quit".')
child.send('quited', err => {
if (err) console.error('Error sending "quited" to child process:', err);
process.exit();
});
});
},
async connectDatabase() {
let Sequelize = require('sequelize');
@ -143,7 +167,7 @@ global.syzoj = {
global.Promise = Sequelize.Promise;
this.db.countQuery = async (sql, options) => (await this.db.query(`SELECT COUNT(*) FROM (${sql}) AS \`__tmp_table\``, options))[0][0]['COUNT(*)'];
this.loadModels();
await this.loadModels();
},
loadModules() {
fs.readdir('./modules/', (err, files) => {
@ -155,7 +179,7 @@ global.syzoj = {
.forEach((file) => this.modules.push(require(`./modules/${file}`)));
});
},
loadModels() {
async loadModels() {
fs.readdir('./models/', (err, files) => {
if (err) {
this.log(err);
@ -163,9 +187,8 @@ global.syzoj = {
}
files.filter((file) => file.endsWith('.js'))
.forEach((file) => require(`./models/${file}`));
this.db.sync();
});
await this.db.sync();
},
lib(name) {
return require(`./libs/${name}`);

32
modules/admin.js

@ -442,3 +442,35 @@ app.post('/admin/raw', async (req, res) => {
})
}
});
app.post('/admin/restart', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
syzoj.restart();
res.render('admin_restart', {
data: JSON.stringify(syzoj.config, null, 2)
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
})
}
});
app.get('/admin/serviceID', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
res.send({
serviceID: syzoj.serviceID
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
})
}
});

1
package.json

@ -65,6 +65,7 @@
"syzoj-renderer": "^1.0.5",
"tempfile": "^2.0.0",
"tmp-promise": "^1.0.3",
"uuid": "^3.3.2",
"waliyun": "^3.1.1",
"winston": "^3.2.1",
"xss": "^1.0.6"

46
views/admin_restart.ejs

@ -0,0 +1,46 @@
<% this.title = '重启服务' %>
<% include header %>
<div class="ui success icon message">
<i class="redo icon"></i>
<div class="content">
<div class="header" style="margin-bottom: 10px; ">
服务重启中
</div>
<p>已等待 <span id="elapsed-wait-time">0</span> 秒。</p>
</div>
</div>
<script>
var span = $('#elapsed-wait-time'),
time = 0;
setInterval(function () {
span.text((++time).toString());
}, 1000);
var checkInterval = 500,
delayBeforeRedirect = 1000,
currentServiceID = <%- serializejs(syzoj.serviceID) %>;
function checkServiceUp() {
function retry() {
setTimeout(checkServiceUp, 500);
}
$.ajax({
url: '/admin/serviceID',
success: function (data, textStatus, xhr) {
if (data.serviceID && data.serviceID !== currentServiceID) {
setTimeout(function () {
location = <%- serializejs(req.query.url || '/') %>;
}, delayBeforeRedirect);
} else retry();
},
complete: function (xhr, textStatus) {
retry();
}
});
}
checkServiceUp();
</script>
<% include footer %>

1
views/header.ejs

@ -72,6 +72,7 @@
<a class="item" href="<%= syzoj.utils.makeUrl(['user', user.id, 'edit']) %>"><i class="edit icon"></i>修改资料</a>
<% if (user.is_admin) { %>
<a class="item" href="<%= syzoj.utils.makeUrl(['admin', 'info']) %>"><i class="settings icon"></i>后台管理</a>
<a class="item" href-post="<%= syzoj.utils.makeUrl(['admin', 'restart'], { url: req.query['url'] || req.originalUrl }) %>"><i class="redo icon"></i>重启服务</a>
<% } %>
<a class="item" href-post="<%= syzoj.utils.makeUrl(['logout'], { url: req.originalUrl }) %>"><i class="power icon"></i>注销</a>
</div>

Loading…
Cancel
Save