Browse Source

Bring up to speed

mods_upload_support
Johannes Zellner 2 years ago
parent
commit
b97623ef4a
25 changed files with 2108 additions and 110 deletions
  1. +10
    -0
      .dockerignore
  2. +8
    -0
      .gitignore
  3. +3
    -4
      Dockerfile
  4. +66
    -0
      backend/minecraft.js
  5. +156
    -0
      backend/routes.js
  6. +51
    -0
      backend/server.js
  7. +157
    -0
      frontend/css/index.css
  8. BIN
      frontend/favicon.png
  9. BIN
      frontend/fonts/mcfont.eot
  10. +251
    -0
      frontend/fonts/mcfont.svg
  11. BIN
      frontend/fonts/mcfont.ttf
  12. BIN
      frontend/fonts/mcfont.woff
  13. BIN
      frontend/img/body-bg.png
  14. BIN
      frontend/img/button_active_center.png
  15. BIN
      frontend/img/button_center.png
  16. BIN
      frontend/img/header-bg.png
  17. BIN
      frontend/img/header-decor.png
  18. +52
    -0
      frontend/index.html
  19. +152
    -0
      frontend/js/index.js
  20. +100
    -0
      frontend/js/superagent.js
  21. +6
    -0
      frontend/js/vue.js
  22. +10
    -102
      index.js
  23. +1072
    -0
      package-lock.json
  24. +12
    -2
      package.json
  25. +2
    -2
      start.sh

+ 10
- 0
.dockerignore View File

@ -0,0 +1,10 @@
node_modules/
world/
minecraft_server.jar
users.json
server.properties
whitelist.json
ops.json
banned-ips.json
banned-players.json
logs/

+ 8
- 0
.gitignore View File

@ -1,2 +1,10 @@
node_modules/
world/
minecraft_server.jar
users.json
server.properties
whitelist.json
ops.json
banned-ips.json
banned-players.json
logs/

+ 3
- 4
Dockerfile View File

@ -1,12 +1,11 @@
FROM cloudron/base:0.10.0
MAINTAINER Johannes Zellner <johannes@nebulon.de>
FROM cloudron/base:1.0.0
RUN mkdir -p /app/code
WORKDIR /app/code
ENV PATH /usr/local/node-4.7.3/bin:$PATH
RUN apt-get update && apt-get install -y openjdk-11-jre-headless
RUN curl -L https://s3.amazonaws.com/Minecraft.Download/versions/1.11.2/minecraft_server.1.11.2.jar -o minecraft_server.jar
RUN curl -L https://launcher.mojang.com/v1/objects/3737db93722a9e39eeada7c27e7aca28b144ffa7/server.jar -o minecraft_server.jar
COPY eula.txt index.js index.html package.json start.sh /app/code/


+ 66
- 0
backend/minecraft.js View File

@ -0,0 +1,66 @@
'use strict';
var path = require('path'),
byline = require('byline');
module.exports = exports = {
status: status,
start: start,
stop: stop,
command: command,
addLogListener: addLogListener
};
var minecraft = null;
var logLineStream = null;
var logListeners = [];
function status() {
return { running: !!minecraft };
}
function start() {
console.log('start minecraft server');
var opts = { cwd: path.join(__dirname, '..') };
if (process.env.CLOUDRON) opts.cwd = '/app/data';
minecraft = require('child_process').spawn('java', ['-Xmx1024M', '-Xms1024M', '-jar', path.join(__dirname, '../minecraft_server.jar'), 'nogui'], opts);
logLineStream = byline(minecraft.stdout);
logLineStream.on('data', function (line) {
console.log(line.toString()); // also log to stdout
logListeners.forEach(function (l) {
l.emit('line', line.toString().split('[Server thread/INFO]:')[1]);
});
});
minecraft.stderr.pipe(process.stderr);
// minecraft.stdout.pipe(process.stdout);
// process.stdin.pipe(minecraft.stdin);
minecraft.on('close', function () {
minecraft = null;
});
}
function stop(callback) {
console.log('stop minecraft server');
if (!minecraft) return callback();
minecraft.kill();
minecraft.on('close', function () {
minecraft = null;
callback();
});
}
function command(cmd, callback) {
if (!minecraft) return;
minecraft.stdin.write(cmd + '\n', callback);
}
function addLogListener(socket) {
logListeners.push(socket);
}

+ 156
- 0
backend/routes.js View File

@ -0,0 +1,156 @@
'use strict';
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
ldapjs = require('ldapjs'),
basicAuth = require('basic-auth'),
minecraft = require('./minecraft.js'),
lastMile = require('connect-lastmile'),
HttpError = lastMile.HttpError,
HttpSuccess = lastMile.HttpSuccess;
module.exports = exports = {
healthcheck: healthcheck,
status: status,
auth: auth,
profile: profile,
settings: {
get: settingsGet,
set: settingsSet
},
start: start,
stop: stop,
command: command
};
const LDAP_URL = process.env.LDAP_URL;
const LDAP_USERS_BASE_DN = process.env.LDAP_USERS_BASE_DN;
const LOCAL_AUTH_FILE = path.resolve('users.json');
const baseDir = process.env.CLOUDRON ? '/app/data' : path.join(__dirname, '..')
const configFilePath = path.join(baseDir, 'server.properties');
var users = {};
var AUTH_METHOD = (LDAP_URL && LDAP_USERS_BASE_DN) ? 'ldap' : 'local';
if (AUTH_METHOD === 'ldap') {
console.log('Use ldap auth');
} else {
console.log(`Use local auth file ${LOCAL_AUTH_FILE}`);
try {
users = JSON.parse(fs.readFileSync(LOCAL_AUTH_FILE, 'utf8'));
} catch (e) {
let template = [{ username: 'username', email: 'test@example.com', password: 'password' }];
console.log(`Unable to read local auth file. Create a JSON file at ${LOCAL_AUTH_FILE} with\n%s`, JSON.stringify(template, null, 4));
process.exit(1);
}
}
function healthcheck(req, res, next) {
next(new HttpSuccess(200, {}));
}
function status(req, res, next) {
next(new HttpSuccess(200, { status: minecraft.status() }));
}
function auth(req, res, next) {
var credentials = basicAuth(req);
if (!credentials) return next(new HttpError(400, 'Basic auth required'));
function attachUser(user) {
req.user = user;
return next();
}
if (AUTH_METHOD === 'ldap') {
var ldapClient = ldapjs.createClient({ url: process.env.LDAP_URL });
ldapClient.on('error', function (error) {
console.error('LDAP error', error);
});
ldapClient.bind(process.env.LDAP_BIND_DN, process.env.LDAP_BIND_PASSWORD, function (error) {
if (error) return next(new HttpError(500, error));
var filter = `(|(uid=${credentials.name})(mail=${credentials.name})(username=${credentials.name})(sAMAccountName=${credentials.name}))`;
ldapClient.search(process.env.LDAP_USERS_BASE_DN, { filter: filter }, function (error, result) {
if (error) return next(new HttpError(500, error));
var items = [];
result.on('searchEntry', function(entry) { items.push(entry.object); });
result.on('error', function (error) { next(new HttpError(500, error)); });
result.on('end', function (result) {
if (result.status !== 0) return next(new HttpError(500, error));
if (items.length === 0) return next(new HttpError(401, 'Invalid credentials'));
// pick the first found
var user = items[0];
ldapClient.bind(user.dn, credentials.pass, function (error) {
if (error) return next(new HttpError(401, 'Invalid credentials'));
attachUser({ username: user.username, email: user.mail });
});
});
});
});
} else {
let user = users.find(function (u) { return (u.username === credentials.name || u.email === credentials.name) && u.password === credentials.pass; });
if (!user) return next(new HttpError(401, 'Invalid credentials'));
attachUser(user);
}
}
function profile(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
next(new HttpSuccess(200, { user: req.user }));
}
function settingsGet(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
fs.readFile(configFilePath, function (error, result) {
if (error) return res.status(500).send(error);
next(new HttpSuccess(200, { settings: result.toString() }));
});
}
function settingsSet(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
fs.writeFile(configFilePath, req.body.settings, function (error) {
if (error) return res.status(500).send(error);
next(new HttpSuccess(202, {}));
});
}
function start(req, res, next) {
minecraft.start();
next(new HttpSuccess(202, {}));
}
function stop(req, res, next) {
minecraft.stop(function () {
next(new HttpSuccess(202, {}));
});
}
function command(req, res, next) {
minecraft.command(req.body.cmd, function (error) {
if (error) return next(new HttpError(500, 'server not running'));
next(new HttpSuccess(202, {}));
});
}

+ 51
- 0
backend/server.js View File

