Browse Source

Add restart service option for admins

master
Menci 6 years ago
parent
commit
7b03706821
  1. 57
      app.js
  2. 32
      modules/admin.js
  3. 1
      package.json
  4. 46
      views/admin_restart.ejs
  5. 1
      views/header.ejs

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