mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 15:04:52 +09:00
[big change] Added serverManager plugin
This commit is contained in:
1 parent
852311e84c
commit
e2566d2d1c
9 files changed
+498
No files matched your search
@@ -51,6 +51,7 @@ sharedPlugins/*
|
||||
!sharedPlugins/researchSync/
|
||||
!sharedPlugins/globalChat/
|
||||
!sharedPlugins/clusterioMod/
|
||||
!sharedPlugins/serverManager/
|
||||
|
||||
# File used for marking plugins as disabled
|
||||
sharedPlugins/*/DISABLED
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
Clusterio plugin for remote server management
|
||||
*/
|
||||
module.exports = {
|
||||
name: "serverManager",
|
||||
version: "1.0.0",
|
||||
binary: "nodePackage",
|
||||
description: "Server management tools for remote config edits and web based server management",
|
||||
masterPlugin: "masterPlugin.js",
|
||||
dependencies: [
|
||||
"playerManager",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
const needle = require("needle");
|
||||
const fs = require("fs-extra");
|
||||
|
||||
const pluginConfig = require("./config");
|
||||
const COMPRESS_LUA = false;
|
||||
|
||||
module.exports = class remoteCommands {
|
||||
constructor(mergedConfig, messageInterface, extras){
|
||||
this.messageInterface = messageInterface;
|
||||
this.config = mergedConfig;
|
||||
this.socket = extras.socket;
|
||||
const pluginManager = require("./../../lib/manager/pluginManager.js")(this.config);
|
||||
const configManager = require("./../../lib/manager/configManager.js");
|
||||
// const modManager = require("./../../lib/manager/modManager.js")(this.config);
|
||||
this.socket.on("hello", () => {
|
||||
this.socket.emit("registerServerManager");
|
||||
});
|
||||
this.socket.on("serverManagerGetStatus", async data => {
|
||||
|
||||
});
|
||||
this.socket.on("serverManagerGetMods", async (data, callback) => {
|
||||
// TODO: Implement this function separately from listMods (which console.logs it)
|
||||
// let mods = modManager.getMods()
|
||||
// callback(mods);
|
||||
});
|
||||
this.socket.on("serverManagerGetConfig", async (data, callback) => {
|
||||
let config = await configManager.getConfig(data.instance);
|
||||
callback(config);
|
||||
});
|
||||
this.socket.on("serverManagerEditConfig", async (data, callback) => {
|
||||
callback(configManager.editConfig(data.instance, data.entry, data.value));
|
||||
});
|
||||
this.socket.on("serverManagerGetPlugins", async (data, callback) => {
|
||||
callback(await pluginManager.getPlugins());
|
||||
});
|
||||
this.socket.on("serverManagerEnablePlugin", async (data, callback) => {
|
||||
callback(await pluginManager.enablePlugin(data.name, data.instanceID /* Currently ignored */));
|
||||
});
|
||||
this.socket.on("serverManagerDisablePlugin", async (data, callback) => {
|
||||
callback(await pluginManager.disablePlugin(data.name, data.instanceID /* Currently ignored */));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
module.exports = that => {
|
||||
/**
|
||||
- Enable a plugin by deleting the DISABLED file
|
||||
- Use socket.io to update masters list of plugins for this server
|
||||
|
||||
Give standard JSON object response
|
||||
*/
|
||||
that.app.post("/api/serverManager/enablePlugin", async (req, res) => {
|
||||
if((req.body.token || req.cookies.token)
|
||||
&& req.body.instanceID
|
||||
&& typeof req.body.instanceID == "string"
|
||||
&& req.body.pluginName
|
||||
&& typeof req.body.pluginName == "string"
|
||||
&& that.playerManager){
|
||||
let perms = await that.playerManager.main.getPermissions((req.body.token || req.cookies.token));
|
||||
if(perms.cluster.includes("enablePlugin")
|
||||
|| (perms.instance[req.body.instanceID]
|
||||
&& perms.instance[req.body.instanceID].includes("enablePlugin"))){
|
||||
that.serverSockets[req.body.instanceID].socket.emit("serverManagerEnablePlugin", {
|
||||
name: req.body.pluginName,
|
||||
instanceID: req.body.instanceID,
|
||||
}, async resp => {
|
||||
if(resp.ok){
|
||||
await updateData("plugins", req.body.instanceID);
|
||||
}
|
||||
res.send(resp);
|
||||
});
|
||||
} else {
|
||||
res.send({
|
||||
ok:false,
|
||||
msg:"Insufficient permissions",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.send({
|
||||
ok:false,
|
||||
msg:"Invalid request body",
|
||||
});
|
||||
}
|
||||
});
|
||||
/**
|
||||
- Disable a plugin by adding an empty file named DISABLED to the plugins install pathname
|
||||
- Use socket.io to update masters lost of plugins for this server
|
||||
|
||||
Give standard JSON object response
|
||||
*/
|
||||
that.app.post("/api/serverManager/disablePlugin", async (req, res) => {
|
||||
if((req.body.token || req.cookies.token)
|
||||
&& req.body.instanceID
|
||||
&& typeof req.body.instanceID == "string"
|
||||
&& req.body.pluginName
|
||||
&& typeof req.body.pluginName == "string"
|
||||
&& that.playerManager){
|
||||
let perms = await that.playerManager.main.getPermissions((req.body.token || req.cookies.token));
|
||||
if(perms.cluster.includes("disablePlugin")
|
||||
|| (perms.instance[req.body.instanceID]
|
||||
&& perms.instance[req.body.instanceID].includes("disablePlugin"))){
|
||||
that.serverSockets[req.body.instanceID].socket.emit("serverManagerDisablePlugin", {
|
||||
name: req.body.pluginName,
|
||||
instanceID: req.body.instanceID,
|
||||
}, async resp => {
|
||||
if(resp.ok){
|
||||
await updateData("plugins", req.body.instanceID);
|
||||
}
|
||||
res.send(resp);
|
||||
});
|
||||
} else {
|
||||
res.send({
|
||||
ok:false,
|
||||
msg:"Insufficient permissions",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.send({
|
||||
ok:false,
|
||||
msg:"Invalid request body",
|
||||
});
|
||||
}
|
||||
});
|
||||
that.app.post("/api/serverManager/editConfig", async (req,res) => {
|
||||
if((req.body.token || req.cookies.token)
|
||||
&& req.body.instanceID
|
||||
&& typeof req.body.instanceID == "string"
|
||||
&& req.body.entry
|
||||
&& typeof req.body.entry == "string"
|
||||
&& req.body.value !== undefined
|
||||
&& that.playerManager){
|
||||
let perms = await that.playerManager.main.getPermissions((req.body.token || req.cookies.token));
|
||||
if(perms.cluster.includes("writeConfig")
|
||||
|| (perms.instance[req.body.instanceID]
|
||||
&& perms.instance[req.body.instanceID].includes("writeConfig"))){
|
||||
that.serverSockets[req.body.instanceID].socket.emit("serverManagerEditConfig", {
|
||||
entry: req.body.entry,
|
||||
value: req.body.value,
|
||||
}, async resp => {
|
||||
if(resp.ok){
|
||||
// Retrieve the latest config from the server and store it
|
||||
await updateData("config", req.body.instanceID);
|
||||
}
|
||||
res.send(resp);
|
||||
});
|
||||
} else {
|
||||
res.send({
|
||||
ok:false,
|
||||
msg:"Insufficient permissions",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.send({
|
||||
ok:false,
|
||||
msg:"Invalid request body",
|
||||
});
|
||||
}
|
||||
});
|
||||
async function updateData(type, instanceID){
|
||||
// Retrieve the latest ${type} from the server and store it
|
||||
if(type == "config"){
|
||||
that.serverSockets[instanceID].socket.emit("serverManagerGetConfig", {}, data => {
|
||||
that.serverSockets[instanceID].config = data;
|
||||
});
|
||||
} else if(type == "plugins"){
|
||||
that.serverSockets[instanceID].socket.emit("serverManagerGetPlugins", {}, data => {
|
||||
that.serverSockets[instanceID].plugins = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
let masterPlugin;
|
||||
|
||||
module.exports = {
|
||||
injectThis: function(x){masterPlugin = x; return this},
|
||||
ui: {
|
||||
slaveListing: [
|
||||
{
|
||||
name:"ServerMangerButton",
|
||||
getHtml: ({slave}) => {
|
||||
let html = "";
|
||||
if(masterPlugin.serverSockets[slave.unique]){
|
||||
let socket = masterPlugin.serverSockets[slave.unique];
|
||||
return `<a style="margin: 5px;" href='/serverManager?instanceID=${slave.unique}'>Server manager</a>`
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const Express = require("express");
|
||||
const ejs = require("ejs");
|
||||
const bcrypt = require("bcrypt-promise");
|
||||
const crypto = require('crypto');
|
||||
const base64url = require('base64url');
|
||||
const sanitizer = require('sanitizer');
|
||||
|
||||
|
||||
class masterPlugin {
|
||||
constructor({config, pluginConfig, pluginPath, socketio, express}){
|
||||
this.config = config;
|
||||
this.pluginConfig = pluginConfig;
|
||||
this.pluginPath = pluginPath;
|
||||
this.io = socketio;
|
||||
this.app = express;
|
||||
|
||||
// load databases
|
||||
// const database = getDatabaseSync("database/playerManager.json");
|
||||
// this.whitelist = getDatabaseSync("database/whitelist.json").whitelist || [];
|
||||
// this.banlist = getDatabaseSync("database/banlist.json").banlist || [];
|
||||
|
||||
// initialize web API
|
||||
require("./js/api-endpoints.js")(this);
|
||||
|
||||
// expose UI elements embedded in the master
|
||||
this.ui = require("./js/ui.js").injectThis(this).ui;
|
||||
|
||||
this.serverSockets = {};
|
||||
|
||||
this.io.on("connection", socket => {
|
||||
let instanceID = "unknown";
|
||||
let isServerManager = false;
|
||||
socket.on("registerSlave", data => {
|
||||
if(data.instanceID && !isNaN(Number(data.instanceID))){
|
||||
instanceID = data.instanceID;
|
||||
if(isServerManager){
|
||||
this.serverSockets[instanceID] = {socket};
|
||||
validServerManager({server: this.serverSockets[instanceID]});
|
||||
}
|
||||
}
|
||||
});
|
||||
socket.on("registerServerManager", () => {
|
||||
isServerManager = true;
|
||||
if(instanceID !== "unknown"){
|
||||
this.serverSockets[instanceID] = {socket};
|
||||
validServerManager({server: this.serverSockets[instanceID]});
|
||||
}
|
||||
console.log("Registered serverManager socket")
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
if(this.serverSockets[instanceID]) delete this.serverSockets[instanceID];
|
||||
console.log(`serverManager ${instanceID} disconnected, ${(Object.keys(this.serverSockets).length)} left`);
|
||||
});
|
||||
});
|
||||
socket.on("gameChat", async data => {
|
||||
// let chatLine = data.data.replace(/(\r\n\t|\n|\r\t)/gm, "").replace("\r", "");
|
||||
// if(typeof chatLine == "string") this.handleChatLine(chatLine, instanceID);
|
||||
});
|
||||
});
|
||||
let validServerManager = ({server}) => {
|
||||
server.socket.emit("serverManagerGetPlugins", {}, data => {
|
||||
server.plugins = data;
|
||||
});
|
||||
server.socket.emit("serverManagerGetConfig", {}, data => {
|
||||
server.config = data;
|
||||
});
|
||||
}
|
||||
|
||||
// I can't seem to get express static pages + ejs rendering to work properly, so I write my own thing.
|
||||
let pages = [
|
||||
{
|
||||
addr: "/serverManager",
|
||||
path: path.join(__dirname,"static/index.html"),
|
||||
render: ejs
|
||||
},
|
||||
]
|
||||
pages.forEach(page => {
|
||||
this.app.get(page.addr, async (req,res) => {
|
||||
if(page.render){
|
||||
if((req.query.token || req.cookies.token) && this.playerManager){
|
||||
var permissions = await this.playerManager.main.getPermissions((req.query.token || req.cookies.token));
|
||||
} else {
|
||||
var permissions = {};
|
||||
}
|
||||
page.render.renderFile(page.path, {
|
||||
permissions,
|
||||
serverManager: this,
|
||||
req: req,
|
||||
}, async (err, prom) => {
|
||||
if(err) console.log(err);
|
||||
let str = await prom;
|
||||
res.send(str);
|
||||
});
|
||||
} else {
|
||||
res.send(await fs.readFile(page.path));
|
||||
}
|
||||
});
|
||||
});
|
||||
this.app.use('/serverManager', Express.static(path.join(__dirname, 'static')));
|
||||
}
|
||||
findInArray(key, value, array){
|
||||
let indexes = [];
|
||||
for(let i in array){
|
||||
if(array[i][key] && array[i][key] === value) indexes.push(i);
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
async onExit(){
|
||||
// await saveDatabase("database/banlist.json", {banlist: this.banlist});
|
||||
return;
|
||||
}
|
||||
async onLoadFinish({plugins}){
|
||||
this.masterPlugins = plugins;
|
||||
plugins.forEach(plugin => {
|
||||
if(plugin.pluginConfig.name == "playerManager"){
|
||||
this.playerManager = plugin;
|
||||
}
|
||||
});
|
||||
}
|
||||
async onPlayerManagerGetPermissions({permissions, user}){
|
||||
if(user
|
||||
&& user.admin){
|
||||
permissions.cluster.push("addPlugin");
|
||||
permissions.cluster.push("removePlugin");
|
||||
permissions.cluster.push("enablePlugin");
|
||||
permissions.cluster.push("disablePlugin");
|
||||
permissions.cluster.push("readConfig");
|
||||
permissions.cluster.push("writeConfig");
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
}
|
||||
module.exports = masterPlugin;
|
||||
|
||||
function getDatabaseSync(path){
|
||||
let db;
|
||||
try {
|
||||
db = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||
} catch(e){
|
||||
db = {};
|
||||
}
|
||||
return db;
|
||||
}
|
||||
async function saveDatabase(path, database){
|
||||
if(!path){
|
||||
throw new Error("No path provided!");
|
||||
} else if(!database){
|
||||
throw new Error("No database provided!");
|
||||
} else {
|
||||
try {
|
||||
await fs.writeFile(path, JSON.stringify(database, null, 4));
|
||||
} catch(e){
|
||||
throw new Error("Unable to write to database! "+path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<!doctype HTML>
|
||||
<head>
|
||||
<%- include("./../../../static/head.html") %>
|
||||
<link rel="stylesheet" type="text/css" href="/serverManager/serverManager.css">
|
||||
<!-- <script defer type="module" src="/serverManager/index.js"></script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include("./../../../static/sidebar.html") %>
|
||||
<div id="body">
|
||||
<div id="serverlistDisplayContainer" class="serverManagerMainContainer">
|
||||
<%
|
||||
html = "";
|
||||
if(req.query
|
||||
&& req.query.instanceID
|
||||
&& slaves[req.query.instanceID]
|
||||
&& slaves[req.query.instanceID].mods
|
||||
&& serverManager.serverSockets[req.query.instanceID]){
|
||||
let slave = slaves[req.query.instanceID];
|
||||
html += `
|
||||
<div id="mods">
|
||||
${slave.mods.map(mod => mod.name).reduce((acc, modname) => acc + "<li>" + modname + "</li>")}
|
||||
</div>
|
||||
<div id="plugins"><ul>`
|
||||
let server = serverManager.serverSockets[req.query.instanceID];
|
||||
if(server.plugins) server.plugins.forEach(plugin => {
|
||||
html += `<li><div class="pluginStatus" style="background-color: ${plugin.enabled ? 'green' : 'red'}"></div><div style="width: calc( 100% - 50px )"><h3>${plugin.name}</h3><p>${plugin.description}</p></div></li>`
|
||||
});
|
||||
html += `</ul></div><div id="config">`
|
||||
if(permissions && permissions.cluster.includes("readConfig")){
|
||||
if(server.config){
|
||||
html += `<ul>`;
|
||||
server.config.forEach(setting => {
|
||||
if(setting.entry[1] !== "_" && setting.entry !== "masterAuthSecret"){
|
||||
html += `<li class="settingInput">
|
||||
<p style="width: 50%; display:inline-block;">${setting.entry}</p>
|
||||
<input style="color:black; width: calc(50% - 4px); box-sizing: border-box;" value='${setting.value}'>
|
||||
</li>`
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
html += `</ul>
|
||||
<input id="submitSettings" type="button" value="Save changes" style="color:black"/>
|
||||
</div>`
|
||||
}
|
||||
%>
|
||||
<%- html %>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
<%- include("./index.js") %>
|
||||
</script>
|
||||
</body>
|
||||
@@ -0,0 +1,60 @@
|
||||
// Handle config editing
|
||||
originalSettings = {};
|
||||
let settings = document.querySelectorAll(".settingInput");
|
||||
settings.forEach(setting => {
|
||||
let entry = setting.children[0].innerText
|
||||
originalSettings[setting.children[0].innerText] = {
|
||||
entry: setting.children[0].innerText,
|
||||
value: setting.children[1].value,
|
||||
}
|
||||
setting.children[1].addEventListener("input", ()=>{
|
||||
let newValue = setting.children[1].value;
|
||||
if(newValue != originalSettings[entry].value){
|
||||
setting.children[1].style.backgroundColor = "lightgreen";
|
||||
} else {
|
||||
setting.children[1].style.backgroundColor = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
submitSettings.addEventListener("click", async ()=>{
|
||||
let settings = document.querySelectorAll(".settingInput");
|
||||
for(let i = 0; i < settings.length; i++){
|
||||
let setting = settings[i];
|
||||
let entry = setting.children[0].innerText
|
||||
if(setting.children[1].value != originalSettings[entry].value){
|
||||
// upload new changes, display results somehow
|
||||
let status = await postJSON("/api/serverManager/editConfig", {
|
||||
entry,
|
||||
value: setting.children[1].value,
|
||||
instanceID: getParameterByName("instanceID"),
|
||||
});
|
||||
if(status.ok){
|
||||
setting.children[1].style.backgroundColor = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Handle plugin management
|
||||
let pluginStatuses = document.querySelectorAll(".pluginStatus");
|
||||
pluginStatuses.forEach(pluginStatus => {
|
||||
pluginStatus.addEventListener("click", async ()=>{
|
||||
if(pluginStatus.style.backgroundColor == "green"){
|
||||
let status = await postJSON("/api/serverManager/disablePlugin", {
|
||||
instanceID: getParameterByName("instanceID"),
|
||||
pluginName: pluginStatus.parentElement.children[1].children[0].innerText,
|
||||
});
|
||||
if(status.ok){
|
||||
pluginStatus.style.backgroundColor = "red";
|
||||
}
|
||||
} else {
|
||||
let status = await postJSON("/api/serverManager/enablePlugin", {
|
||||
instanceID: getParameterByName("instanceID"),
|
||||
pluginName: pluginStatus.parentElement.children[1].children[0].innerText,
|
||||
});
|
||||
if(status.ok){
|
||||
pluginStatus.style.backgroundColor = "green";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
#mods {
|
||||
width: calc(( 100% ) / 4);
|
||||
float:left;
|
||||
}
|
||||
#plugins {
|
||||
width:calc((( 100% ) - (( 100% ) / 4))/2);
|
||||
float:left;
|
||||
}
|
||||
#config {
|
||||
width:calc((( 100% ) - (( 100% ) / 4))/2);
|
||||
float:left;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding:0px;
|
||||
}
|
||||
#plugins > ul > li > div {
|
||||
float:left;
|
||||
}
|
||||
#plugins .pluginStatus {
|
||||
width:40px;
|
||||
height: 40px;
|
||||
}
|
||||
Reference in new issue
Block a user