@ -0,0 +1,51 @@
'use strict';
var assert = require('assert'),
connectTimeout = require('connect-timeout'),
lastMile = require('connect-lastmile'),
routes = require('./routes.js'),
minecraft = require('./minecraft.js'),
express = require('express');
module.exports = exports = {
start: start
};
function start(port, callback) {
assert.strictEqual(typeof port, 'number');
assert.strictEqual(typeof callback, 'function');
var router = express.Router();
router.del = router.delete;
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
router.get ('/api/v1/healthcheck', routes.healthcheck);
router.get ('/api/v1/status', routes.auth, routes.status);
router.get ('/api/v1/profile', routes.auth, routes.profile);
router.post('/api/v1/start', routes.auth, routes.start);
router.post('/api/v1/stop', routes.auth, routes.stop);
router.post('/api/v1/command', routes.auth, routes.command);
router.get ('/api/v1/settings', routes.auth, routes.settings.get);
router.post('/api/v1/settings', routes.auth, routes.settings.set);
// for log lines
io.on('connection', function (socket) {
socket.on('cmd', function (cmd) {
minecraft.cmd(cmd);
});
minecraft.addLogListener(socket);
});
app
.use(connectTimeout(10000, { respond: true }))
.use(express.json())
.use(express.urlencoded({ extended: true }))
.use(router)
.use(express.static('./frontend'))
.use(lastMile());
http.listen(port, callback);
}

+ 157
- 0
frontend/css/index.css View File

@ -0,0 +1,157 @@
@font-face {
font-family: 'Minecraft';
src: url('/fonts/mcfont.eot');
src: url('/fonts/mcfont.woff') format('woff'),
url('/fonts/mcfont.ttf') format('truetype'),
url('/fonts/mcfont.svg#svgFontName') format('svg');
}
html, body {
height: 100%;
font-family: Minecraft;
text-shadow: 3px 3px #4C4C4C;
color: white;
margin: 0;
color: rgb(187, 189, 194);
background: transparent url('/img/body-bg.png') repeat;
font-size: 16px;
line-height: 40px;
}
header {
color: white;
padding: 10px;
}
a {
color: #FFFFA0 !important;
text-decoration: none;
}
.pull-right {
float: right;
}
header > h1 {
margin: 0;
text-align: center;
vertical-align: middle;
font-weight: normal;
}
footer {
width: 100%;
text-align: center;
}
.container {
width: 100%;
max-width: 800px;
margin: auto;
}
.inactive {
color: red;
}
.active {
color: greenyellow;
}
button {
height: 40px;
min-width: 200px;
outline: 2px solid black;
position: relative;
margin: 0;
display: inline-block;
background-image: url('/img/button_center.png');
background-repeat: repeat;
padding: 0;
font-family: Minecraft;
text-align: center;
color: white;
text-shadow: 3px 3px #4C4C4C;
border-bottom: 4px solid #565656;
border-right: 2px solid #565656;
border-left: 2px solid #AAA;
border-top: 2px solid #AAA;
box-sizing: border-box;
font-size: 16px;
cursor: pointer;
}
button:hover {
border-bottom: 4px solid #59639A;
border-right: 2px solid #59639A;
border-left: 2px solid #BDC6FF;
border-top: 2px solid #BDC6FF;
background-image: url('/img/button_active_center.png');
color: #FFFFA0;
}
input {
background-color: black;
border-color: #888787;
border-style: solid;
border-width: 2px;
height: 40px;
font-family: Minecraft;
font-size: 16px;
color: white;
padding: 10px;
}
input:focus, textarea:focus {
border-color: white;
}
textarea {
background-color: black;
border-color: #4C4C4C;
border-style: solid;
border-width: 2px;
font-family: Minecraft;
font-size: 16px;
color: white;
padding: 10px;
width: 100%;
min-height: 300px;
}
.logout {
position: fixed;
top: 10px;
left: 10px;
}
.login input, .login button {
width: 100%;
}
.logstream {
width: 100%;
height: 400px;
overflow: auto;
background-color: black;
}
.logstream > p {
font-family: Minecraft;
text-shadow: none;
line-height: normal;
font-size: 14px;
color: white;
margin: 0;
}
.command {
width: 100%;
font-size: 14px;
}
.command::before {
content: '>'
}

BIN
frontend/favicon.png View File

Before After
Width: 512  |  Height: 512  |  Size: 50 KiB

BIN
frontend/fonts/mcfont.eot View File


+ 251
- 0
frontend/fonts/mcfont.svg View File

@ -0,0 +1,251 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg>
<metadata>
Created by FontForge 20110222 at Sun Jan 5 10:13:18 2014
By Orthosie Webhosting
Copyright Pwnage_Block 2011
</metadata>
<defs>
<font id="Minecraft" horiz-adv-x="768" >
<font-face
font-family="Minecraft"
font-weight="400"
font-stretch="normal"
units-per-em="1024"
panose-1="0 0 4 0 0 0 0 0 0 0"
ascent="896"
descent="-128"
x-height="640"
cap-height="896"
bbox="0 -128 1280 1152"
underline-thickness="51"
underline-position="126"
unicode-range="U+0020-201D"
/>
<missing-glyph horiz-adv-x="374"
d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
<glyph glyph-name=".notdef" horiz-adv-x="374"
d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
<glyph glyph-name=".null" horiz-adv-x="0"
/>
<glyph glyph-name="nonmarkingreturn" horiz-adv-x="341"
/>
<glyph glyph-name="space" unicode=" " horiz-adv-x="256"
/>
<glyph glyph-name="exclam" unicode="!" horiz-adv-x="256"
d="M0 0v128h128v-128h-128zM0 256v640h128v-640h-128z" />
<glyph glyph-name="quotedbl" unicode="&#x22;" horiz-adv-x="512"
d="M0 640v256h128v-256h-128zM256 640v256h128v-256h-128z" />
<glyph glyph-name="numbersign" unicode="#"
d="M384 384v128h-128v-128h128zM128 0v256h-128v128h128v128h-128v128h128v256h128v-256h128v256h128v-256h128v-128h-128v-128h128v-128h-128v-256h-128v256h-128v-256h-128z" />
<glyph glyph-name="dollar" unicode="$"
d="M256 0v128h-256v128h512v-128h-128v-128h-128zM512 256v128h128v-128h-128zM128 384v128h384v-128h-384zM0 512v128h128v-128h-128zM128 640v128h128v128h128v-128h256v-128h-512z" />
<glyph glyph-name="percent" unicode="%"
d="M0 0v128h128v-128h-128zM512 0v256h128v-256h-128zM128 128v256h128v-256h-128zM256 384v128h128v-128h-128zM384 512v256h128v-256h-128zM0 640v256h128v-256h-128zM512 768v128h128v-128h-128z" />
<glyph glyph-name="ampersand" unicode="&#x26;"
d="M128 0v128h256v-128h-256zM512 0v128h128v-128h-128zM0 128v256h128v-256h-128zM512 384v128h128v-128h-128zM384 128v128h-128v128h-128v128h128v128h128v-256h128v-256h-128zM128 640v128h128v-128h-128zM384 640v128h128v-128h-128zM256 768v128h128v-128h-128z" />
<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="256"
d="M0 640v256h128v-256h-128z" />
<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="640"
d="M256 0v128h256v-128h-256zM128 128v128h128v-128h-128zM0 256v384h128v-384h-128zM128 640v128h128v-128h-128zM256 768v128h256v-128h-256z" />
<glyph glyph-name="parenright" unicode=")" horiz-adv-x="640"
d="M0 0v128h256v-128h-256zM256 128v128h128v-128h-128zM384 256v384h128v-384h-128zM256 640v128h128v-128h-128zM0 768v128h256v-128h-256z" />
<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="640"
d="M0 256v128h128v-128h-128zM384 256v128h128v-128h-128zM128 384v128h256v-128h-256zM0 512v128h128v-128h-128zM384 512v128h128v-128h-128z" />
<glyph glyph-name="plus" unicode="+"
d="M256 0v256h-256v128h256v256h128v-256h256v-128h-256v-256h-128z" />
<glyph glyph-name="comma" unicode="," horiz-adv-x="256"
d="M0 -128v384h128v-384h-128z" />
<glyph glyph-name="hyphen" unicode="-"
d="M0 256v128h640v-128h-640z" />
<glyph glyph-name="period" unicode="." horiz-adv-x="256"
d="M0 0v256h128v-256h-128z" />
<glyph glyph-name="slash" unicode="/"
d="M0 0v128h128v-128h-128zM128 128v256h128v-256h-128zM256 384v128h128v-128h-128zM384 512v256h128v-256h-128zM512 768v128h128v-128h-128z" />
<glyph glyph-name="zero" unicode="0"
d="M128 0v128h384v-128h-384zM256 384v128h128v-128h-128zM0 128v640h128v-384h128v-128h-128v-128h-128zM512 128v384h-128v128h128v128h128v-640h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="one" unicode="1"
d="M0 0v128h256v512h-128v128h128v128h128v-768h256v-128h-640z" />
<glyph glyph-name="two" unicode="2"
d="M0 0v256h128v-128h512v-128h-640zM128 256v128h128v-128h-128zM256 384v128h256v-128h-256zM0 640v128h128v-128h-128zM512 512v256h128v-256h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="three" unicode="3"
d="M128 0v128h384v-128h-384zM0 128v128h128v-128h-128zM512 128v256h128v-256h-128zM256 384v128h256v-128h-256zM0 640v128h128v-128h-128zM512 512v256h128v-256h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="four" unicode="4"
d="M128 512v128h128v-128h-128zM256 640v128h128v-128h-128zM512 0v256h-512v256h128v-128h384v384h-128v128h256v-896h-128z" />
<glyph glyph-name="five" unicode="5"
d="M128 0v128h384v-128h-384zM0 128v128h128v-128h-128zM512 128v384h128v-384h-128zM0 512v384h640v-128h-512v-128h384v-128h-512z" />
<glyph glyph-name="six" unicode="6"
d="M128 0v128h384v-128h-384zM512 128v256h128v-256h-128zM0 128v512h128v-128h384v-128h-384v-256h-128zM128 640v128h128v-128h-128zM256 768v128h256v-128h-256z" />
<glyph glyph-name="seven" unicode="7"
d="M256 0v384h128v-384h-128zM384 384v128h128v-128h-128zM512 512v256h-384v-128h-128v256h640v-384h-128z" />
<glyph glyph-name="eight" unicode="8"
d="M128 0v128h384v-128h-384zM0 128v256h128v-256h-128zM512 128v256h128v-256h-128zM128 384v128h384v-128h-384zM0 512v256h128v-256h-128zM512 512v256h128v-256h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="nine" unicode="9"
d="M128 0v128h256v-128h-256zM384 128v128h128v-128h-128zM0 512v256h128v-256h-128zM512 256v128h-384v128h384v256h128v-512h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="colon" unicode=":" horiz-adv-x="256"
d="M0 0v256h128v-256h-128zM0 512v256h128v-256h-128z" />
<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="256"
d="M0 -128v384h128v-384h-128zM0 512v256h128v-256h-128z" />
<glyph glyph-name="less" unicode="&#x3c;" horiz-adv-x="640"
d="M384 0v128h128v-128h-128zM256 128v128h128v-128h-128zM128 256v128h128v-128h-128zM0 384v128h128v-128h-128zM128 512v128h128v-128h-128zM256 640v128h128v-128h-128zM384 768v128h128v-128h-128z" />
<glyph glyph-name="equal" unicode="="
d="M0 128v128h640v-128h-640zM0 512v128h640v-128h-640z" />
<glyph glyph-name="greater" unicode="&#x3e;" horiz-adv-x="640"
d="M0 0v128h128v-128h-128zM128 128v128h128v-128h-128zM256 256v128h128v-128h-128zM384 384v128h128v-128h-128zM256 512v128h128v-128h-128zM128 640v128h128v-128h-128zM0 768v128h128v-128h-128z" />
<glyph glyph-name="question" unicode="?"
d="M256 0v128h128v-128h-128zM256 256v128h128v-128h-128zM384 384v128h128v-128h-128zM0 640v128h128v-128h-128zM512 512v256h128v-256h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="at" unicode="@" horiz-adv-x="896"
d="M128 0v128h640v-128h-640zM0 128v640h128v-640h-128zM256 256v384h256v-256h128v384h128v-512h-512zM128 768v128h512v-128h-512z" />
<glyph glyph-name="A" unicode="A"
d="M0 0v768h128v-128h384v128h128v-768h-128v512h-384v-512h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="B" unicode="B"
d="M512 128v384h128v-384h-128zM512 640v128h128v-128h-128zM0 0v896h512v-128h-384v-128h384v-128h-384v-384h384v-128h-512z" />
<glyph glyph-name="C" unicode="C"
d="M128 0v128h384v-128h-384zM512 128v128h128v-128h-128zM0 128v640h128v-640h-128zM512 640v128h128v-128h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="D" unicode="D"
d="M512 128v640h128v-640h-128zM0 0v896h512v-128h-384v-640h384v-128h-512z" />
<glyph glyph-name="E" unicode="E"
d="M0 0v896h640v-128h-512v-128h256v-128h-256v-384h512v-128h-640z" />
<glyph glyph-name="F" unicode="F"
d="M0 0v896h640v-128h-512v-128h256v-128h-256v-512h-128z" />
<glyph glyph-name="G" unicode="G"
d="M128 0v128h384v-128h-384zM512 128v384h-128v128h256v-512h-128zM0 128v640h128v-640h-128zM128 768v128h512v-128h-512z" />
<glyph glyph-name="H" unicode="H"
d="M0 0v896h128v-256h384v256h128v-896h-128v512h-384v-512h-128z" />
<glyph glyph-name="I" unicode="I" horiz-adv-x="512"
d="M0 0v128h128v640h-128v128h384v-128h-128v-640h128v-128h-384z" />
<glyph glyph-name="J" unicode="J"
d="M128 0v128h384v-128h-384zM0 128v128h128v-128h-128zM512 128v768h128v-768h-128z" />
<glyph glyph-name="K" unicode="K"
d="M512 0v384h128v-384h-128zM384 384v128h128v-128h-128zM384 640v128h128v-128h-128zM0 0v896h128v-256h256v-128h-256v-512h-128zM512 768v128h128v-128h-128z" />
<glyph glyph-name="L" unicode="L"
d="M0 0v896h128v-768h512v-128h-640z" />
<glyph glyph-name="M" unicode="M"
d="M256 512v128h128v-128h-128zM0 0v896h128v-128h128v-128h-128v-640h-128zM512 0v640h-128v128h128v128h128v-896h-128z" />
<glyph glyph-name="N" unicode="N"
d="M256 512v128h128v-128h-128zM0 0v896h128v-128h128v-128h-128v-640h-128zM512 0v384h-128v128h128v384h128v-896h-128z" />
<glyph glyph-name="O" unicode="O"
d="M128 0v128h384v-128h-384zM0 128v640h128v-640h-128zM512 128v640h128v-640h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="P" unicode="P"
d="M512 640v128h128v-128h-128zM0 0v896h512v-128h-384v-128h384v-128h-384v-512h-128z" />
<glyph glyph-name="Q" unicode="Q"
d="M128 0v128h256v-128h-256zM512 0v128h128v-128h-128zM384 128v128h128v-128h-128zM0 128v640h128v-640h-128zM512 256v512h128v-512h-128zM128 768v128h384v-128h-384z" />
<glyph glyph-name="R" unicode="R"
d="M512 0v512h128v-512h-128zM512 640v128h128v-128h-128zM0 0v896h512v-128h-384v-128h384v-128h-384v-512h-128z" />
<glyph glyph-name="S" unicode="S"
d="M128 0v128h384v-128h-384zM0 128v128h128v-128h-128zM512 128v384h128v-384h-128zM128 512v128h384v-128h-384zM0 640v128h128v-128h-128zM128 768v128h512v-128h-512z" />
<glyph glyph-name="T" unicode="T"
d="M256 0v768h-256v128h640v-128h-256v-768h-128z" />
<glyph glyph-name="U" unicode="U"
d="M128 0v128h384v-128h-384zM0 128v768h128v-768h-128zM512 128v768h128v-768h-128z" />
<glyph glyph-name="V" unicode="V"
d="M256 0v128h128v-128h-128zM128 128v256h128v-256h-128zM384 128v256h128v-256h-128zM0 384v512h128v-512h-128zM512 384v512h128v-512h-128z" />
<glyph glyph-name="W" unicode="W"
d="M256 256v128h128v-128h-128zM0 0v896h128v-640h128v-128h-128v-128h-128zM512 0v128h-128v128h128v640h128v-896h-128z" />
<glyph glyph-name="X" unicode="X"
d="M0 0v384h128v-384h-128zM512 0v384h128v-384h-128zM128 384v128h128v-128h-128zM384 384v128h128v-128h-128zM256 512v128h128v-128h-128zM128 640v128h128v-128h-128zM384 640v128h128v-128h-128zM0 768v128h128v-128h-128zM512 768v128h128v-128h-128z" />
<glyph glyph-name="Y" unicode="Y"
d="M256 0v640h128v-640h-128zM128 640v128h128v-128h-128zM384 640v128h128v-128h-128zM0 768v128h128v-128h-128zM512 768v128h128v-128h-128z" />
<glyph glyph-name="Z" unicode="Z"
d="M0 0v256h128v-128h512v-128h-640zM128 256v128h128v-128h-128zM256 384v128h128v-128h-128zM384 512v128h128v-128h-128zM512 640v128h-512v128h640v-256h-128z" />
<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="512"
d="M0 0v896h384v-128h-256v-640h256v-128h-384z" />
<glyph glyph-name="backslash" unicode="\"
d="M512 0v128h128v-128h-128zM384 128v256h128v-256h-128zM256 384v128h128v-128h-128zM128 512v256h128v-256h-128zM0 768v128h128v-128h-128z" />
<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="512"
d="M0 0v128h256v640h-256v128h384v-896h-384z" />
<glyph glyph-name="asciicircum" unicode="^"
d="M0 512v128h128v-128h-128zM512 512v128h128v-128h-128zM128 640v128h128v-128h-128zM384 640v128h128v-128h-128zM256 768v128h128v-128h-128z" />
<glyph glyph-name="underscore" unicode="_"
d="M0 -128v128h640v-128h-640z" />
<glyph glyph-name="grave" unicode="`" horiz-adv-x="384"
d="M0 640v128h128v-128h-128zM128 768v128h128v-128h-128z" />
<glyph glyph-name="a" unicode="a"
d="M0 128v128h128v-128h-128zM128 0v128h384v128h-384v128h384v128h128v-512h-512zM128 512v128h384v-128h-384z" />
<glyph glyph-name="b" unicode="b"
d="M512 128v384h128v-384h-128zM256 512v128h256v-128h-256zM0 0v896h128v-384h128v-128h-128v-256h384v-128h-512z" />
<glyph glyph-name="c" unicode="c"
d="M128 0v128h384v-128h-384zM512 128v128h128v-128h-128zM0 128v384h128v-384h-128zM512 384v128h128v-128h-128zM128 512v128h384v-128h-384z" />
<glyph glyph-name="d" unicode="d"
d="M0 128v384h128v-384h-128zM128 512v128h256v-128h-256zM128 0v128h384v256h-128v128h128v384h128v-896h-512z" />
<glyph glyph-name="e" unicode="e"
d="M128 0v128h512v-128h-512zM0 128v384h128v-128h384v128h128v-256h-512v-128h-128zM128 512v128h384v-128h-384z" />
<glyph glyph-name="f" unicode="f" horiz-adv-x="640"
d="M128 0v512h-128v128h128v128h128v-128h256v-128h-256v-512h-128zM256 768v128h256v-128h-256z" />
<glyph glyph-name="g" unicode="g"
d="M0 -128v128h512v-128h-512zM0 256v256h128v-256h-128zM512 0v128h-384v128h384v256h-384v128h512v-640h-128z" />
<glyph glyph-name="h" unicode="h"
d="M512 0v512h128v-512h-128zM256 512v128h256v-128h-256zM0 0v896h128v-384h128v-128h-128v-384h-128z" />
<glyph glyph-name="i" unicode="i" horiz-adv-x="256"
d="M0 0v640h128v-640h-128zM0 768v128h128v-128h-128z" />
<glyph glyph-name="j" unicode="j"
d="M128 -128v128h384v-128h-384zM0 0v256h128v-256h-128zM512 0v640h128v-640h-128zM512 768v128h128v-128h-128z" />
<glyph glyph-name="k" unicode="k" horiz-adv-x="640"
d="M384 0v128h128v-128h-128zM256 128v128h128v-128h-128zM256 384v128h128v-128h-128zM384 512v128h128v-128h-128zM0 0v896h128v-512h128v-128h-128v-256h-128z" />
<glyph glyph-name="l" unicode="l" horiz-adv-x="384"
d="M128 0v128h128v-128h-128zM0 128v768h128v-768h-128z" />
<glyph glyph-name="m" unicode="m"
d="M256 256v256h128v-256h-128zM512 0v512h128v-512h-128zM0 0v640h256v-128h-128v-512h-128zM384 512v128h128v-128h-128z" />
<glyph glyph-name="n" unicode="n"
d="M512 0v512h128v-512h-128zM0 0v640h512v-128h-384v-512h-128z" />
<glyph glyph-name="o" unicode="o"
d="M128 0v128h384v-128h-384zM0 128v384h128v-384h-128zM512 128v384h128v-384h-128zM128 512v128h384v-128h-384z" />
<glyph glyph-name="p" unicode="p"
d="M512 256v256h128v-256h-128zM0 -128v768h128v-128h128v-128h-128v-128h384v-128h-384v-256h-128zM256 512v128h256v-128h-256z" />
<glyph glyph-name="q" unicode="q"
d="M0 256v256h128v-256h-128zM128 512v128h256v-128h-256zM512 -128v256h-384v128h384v128h-128v128h128v128h128v-768h-128z" />
<glyph glyph-name="r" unicode="r"
d="M512 384v128h128v-128h-128zM0 0v640h128v-128h128v-128h-128v-384h-128zM256 512v128h256v-128h-256z" />
<glyph glyph-name="s" unicode="s"
d="M0 0v128h512v-128h-512zM512 128v128h128v-128h-128zM128 256v128h384v-128h-384zM0 384v128h128v-128h-128zM128 512v128h512v-128h-512z" />
<glyph glyph-name="t" unicode="t" horiz-adv-x="512"
d="M256 0v128h128v-128h-128zM128 128v512h-128v128h128v128h128v-128h128v-128h-128v-512h-128z" />
<glyph glyph-name="u" unicode="u"
d="M0 128v512h128v-512h-128zM128 0v128h384v512h128v-640h-512z" />
<glyph glyph-name="v" unicode="v"
d="M256 0v128h128v-128h-128zM128 128v128h128v-128h-128zM384 128v128h128v-128h-128zM0 256v384h128v-384h-128zM512 256v384h128v-384h-128z" />
<glyph glyph-name="w" unicode="w"
d="M0 128v512h128v-512h-128zM128 0v128h128v256h128v-256h128v512h128v-640h-512z" />
<glyph glyph-name="x" unicode="x"
d="M0 0v128h128v-128h-128zM512 0v128h128v-128h-128zM128 128v128h128v-128h-128zM384 128v128h128v-128h-128zM256 256v128h128v-128h-128zM128 384v128h128v-128h-128zM384 384v128h128v-128h-128zM0 512v128h128v-128h-128zM512 512v128h128v-128h-128z" />
<glyph glyph-name="y" unicode="y"
d="M0 -128v128h512v-128h-512zM0 256v384h128v-384h-128zM512 0v128h-384v128h384v384h128v-640h-128z" />
<glyph glyph-name="z" unicode="z"
d="M0 0v128h128v128h128v-128h384v-128h-640zM256 256v128h128v-128h-128zM384 384v128h-384v128h640v-128h-128v-128h-128z" />
<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="640"
d="M256 0v128h256v-128h-256zM128 128v256h128v-256h-128zM0 384v128h128v-128h-128zM128 512v256h128v-256h-128zM256 768v128h256v-128h-256z" />
<glyph glyph-name="bar" unicode="|" horiz-adv-x="256"
d="M0 0v384h128v-384h-128zM0 512v384h128v-384h-128z" />
<glyph glyph-name="braceright" unicode="}" horiz-adv-x="640"
d="M0 0v128h256v-128h-256zM256 128v256h128v-256h-128zM384 384v128h128v-128h-128zM256 512v256h128v-256h-128zM0 768v128h256v-128h-256z" />
<glyph glyph-name="asciitilde" unicode="~" horiz-adv-x="896"
d="M0 640v128h128v-128h-128zM384 640v128h256v-128h-256zM128 768v128h256v-128h-256zM640 768v128h128v-128h-128z" />
<glyph glyph-name="quoteleft" unicode="&#x2018;" horiz-adv-x="384"
d="M0 512v256h128v-256h-128zM128 768v128h128v-128h-128z" />
<glyph glyph-name="quoteright" unicode="&#x2019;" horiz-adv-x="384"
d="M0 512v128h128v-128h-128zM128 640v256h128v-256h-128z" />
<glyph glyph-name="quotedblleft" unicode="&#x201c;" horiz-adv-x="640"
d="M0 512v256h128v-256h-128zM256 512v256h128v-256h-128zM128 768v128h128v-128h-128zM384 768v128h128v-128h-128z" />
<glyph glyph-name="quotedblright" unicode="&#x201d;" horiz-adv-x="640"
d="M0 512v128h128v-128h-128zM256 512v128h128v-128h-128zM128 640v256h128v-256h-128zM384 640v256h128v-256h-128z" />
<glyph glyph-name="exclamdown" unicode="&#xa1;" horiz-adv-x="256"
d="M0 0v640h128v-640h-128zM0 768v128h128v-128h-128z" />
<glyph glyph-name="yen" unicode="&#xa5;"
d="M256 0v128h-128v128h128v128h-128v128h128v128h128v-128h128v-128h-128v-128h128v-128h-128v-128h-128zM128 640v128h128v-128h-128zM384 640v128h128v-128h-128zM0 768v128h128v-128h-128zM512 768v128h128v-128h-128z" />
<glyph glyph-name="dieresis" unicode="&#xa8;" horiz-adv-x="640"
d="M128 768v128h128v-128h-128zM384 768v128h128v-128h-128z" />
<glyph glyph-name="questiondown" unicode="&#xbf;"
d="M128 0v128h384v-128h-384zM512 128v128h128v-128h-128zM0 128v256h128v-256h-128zM128 384v128h128v-128h-128zM256 512v128h128v-128h-128zM256 768v128h128v-128h-128z" />
<glyph glyph-name="Oslash" unicode="&#xd8;" horiz-adv-x="896"
d="M0 0v128h128v-128h-128zM256 0v128h256v-128h-256zM128 128v128h128v-128h-128zM512 128v128h128v-128h-128zM256 256v128h128v-128h-128zM0 256v256h128v-256h-128zM384 384v128h128v-128h-128zM640 256v256h128v-256h-128zM128 512v128h128v-128h-128zM512 512v128h128
v-128h-128zM256 640v128h256v-128h-256zM640 640v128h128v-128h-128z" />
<glyph glyph-name="aring" unicode="&#xe5;"
d="M128 0v128h256v-128h-256zM0 128v384h128v-384h-128zM512 0v128h-128v128h128v256h-384v128h512v-640h-128zM256 768v128h128v-128h-128zM128 896v128h128v-128h-128zM384 896v128h128v-128h-128zM256 1024v128h128v-128h-128z" />
<glyph glyph-name="ae" unicode="&#xe6;" horiz-adv-x="1408"
d="M128 0v128h256v-128h-256zM768 0v128h512v-128h-512zM0 128v384h128v-384h-128zM1152 384v128h128v-128h-128zM512 0v128h-128v128h128v256h-384v128h512v-128h128v-128h384v-128h-384v-128h-128v-128h-128zM768 512v128h384v-128h-384z" />
<glyph glyph-name="oslash" unicode="&#xf8;" horiz-adv-x="896"
d="M0 0v128h128v-128h-128zM256 0v128h256v-128h-256zM128 128v128h128v-128h-128zM512 128v128h128v-128h-128zM256 256v128h128v-128h-128zM0 256v256h128v-256h-128zM384 384v128h128v-128h-128zM640 256v256h128v-256h-128zM128 512v128h128v-128h-128zM512 512v128h128
v-128h-128zM256 640v128h256v-128h-256zM640 640v128h128v-128h-128z" />
</font>
</defs></svg>

BIN
frontend/fonts/mcfont.ttf View File


BIN
frontend/fonts/mcfont.woff View File


BIN
frontend/img/body-bg.png View File

Before After
Width: 48  |  Height: 48  |  Size: 304 B

BIN
frontend/img/button_active_center.png View File

Before After
Width: 196  |  Height: 15  |  Size: 2.2 KiB

BIN
frontend/img/button_center.png View File

Before After
Width: 196  |  Height: 15  |  Size: 2.2 KiB

BIN
frontend/img/header-bg.png View File

Before After
Width: 48  |  Height: 62  |  Size: 340 B

BIN
frontend/img/header-decor.png View File

Before After
Width: 1  |  Height: 10  |  Size: 954 B

+ 52
- 0
frontend/index.html View File

@ -0,0 +1,52 @@
<html>
<head>
<title> Minecraft Server </title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/css/index.css">
<script src="/js/vue.js"></script>
<script src="/js/superagent.js"></script>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<div id="app">
<header>
<h1>Minecraft Server</h1>
</header>
<div v-if="profile" class="container">
<p>
Server Status: <span style="padding-left: 20px;" :class="{ active: status.running, inactive: !status.running }">{{ status.running ? 'Running' : 'Stopped' }}</span>
<button v-show="!status.running" class="pull-right" @click="start()">Start</button>
<button v-show="status.running" class="pull-right" @click="stop()">Stop</button>
</p>
<!-- <p>Settings:</p>
<textarea v-model="settings"></textarea>
<button>Save</button> -->
<div class="logstream">
<p v-for="line in logstream">{{ line }}</p>
</div>
<input type="text" v-model="cmd" placeholder="Send server commands here, try /help" class="command" @keyup.enter="sendCommand()"/>
</div>
<div v-else class="container login" style="max-width: 400px;">
<form @submit.prevent="onLogin">
<p>Username</p>
<input type="text" v-model="login.username" required/>
<p>Password</p>
<input type="password" v-model="login.password" required/>
<p><button type="submit" @click="onLogin" :disabled="loginSubmitBusy">Login</button></p>
</form>
</div>
<footer style="height: 24px">Built by the <a href="https://cloudron.io" target="_blank">Cloudron.io</a> team</footer>
<button v-if="profile" @click="onLogout" class="logout">Logout</button>
</div>
<script src="/js/index.js"></script>
</body>
</html>

+ 152
- 0
frontend/js/index.js View File

@ -0,0 +1,152 @@
'use strict';
/* global Vue */
/* global superagent */
new Vue({
el: '#app',
data: {
login: {
username: '',
password: ''
},
loginSubmitBusy: false,
status: {},
settings: '',
logstream: [],
cmd: '',
profile: null
},
methods: {
onError: function (error) {
console.error(error);
},
onReady: function () {
this.refresh();
this.loadSettings();
this.loadLogs();
},
refresh: function () {
var that = this;
superagent.get('/api/v1/status').auth(this.login.username, this.login.password).end(function (error, result) {
if (error) return that.onError(error);
if (result.statusCode !== 200) return that.onError('Unexpected response: ' + result.statusCode + ' ' + result.text);
that.status = result.body.status;
setTimeout(that.refresh, 5000);
});
},
onLogin: function () {
var that = this;
that.loginSubmitBusy = true;
superagent.get('/api/v1/profile').auth(this.login.username, this.login.password).end(function (error, result) {
that.loginSubmitBusy = false;
if (error && error.status === 401) {
that.$refs.loginInput.focus();
that.login.username = '';
that.login.password = '';
return that.onError('Invalid username or password');
}
if (error) return that.onError(error);
if (result.statusCode !== 200) return that.onError('Unexpected response: ' + result.statusCode + ' ' + result.text);
// stash the credentials in the local storage
window.localStorage.username = that.login.username;
window.localStorage.password = that.login.password;
that.profile = result.body.user;
that.onReady();
});
},
onLogout: function () {
this.profile = null;
// delete the credentials from the local storage
delete window.localStorage.username;
delete window.localStorage.password;
},
start: function () {
var that = this;
superagent.post('/api/v1/start').auth(this.login.username, this.login.password).end(function (error, result) {
if (error) return that.onError(error);
if (result.statusCode !== 202) return that.onError('Unexpected response: ' + result.statusCode + ' ' + result.text);
});
},
stop: function () {
var that = this;
superagent.post('/api/v1/stop').auth(this.login.username, this.login.password).end(function (error, result) {
if (error) return that.onError(error);
if (result.statusCode !== 202) return that.onError('Unexpected response: ' + result.statusCode + ' ' + result.text);
});
},
loadSettings: function () {
var that = this;
superagent.get('/api/v1/settings').auth(this.login.username, this.login.password).end(function (error, result) {
if (error) return that.onError(error);
if (result.statusCode !== 200) return that.onError('Unexpected response: ' + result.statusCode + ' ' + result.text);
that.settings = result.body.settings;
});
},
sendCommand: function () {
var that = this;
superagent.post('/api/v1/command').send({ cmd: this.cmd }).auth(this.login.username, this.login.password).end(function (error, result) {
if (error) return that.onError(error);
if (result.statusCode !== 202) return that.onError('Unexpected response: ' + result.statusCode + ' ' + result.text);
that.cmd = '';
});
},
loadLogs: function () {
var that = this;
var socket = io();
socket.on('line', function (line) {
that.logstream.push(line);
Vue.nextTick(function () {
var elem = document.getElementsByClassName('logstream')[0];
elem.scrollTop = elem.scrollHeight - 100;
});
});
}
},
mounted: function () {
var that = this;
that.login.username = window.localStorage.username || '';
that.login.password = window.localStorage.password || '';
if (!that.login.username || !that.login.password) {
that.profile = null;
return;
}
superagent.get('/api/v1/profile').auth(that.login.username, that.login.password).end(function (error, result) {
if (error && error.status === 401) {
// clear the local storage on wrong credentials
delete window.localStorage.username;
delete window.localStorage.password;
that.profile = null;
return;
}
if (error) return that.onError(error);
if (result.statusCode !== 200) that.onError('Unexpected response: ' + result.statusCode + ' ' + result.text);
that.profile = result.body.user;
that.onReady();
});
}
});

+ 100
- 0
frontend/js/superagent.js View File

@ -0,0 +1,100 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.superagent=f()}})(function(){var define,module,exports;return(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){function Agent(){this._defaults=[];}
["use","on","once","set","query","type","accept","auth","withCredentials","sortQuery","retry","ok","redirects","timeout","buffer","serialize","parse","ca","key","pfx","cert"].forEach(function(fn){Agent.prototype[fn]=function(){this._defaults.push({fn:fn,arguments:arguments});return this;}});Agent.prototype._setDefaults=function(req){this._defaults.forEach(function(def){req[def.fn].apply(req,def.arguments);});};module.exports=Agent;},{}],2:[function(require,module,exports){'use strict';function isObject(obj){return null!==obj&&'object'===typeof obj;}
module.exports=isObject;},{}],3:[function(require,module,exports){'use strict';var isObject=require('./is-object');module.exports=RequestBase;function RequestBase(obj){if(obj)return mixin(obj);}
function mixin(obj){for(var key in RequestBase.prototype){obj[key]=RequestBase.prototype[key];}
return obj;}
RequestBase.prototype.clearTimeout=function _clearTimeout(){clearTimeout(this._timer);clearTimeout(this._responseTimeoutTimer);delete this._timer;delete this._responseTimeoutTimer;return this;};RequestBase.prototype.parse=function parse(fn){this._parser=fn;return this;};RequestBase.prototype.responseType=function(val){this._responseType=val;return this;};RequestBase.prototype.serialize=function serialize(fn){this._serializer=fn;return this;};RequestBase.prototype.timeout=function timeout(options){if(!options||'object'!==typeof options){this._timeout=options;this._responseTimeout=0;return this;}
for(var option in options){switch(option){case 'deadline':this._timeout=options.deadline;break;case 'response':this._responseTimeout=options.response;break;default:console.warn("Unknown timeout option",option);}}
return this;};RequestBase.prototype.retry=function retry(count,fn){if(arguments.length===0||count===true)count=1;if(count<=0)count=0;this._maxRetries=count;this._retries=0;this._retryCallback=fn;return this;};var ERROR_CODES=['ECONNRESET','ETIMEDOUT','EADDRINFO','ESOCKETTIMEDOUT'];RequestBase.prototype._shouldRetry=function(err,res){if(!this._maxRetries||this._retries++>=this._maxRetries){return false;}
if(this._retryCallback){try{var override=this._retryCallback(err,res);if(override===true)return true;if(override===false)return false;}catch(e){console.error(e);}}
if(res&&res.status&&res.status>=500&&res.status!=501)return true;if(err){if(err.code&&~ERROR_CODES.indexOf(err.code))return true;if(err.timeout&&err.code=='ECONNABORTED')return true;if(err.crossDomain)return true;}
return false;};RequestBase.prototype._retry=function(){this.clearTimeout();if(this.req){this.req=null;this.req=this.request();}
this._aborted=false;this.timedout=false;return this._end();};RequestBase.prototype.then=function then(resolve,reject){if(!this._fullfilledPromise){var self=this;if(this._endCalled){console.warn("Warning: superagent request was sent twice, because both .end() and .then() were called. Never call .end() if you use promises");}
this._fullfilledPromise=new Promise(function(innerResolve,innerReject){self.end(function(err,res){if(err)innerReject(err);else innerResolve(res);});});}
return this._fullfilledPromise.then(resolve,reject);};RequestBase.prototype['catch']=function(cb){return this.then(undefined,cb);};RequestBase.prototype.use=function use(fn){fn(this);return this;};RequestBase.prototype.ok=function(cb){if('function'!==typeof cb)throw Error("Callback required");this._okCallback=cb;return this;};RequestBase.prototype._isResponseOK=function(res){if(!res){return false;}
if(this._okCallback){return this._okCallback(res);}
return res.status>=200&&res.status<300;};RequestBase.prototype.get=function(field){return this._header[field.toLowerCase()];};RequestBase.prototype.getHeader=RequestBase.prototype.get;RequestBase.prototype.set=function(field,val){if(isObject(field)){for(var key in field){this.set(key,field[key]);}
return this;}
this._header[field.toLowerCase()]=val;this.header[field]=val;return this;};RequestBase.prototype.unset=function(field){delete this._header[field.toLowerCase()];delete this.header[field];return this;};RequestBase.prototype.field=function(name,val){if(null===name||undefined===name){throw new Error('.field(name, val) name can not be empty');}
if(this._data){console.error(".field() can't be used if .send() is used. Please use only .send() or only .field() & .attach()");}
if(isObject(name)){for(var key in name){this.field(key,name[key]);}
return this;}
if(Array.isArray(val)){for(var i in val){this.field(name,val[i]);}
return this;}
if(null===val||undefined===val){throw new Error('.field(name, val) val can not be empty');}
if('boolean'===typeof val){val=''+val;}
this._getFormData().append(name,val);return this;};RequestBase.prototype.abort=function(){if(this._aborted){return this;}
this._aborted=true;this.xhr&&this.xhr.abort();this.req&&this.req.abort();this.clearTimeout();this.emit('abort');return this;};RequestBase.prototype._auth=function(user,pass,options,base64Encoder){switch(options.type){case 'basic':this.set('Authorization','Basic '+base64Encoder(user+':'+pass));break;case 'auto':this.username=user;this.password=pass;break;case 'bearer':this.set('Authorization','Bearer '+user);break;}
return this;};RequestBase.prototype.withCredentials=function(on){if(on==undefined)on=true;this._withCredentials=on;return this;};RequestBase.prototype.redirects=function(n){this._maxRedirects=n;return this;};RequestBase.prototype.maxResponseSize=function(n){if('number'!==typeof n){throw TypeError("Invalid argument");}
this._maxResponseSize=n;return this;};RequestBase.prototype.toJSON=function(){return{method:this.method,url:this.url,data:this._data,headers:this._header,};};RequestBase.prototype.send=function(data){var isObj=isObject(data);var type=this._header['content-type'];if(this._formData){console.error(".send() can't be used if .attach() or .field() is used. Please use only .send() or only .field() & .attach()");}
if(isObj&&!this._data){if(Array.isArray(data)){this._data=[];}else if(!this._isHost(data)){this._data={};}}else if(data&&this._data&&this._isHost(this._data)){throw Error("Can't merge these send calls");}
if(isObj&&isObject(this._data)){for(var key in data){this._data[key]=data[key];}}else if('string'==typeof data){if(!type)this.type('form');type=this._header['content-type'];if('application/x-www-form-urlencoded'==type){this._data=this._data?this._data+'&'+data:data;}else{this._data=(this._data||'')+data;}}else{this._data=data;}
if(!isObj||this._isHost(data)){return this;}
if(!type)this.type('json');return this;};RequestBase.prototype.sortQuery=function(sort){this._sort=typeof sort==='undefined'?true:sort;return this;};RequestBase.prototype._finalizeQueryString=function(){var query=this._query.join('&');if(query){this.url+=(this.url.indexOf('?')>=0?'&':'?')+query;}
this._query.length=0;if(this._sort){var index=this.url.indexOf('?');if(index>=0){var queryArr=this.url.substring(index+1).split('&');if('function'===typeof this._sort){queryArr.sort(this._sort);}else{queryArr.sort();}
this.url=this.url.substring(0,index)+'?'+queryArr.join('&');}}};RequestBase.prototype._appendQueryString=function(){console.trace("Unsupported");}
RequestBase.prototype._timeoutError=function(reason,timeout,errno){if(this._aborted){return;}
var err=new Error(reason+timeout+'ms exceeded');err.timeout=timeout;err.code='ECONNABORTED';err.errno=errno;this.timedout=true;this.abort();this.callback(err);};RequestBase.prototype._setTimeouts=function(){var self=this;if(this._timeout&&!this._timer){this._timer=setTimeout(function(){self._timeoutError('Timeout of ',self._timeout,'ETIME');},this._timeout);}
if(this._responseTimeout&&!this._responseTimeoutTimer){this._responseTimeoutTimer=setTimeout(function(){self._timeoutError('Response timeout of ',self._responseTimeout,'ETIMEDOUT');},this._responseTimeout);}};},{"./is-object":2}],4:[function(require,module,exports){'use strict';var utils=require('./utils');module.exports=ResponseBase;function ResponseBase(obj){if(obj)return mixin(obj);}
function mixin(obj){for(var key in ResponseBase.prototype){obj[key]=ResponseBase.prototype[key];}
return obj;}
ResponseBase.prototype.get=function(field){return this.header[field.toLowerCase()];};ResponseBase.prototype._setHeaderProperties=function(header){var ct=header['content-type']||'';this.type=utils.type(ct);var params=utils.params(ct);for(var key in params)this[key]=params[key];this.links={};try{if(header.link){this.links=utils.parseLinks(header.link);}}catch(err){}};ResponseBase.prototype._setStatusProperties=function(status){var type=status/100|0;this.status=this.statusCode=status;this.statusType=type;this.info=1==type;this.ok=2==type;this.redirect=3==type;this.clientError=4==type;this.serverError=5==type;this.error=(4==type||5==type)?this.toError():false;this.created=201==status;this.accepted=202==status;this.noContent=204==status;this.badRequest=400==status;this.unauthorized=401==status;this.notAcceptable=406==status;this.forbidden=403==status;this.notFound=404==status;this.unprocessableEntity=422==status;};},{"./utils":5}],5:[function(require,module,exports){'use strict';exports.type=function(str){return str.split(/ *; */).shift();};exports.params=function(str){return str.split(/ *; */).reduce(function(obj,str){var parts=str.split(/ *= */);var key=parts.shift();var val=parts.shift();if(key&&val)obj[key]=val;return obj;},{});};exports.parseLinks=function(str){return str.split(/ *, */).reduce(function(obj,str){var parts=str.split(/ *; */);var url=parts[0].slice(1,-1);var rel=parts[1].split(/ *= */)[1].slice(1,-1);obj[rel]=url;return obj;},{});};exports.cleanHeader=function(header,changesOrigin){delete header['content-type'];delete header['content-length'];delete header['transfer-encoding'];delete header['host'];if(changesOrigin){delete header['authorization'];delete header['cookie'];}
return header;};},{}],6:[function(require,module,exports){if(typeof module!=='undefined'){module.exports=Emitter;}
function Emitter(obj){if(obj)return mixin(obj);};function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key];}
return obj;}
Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks['$'+event]=this._callbacks['$'+event]||[]).push(fn);return this;};Emitter.prototype.once=function(event,fn){function on(){this.off(event,on);fn.apply(this,arguments);}
on.fn=fn;this.on(event,on);return this;};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this;}
var callbacks=this._callbacks['$'+event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks['$'+event];return this;}
var cb;for(var i=0;i<callbacks.length;i++){cb=callbacks[i];if(cb===fn||cb.fn===fn){callbacks.splice(i,1);break;}}
return this;};Emitter.prototype.emit=function(event){this._callbacks=this._callbacks||{};var args=[].slice.call(arguments,1),callbacks=this._callbacks['$'+event];if(callbacks){callbacks=callbacks.slice(0);for(var i=0,len=callbacks.length;i<len;++i){callbacks[i].apply(this,args);}}
return this;};Emitter.prototype.listeners=function(event){this._callbacks=this._callbacks||{};return this._callbacks['$'+event]||[];};Emitter.prototype.hasListeners=function(event){return!!this.listeners(event).length;};},{}],7:[function(require,module,exports){var root;if(typeof window!=='undefined'){root=window;}else if(typeof self!=='undefined'){root=self;}else{console.warn("Using browser-only version of superagent in non-browser environment");root=this;}
var Emitter=require('component-emitter');var RequestBase=require('./request-base');var isObject=require('./is-object');var ResponseBase=require('./response-base');var Agent=require('./agent-base');function noop(){};var request=exports=module.exports=function(method,url){if('function'==typeof url){return new exports.Request('GET',method).end(url);}
if(1==arguments.length){return new exports.Request('GET',method);}
return new exports.Request(method,url);}
exports.Request=Request;request.getXHR=function(){if(root.XMLHttpRequest&&(!root.location||'file:'!=root.location.protocol||!root.ActiveXObject)){return new XMLHttpRequest;}else{try{return new ActiveXObject('Microsoft.XMLHTTP');}catch(e){}
try{return new ActiveXObject('Msxml2.XMLHTTP.6.0');}catch(e){}
try{return new ActiveXObject('Msxml2.XMLHTTP.3.0');}catch(e){}
try{return new ActiveXObject('Msxml2.XMLHTTP');}catch(e){}}
throw Error("Browser-only version of superagent could not find XHR");};var trim=''.trim?function(s){return s.trim();}:function(s){return s.replace(/(^\s*|\s*$)/g,'');};function serialize(obj){if(!isObject(obj))return obj;var pairs=[];for(var key in obj){pushEncodedKeyValuePair(pairs,key,obj[key]);}
return pairs.join('&');}
function pushEncodedKeyValuePair(pairs,key,val){if(val!=null){if(Array.isArray(val)){val.forEach(function(v){pushEncodedKeyValuePair(pairs,key,v);});}else if(isObject(val)){for(var subkey in val){pushEncodedKeyValuePair(pairs,key+'['+subkey+']',val[subkey]);}}else{pairs.push(encodeURIComponent(key)
+'='+encodeURIComponent(val));}}else if(val===null){pairs.push(encodeURIComponent(key));}}
request.serializeObject=serialize;function parseString(str){var obj={};var pairs=str.split('&');var pair;var pos;for(var i=0,len=pairs.length;i<len;++i){pair=pairs[i];pos=pair.indexOf('=');if(pos==-1){obj[decodeURIComponent(pair)]='';}else{obj[decodeURIComponent(pair.slice(0,pos))]=decodeURIComponent(pair.slice(pos+1));}}
return obj;}
request.parseString=parseString;request.types={html:'text/html',json:'application/json',xml:'text/xml',urlencoded:'application/x-www-form-urlencoded','form':'application/x-www-form-urlencoded','form-data':'application/x-www-form-urlencoded'};request.serialize={'application/x-www-form-urlencoded':serialize,'application/json':JSON.stringify};request.parse={'application/x-www-form-urlencoded':parseString,'application/json':JSON.parse};function parseHeader(str){var lines=str.split(/\r?\n/);var fields={};var index;var line;var field;var val;for(var i=0,len=lines.length;i<len;++i){line=lines[i];index=line.indexOf(':');if(index===-1){continue;}
field=line.slice(0,index).toLowerCase();val=trim(line.slice(index+1));fields[field]=val;}
return fields;}
function isJSON(mime){return /[\/+]json($|[^-\w])/.test(mime);}
function Response(req){this.req=req;this.xhr=this.req.xhr;this.text=((this.req.method!='HEAD'&&(this.xhr.responseType===''||this.xhr.responseType==='text'))||typeof this.xhr.responseType==='undefined')?this.xhr.responseText:null;this.statusText=this.req.xhr.statusText;var status=this.xhr.status;if(status===1223){status=204;}
this._setStatusProperties(status);this.header=this.headers=parseHeader(this.xhr.getAllResponseHeaders());this.header['content-type']=this.xhr.getResponseHeader('content-type');this._setHeaderProperties(this.header);if(null===this.text&&req._responseType){this.body=this.xhr.response;}else{this.body=this.req.method!='HEAD'?this._parseBody(this.text?this.text:this.xhr.response):null;}}
ResponseBase(Response.prototype);Response.prototype._parseBody=function(str){var parse=request.parse[this.type];if(this.req._parser){return this.req._parser(this,str);}
if(!parse&&isJSON(this.type)){parse=request.parse['application/json'];}
return parse&&str&&(str.length||str instanceof Object)?parse(str):null;};Response.prototype.toError=function(){var req=this.req;var method=req.method;var url=req.url;var msg='cannot '+method+' '+url+' ('+this.status+')';var err=new Error(msg);err.status=this.status;err.method=method;err.url=url;return err;};request.Response=Response;function Request(method,url){var self=this;this._query=this._query||[];this.method=method;this.url=url;this.header={};this._header={};this.on('end',function(){var err=null;var res=null;try{res=new Response(self);}catch(e){err=new Error('Parser is unable to parse the response');err.parse=true;err.original=e;if(self.xhr){err.rawResponse=typeof self.xhr.responseType=='undefined'?self.xhr.responseText:self.xhr.response;err.status=self.xhr.status?self.xhr.status:null;err.statusCode=err.status;}else{err.rawResponse=null;err.status=null;}
return self.callback(err);}
self.emit('response',res);var new_err;try{if(!self._isResponseOK(res)){new_err=new Error(res.statusText||'Unsuccessful HTTP response');}}catch(custom_err){new_err=custom_err;}
if(new_err){new_err.original=err;new_err.response=res;new_err.status=res.status;self.callback(new_err,res);}else{self.callback(null,res);}});}
Emitter(Request.prototype);RequestBase(Request.prototype);Request.prototype.type=function(type){this.set('Content-Type',request.types[type]||type);return this;};Request.prototype.accept=function(type){this.set('Accept',request.types[type]||type);return this;};Request.prototype.auth=function(user,pass,options){if(1===arguments.length)pass='';if(typeof pass==='object'&&pass!==null){options=pass;pass='';}
if(!options){options={type:'function'===typeof btoa?'basic':'auto',};}
var encoder=function(string){if('function'===typeof btoa){return btoa(string);}
throw new Error('Cannot use basic auth, btoa is not a function');};return this._auth(user,pass,options,encoder);};Request.prototype.query=function(val){if('string'!=typeof val)val=serialize(val);if(val)this._query.push(val);return this;};Request.prototype.attach=function(field,file,options){if(file){if(this._data){throw Error("superagent can't mix .send() and .attach()");}
this._getFormData().append(field,file,options||file.name);}
return this;};Request.prototype._getFormData=function(){if(!this._formData){this._formData=new root.FormData();}
return this._formData;};Request.prototype.callback=function(err,res){if(this._shouldRetry(err,res)){return this._retry();}
var fn=this._callback;this.clearTimeout();if(err){if(this._maxRetries)err.retries=this._retries-1;this.emit('error',err);}
fn(err,res);};Request.prototype.crossDomainError=function(){var err=new Error('Request has been terminated\nPossible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.');err.crossDomain=true;err.status=this.status;err.method=this.method;err.url=this.url;this.callback(err);};Request.prototype.buffer=Request.prototype.ca=Request.prototype.agent=function(){console.warn("This is not supported in browser version of superagent");return this;};Request.prototype.pipe=Request.prototype.write=function(){throw Error("Streaming is not supported in browser version of superagent");};Request.prototype._isHost=function _isHost(obj){return obj&&'object'===typeof obj&&!Array.isArray(obj)&&Object.prototype.toString.call(obj)!=='[object Object]';}
Request.prototype.end=function(fn){if(this._endCalled){console.warn("Warning: .end() was called twice. This is not supported in superagent");}
this._endCalled=true;this._callback=fn||noop;this._finalizeQueryString();return this._end();};Request.prototype._end=function(){var self=this;var xhr=(this.xhr=request.getXHR());var data=this._formData||this._data;this._setTimeouts();xhr.onreadystatechange=function(){var readyState=xhr.readyState;if(readyState>=2&&self._responseTimeoutTimer){clearTimeout(self._responseTimeoutTimer);}
if(4!=readyState){return;}
var status;try{status=xhr.status}catch(e){status=0;}
if(!status){if(self.timedout||self._aborted)return;return self.crossDomainError();}
self.emit('end');};var handleProgress=function(direction,e){if(e.total>0){e.percent=e.loaded/e.total*100;}
e.direction=direction;self.emit('progress',e);};if(this.hasListeners('progress')){try{xhr.onprogress=handleProgress.bind(null,'download');if(xhr.upload){xhr.upload.onprogress=handleProgress.bind(null,'upload');}}catch(e){}}
try{if(this.username&&this.password){xhr.open(this.method,this.url,true,this.username,this.password);}else{xhr.open(this.method,this.url,true);}}catch(err){return this.callback(err);}
if(this._withCredentials)xhr.withCredentials=true;if(!this._formData&&'GET'!=this.method&&'HEAD'!=this.method&&'string'!=typeof data&&!this._isHost(data)){var contentType=this._header['content-type'];var serialize=this._serializer||request.serialize[contentType?contentType.split(';')[0]:''];if(!serialize&&isJSON(contentType)){serialize=request.serialize['application/json'];}
if(serialize)data=serialize(data);}
for(var field in this.header){if(null==this.header[field])continue;if(this.header.hasOwnProperty(field))
xhr.setRequestHeader(field,this.header[field]);}
if(this._responseType){xhr.responseType=this._responseType;}
this.emit('request',this);xhr.send(typeof data!=='undefined'?data:null);return this;};request.agent=function(){return new Agent();};["GET","POST","OPTIONS","PATCH","PUT","DELETE"].forEach(function(method){Agent.prototype[method.toLowerCase()]=function(url,fn){var req=new request.Request(method,url);this._setDefaults(req);if(fn){req.end(fn);}
return req;};});Agent.prototype.del=Agent.prototype['delete'];request.get=function(url,data,fn){var req=request('GET',url);if('function'==typeof data)(fn=data),(data=null);if(data)req.query(data);if(fn)req.end(fn);return req;};request.head=function(url,data,fn){var req=request('HEAD',url);if('function'==typeof data)(fn=data),(data=null);if(data)req.query(data);if(fn)req.end(fn);return req;};request.options=function(url,data,fn){var req=request('OPTIONS',url);if('function'==typeof data)(fn=data),(data=null);if(data)req.send(data);if(fn)req.end(fn);return req;};function del(url,data,fn){var req=request('DELETE',url);if('function'==typeof data)(fn=data),(data=null);if(data)req.send(data);if(fn)req.end(fn);return req;}
request['del']=del;request['delete']=del;request.patch=function(url,data,fn){var req=request('PATCH',url);if('function'==typeof data)(fn=data),(data=null);if(data)req.send(data);if(fn)req.end(fn);return req;};request.post=function(url,data,fn){var req=request('POST',url);if('function'==typeof data)(fn=data),(data=null);if(data)req.send(data);if(fn)req.end(fn);return req;};request.put=function(url,data,fn){var req=request('PUT',url);if('function'==typeof data)(fn=data),(data=null);if(data)req.send(data);if(fn)req.end(fn);return req;};},{"./agent-base":1,"./is-object":2,"./request-base":3,"./response-base":4,"component-emitter":6}]},{},[7])(7)});

+ 6
- 0
frontend/js/vue.js
File diff suppressed because it is too large
View File


+ 10
- 102
index.js View File

@ -1,110 +1,18 @@
'use strict';
var express = require('express'),
path = require('path'),
bodyParser = require('body-parser'),
fs = require('fs');
var minecraft = null;
var configFilePath = path.join('/app/data/', 'server.properties');
var opsFilePath = path.join('/app/data/', 'ops.txt');
function startMinecraft() {
console.log('start minecraft server');
minecraft = require('child_process').spawn('java', ['-Xmx1024M', '-Xms1024M', '-jar', path.join(__dirname, 'minecraft_server.jar'), 'nogui'], { cwd: '/app/data/' });
minecraft.stdout.pipe(process.stdout);
minecraft.stderr.pipe(process.stderr);
minecraft.on('close', function () {
minecraft = null;
});
}
function stopMinecraft(callback) {
console.log('stop minecraft server');
if (!minecraft) return callback();
minecraft.kill();
minecraft.on('close', function () {
minecraft = null;
callback();
});
}
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.get('/healthcheck', function (req, res) {
res.sendStatus(200);
});
#!/usr/bin/env node
app.get('/config', function (req, res) {
fs.readFile(configFilePath, function (error, result) {
if (error) return res.status(500).send(error);
res.sendStatus(200, result);
});
});
app.post('/config', function (req, res) {
stopMinecraft(function () {
fs.writeFile(configFilePath, req.body.config, function (error) {
if (error) return res.status(500).send(error);
startMinecraft();
res.sendStatus(200);
});
});
});
app.get('/ops', function (req, res) {
fs.readFile(opsFilePath, function (error, result) {
if (error) return res.status(500).send(error);
res.status(200).send(result);
});
});
app.post('/ops', function (req, res) {
stopMinecraft(function () {
fs.writeFile(opsFilePath, req.body.ops, function (error) {
if (error) res.status(500).send(error);
startMinecraft();
'use strict';
res.sendStatus(200);
});
});
});
require('supererror');
app.get('/status', function (req, res) {
res.status(200).send({ running: !!minecraft });
});
var server = require('./backend/server.js'),
minecraft = require('./backend/minecraft.js');
app.post('/start', function (req, res) {
startMinecraft();
res.sendStatus(200);
});
const PORT = process.env.PORT || 3000;
app.post('/stop', function (req, res) {
stopMinecraft(function () {
res.sendStatus(200);
});
});
server.start(parseInt(PORT), function (error) {
if (error) return console.error('Failed to start server.', error);
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log(`Server is up and running on port ${PORT}`);
console.log('Configuration server listening at http://%s:%s', host, port);
minecraft.start();
});

+ 1072
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 12
- 2
package.json View File

@ -13,7 +13,17 @@
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.12.3",
"express": "^4.12.3"
"body-parser": "^1.18.3",
"byline": "^5.0.0",
"compression": "^1.7.3",
"connect-lastmile": "^1.0.2",
"connect-loki": "^1.1.0",
"connect-timeout": "^1.9.0",
"express": "^4.16.4",
"express-session": "^1.15.6",
"ldapjs": "^1.0.2",
"morgan": "^1.9.1",
"socket.io": "^2.2.0",
"supererror": "^0.7.2"
}
}

+ 2
- 2
start.sh View File

@ -5,8 +5,8 @@ set -eu
echo "=> Ensure directories"
mkdir -p /app/data/
echo "=> Copy EULA"
cp /app/code/eula.txt /app/data/eula.txt
echo "=> Accept EULA"
echo "eula=true" > /app/data/eula.txt
echo "=> Starting management server"
node /app/code/index.js

Loading…
Cancel
Save