[REV] Switch to use IDS over server names
This commit is contained in:
parent
e94aca7c96
commit
91587f66b2
21 changed files with 196 additions and 221 deletions
|
@ -10,7 +10,7 @@ import { sendError } from "../util/ExpressClientError.js";
|
||||||
export async function listFiles(req, res) {
|
export async function listFiles(req, res) {
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
if (!serverSpec) return res.sendStatus(400);
|
if (!serverSpec) return res.sendStatus(400);
|
||||||
if (!serverSpec.host) return res.status(400).send("Server name required!");
|
if (!serverSpec.id) return res.status(400).send("Server id missing!");
|
||||||
listServerFiles(serverSpec)
|
listServerFiles(serverSpec)
|
||||||
.then((f) => {
|
.then((f) => {
|
||||||
const fileData = f.map((fi, i) => ({
|
const fileData = f.map((fi, i) => ({
|
||||||
|
@ -29,7 +29,7 @@ export async function listFiles(req, res) {
|
||||||
export async function createFolder(req, res) {
|
export async function createFolder(req, res) {
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
if (!serverSpec) return res.sendStatus(400);
|
if (!serverSpec) return res.sendStatus(400);
|
||||||
if (!serverSpec.host) return res.status(400).send("Server name required!");
|
if (!serverSpec.id) return res.status(400).send("Server id missing!");
|
||||||
if (!serverSpec.path) return res.status(400).send("Path required!");
|
if (!serverSpec.path) return res.status(400).send("Path required!");
|
||||||
createServerFolder(serverSpec)
|
createServerFolder(serverSpec)
|
||||||
.then(() => res.sendStatus(200))
|
.then(() => res.sendStatus(200))
|
||||||
|
@ -39,7 +39,7 @@ export async function createFolder(req, res) {
|
||||||
export async function deleteItem(req, res) {
|
export async function deleteItem(req, res) {
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
if (!serverSpec) return res.sendStatus(400);
|
if (!serverSpec) return res.sendStatus(400);
|
||||||
if (!serverSpec.host) return res.status(400).send("Server name required!");
|
if (!serverSpec.id) return res.status(400).send("Server id missing!");
|
||||||
if (!serverSpec.path) return res.status(400).send("Path required!");
|
if (!serverSpec.path) return res.status(400).send("Path required!");
|
||||||
if (serverSpec.isDir === undefined || serverSpec.isDir === null)
|
if (serverSpec.isDir === undefined || serverSpec.isDir === null)
|
||||||
return res.status(400).send("IsDIr required!");
|
return res.status(400).send("IsDIr required!");
|
||||||
|
@ -50,7 +50,7 @@ export async function deleteItem(req, res) {
|
||||||
|
|
||||||
export async function uploadItem(req, res) {
|
export async function uploadItem(req, res) {
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
if (!serverSpec.host) return res.status(400).send("Server name required!");
|
if (!serverSpec.id) return res.status(400).send("Server id missing!");
|
||||||
if (!serverSpec.path) return res.status(400).send("Path required!");
|
if (!serverSpec.path) return res.status(400).send("Path required!");
|
||||||
uploadServerItem(serverSpec, req.file)
|
uploadServerItem(serverSpec, req.file)
|
||||||
.then(() => res.sendStatus(200))
|
.then(() => res.sendStatus(200))
|
||||||
|
@ -59,7 +59,7 @@ export async function uploadItem(req, res) {
|
||||||
|
|
||||||
export async function getItem(req, res) {
|
export async function getItem(req, res) {
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
if (!serverSpec.host) return res.status(400).send("Server name required!");
|
if (!serverSpec.id) return res.status(400).send("Server id missing!");
|
||||||
if (!serverSpec.path) return res.status(400).send("Path required!");
|
if (!serverSpec.path) return res.status(400).send("Path required!");
|
||||||
getServerItem(serverSpec, res)
|
getServerItem(serverSpec, res)
|
||||||
.then(({ ds, ftpTransfer }) => {
|
.then(({ ds, ftpTransfer }) => {
|
||||||
|
|
|
@ -8,33 +8,35 @@ import {
|
||||||
import { sendError } from "../util/ExpressClientError.js";
|
import { sendError } from "../util/ExpressClientError.js";
|
||||||
import { toggleServer } from "../k8s/k8s-server-control.js";
|
import { toggleServer } from "../k8s/k8s-server-control.js";
|
||||||
|
|
||||||
|
const dnsRegex = new RegExp(
|
||||||
|
`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`,
|
||||||
|
);
|
||||||
|
|
||||||
function payloadFilter(req, res) {
|
function payloadFilter(req, res) {
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
if (!serverSpec) return res.sendStatus(400);
|
if (!serverSpec) return res.sendStatus(400);
|
||||||
const { name, host, version, serverType, memory } =
|
const { name, host, version, serverType, memory } = serverSpec;
|
||||||
serverSpec;
|
|
||||||
if (!name) return res.status(400).send("Server name is required!");
|
if (!name) return res.status(400).send("Server name is required!");
|
||||||
if (!host) return res.status(400).send("Server host is required!");
|
if (!host) return res.status(400).send("Server host is required!");
|
||||||
|
if (!dnsRegex.test(host)) return res.status(400).send("Hostname invalid!");
|
||||||
if (!version) return res.status(400).send("Server version is required!");
|
if (!version) return res.status(400).send("Server version is required!");
|
||||||
if (!serverType) return res.status(400).send("Server type is required!");
|
if (!serverType) return res.status(400).send("Server type is required!");
|
||||||
if (!memory) return res.status(400).send("Memory is required!");
|
if (!memory) return res.status(400).send("Memory is required!");
|
||||||
return "filtered";
|
return "filtered";
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkServerHost(serverSpec) {
|
function checkServerId(serverSpec) {
|
||||||
if (!serverSpec) throw new ExpressClientError({ c: 400 });
|
if (!serverSpec) throw new ExpressClientError({ c: 400 });
|
||||||
if (!serverSpec.host)
|
if (!serverSpec.id)
|
||||||
throw new ExpressClientError({ c: 400, m: "Server name required!" });
|
throw new ExpressClientError({ c: 400, m: "Server id missing!" });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createServer(req, res) {
|
export async function createServer(req, res) {
|
||||||
if (payloadFilter(req, res) !== "filtered") return;
|
if (payloadFilter(req, res) !== "filtered") return;
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
try {
|
try {
|
||||||
const serverSpecs = await getServerEntry(serverSpec.id);
|
const serverEntry = await createServerEntry(serverSpec);
|
||||||
if (serverSpecs.length !== 0) throw Error("Server already exists in DB!");
|
await createServerResources(serverEntry);
|
||||||
await createServerResources(serverSpec);
|
|
||||||
await createServerEntry(serverSpec);
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendError(res)(e);
|
sendError(res)(e);
|
||||||
|
@ -45,7 +47,7 @@ export async function deleteServer(req, res) {
|
||||||
// Ensure spec is safe
|
// Ensure spec is safe
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
try {
|
try {
|
||||||
checkServerHost(serverSpec);
|
checkServerId(serverSpec);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return sendError(res)(e);
|
return sendError(res)(e);
|
||||||
}
|
}
|
||||||
|
@ -60,12 +62,12 @@ export async function startServer(req, res) {
|
||||||
// Ensure spec is safe
|
// Ensure spec is safe
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
try {
|
try {
|
||||||
checkServerHost(serverSpec);
|
checkServerId(serverSpec);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return sendError(res)(e);
|
return sendError(res)(e);
|
||||||
}
|
}
|
||||||
const { name } = serverSpec;
|
const { id } = serverSpec;
|
||||||
toggleServer(name, true)
|
toggleServer(id, true)
|
||||||
.then(() => res.sendStatus(200))
|
.then(() => res.sendStatus(200))
|
||||||
.catch(sendError(res));
|
.catch(sendError(res));
|
||||||
}
|
}
|
||||||
|
@ -74,12 +76,12 @@ export async function stopServer(req, res) {
|
||||||
// Ensure spec is safe
|
// Ensure spec is safe
|
||||||
const serverSpec = req.body;
|
const serverSpec = req.body;
|
||||||
try {
|
try {
|
||||||
checkServerHost(serverSpec);
|
checkServerId(serverSpec);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return sendError(res)(e);
|
return sendError(res)(e);
|
||||||
}
|
}
|
||||||
const { name } = serverSpec;
|
const { id } = serverSpec;
|
||||||
toggleServer(name, false)
|
toggleServer(id, false)
|
||||||
.then(() => res.sendStatus(200))
|
.then(() => res.sendStatus(200))
|
||||||
.catch(sendError(res));
|
.catch(sendError(res));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,32 +9,47 @@ const asExpressClientError = (e) => {
|
||||||
|
|
||||||
export async function createServerEntry(serverSpec) {
|
export async function createServerEntry(serverSpec) {
|
||||||
const { name, host, version, serverType: server_type, memory } = serverSpec;
|
const { name, host, version, serverType: server_type, memory } = serverSpec;
|
||||||
const q = insertQuery(table, { name, host, version, server_type, memory });
|
var q = insertQuery(table, { name, host, version, server_type, memory });
|
||||||
|
q += "\n RETURNING *";
|
||||||
|
try {
|
||||||
|
const entries = await pg.query(q);
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
host,
|
||||||
|
version,
|
||||||
|
server_type: serverType,
|
||||||
|
memory,
|
||||||
|
} = entries[0];
|
||||||
|
return { name, id, host, version, serverType, memory };
|
||||||
|
} catch (e) {
|
||||||
|
asExpressClientError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteServerEntry(serverId) {
|
||||||
|
if (!serverId) asExpressClientError({ message: "Server ID Required!" });
|
||||||
|
const q = deleteQuery(table, { id: serverId });
|
||||||
return pg.query(q).catch(asExpressClientError);
|
return pg.query(q).catch(asExpressClientError);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteServerEntry(serverName) {
|
export async function getServerEntry(serverId) {
|
||||||
if (!serverName) asExpressClientError({ message: "Server Name Required!" });
|
if (!serverId) asExpressClientError({ message: "Server ID Required!" });
|
||||||
const q = deleteQuery(table, { name: serverName });
|
const q = selectWhereQuery(table, { id: serverId });
|
||||||
return pg.query(q).catch(asExpressClientError);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getServerEntry(serverName) {
|
|
||||||
if (!serverName) asExpressClientError({ message: "Server Name Required!" });
|
|
||||||
const q = selectWhereQuery(table, { name: serverName });
|
|
||||||
try {
|
try {
|
||||||
const serverSpecs = await pg.query(q);
|
const serverSpecs = await pg.query(q);
|
||||||
if (serverSpecs.length === 0) return [];
|
if (serverSpecs.length === 0) return [];
|
||||||
if (!serverSpecs.length === 1)
|
if (!serverSpecs.length === 1)
|
||||||
throw Error("Multiple servers found with the same name!");
|
throw Error("Multiple servers found with the same name!");
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
host,
|
host,
|
||||||
version,
|
version,
|
||||||
server_type: serverType,
|
server_type: serverType,
|
||||||
memory,
|
memory,
|
||||||
} = serverSpecs[0];
|
} = serverSpecs[0];
|
||||||
return { name, host, version, serverType, memory };
|
return { name, id, host, version, serverType, memory };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
asExpressClientError(e);
|
asExpressClientError(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ data:
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
minecluster.dunemask.net/server-name: changeme-server-name
|
minecluster.dunemask.net/id: changeme-server-id
|
||||||
labels:
|
labels:
|
||||||
app: changeme-app-label
|
app: changeme-app-label
|
||||||
name: changeme-rcon-secret
|
name: changeme-rcon-secret
|
||||||
|
|
|
@ -2,7 +2,7 @@ apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
minecluster.dunemask.net/server-name: changeme-server-name
|
minecluster.dunemask.net/id: changeme-server-id
|
||||||
labels:
|
labels:
|
||||||
app: changeme-app
|
app: changeme-app
|
||||||
name: changeme-rcon
|
name: changeme-rcon
|
||||||
|
|
|
@ -2,7 +2,7 @@ apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
minecluster.dunemask.net/server-name: changeme-server-name
|
minecluster.dunemask.net/id: changeme-server-id
|
||||||
name: changeme-name
|
name: changeme-name
|
||||||
namespace: changeme-namespace
|
namespace: changeme-namespace
|
||||||
spec:
|
spec:
|
||||||
|
@ -17,7 +17,7 @@ spec:
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
minecluster.dunemask.net/server-name: changeme-server-name
|
minecluster.dunemask.net/id: changeme-server-id
|
||||||
labels:
|
labels:
|
||||||
app: changeme-app
|
app: changeme-app
|
||||||
spec:
|
spec:
|
||||||
|
|
|
@ -2,7 +2,7 @@ apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
minecluster.dunemask.net/server-name: changeme-server-name
|
minecluster.dunemask.net/id: changeme-server-id
|
||||||
labels:
|
labels:
|
||||||
service: changeme-service-name
|
service: changeme-service-name
|
||||||
name: changeme-pvc-name
|
name: changeme-pvc-name
|
||||||
|
|
|
@ -4,7 +4,7 @@ metadata:
|
||||||
annotations:
|
annotations:
|
||||||
ingress.qumine.io/hostname: changeme-url
|
ingress.qumine.io/hostname: changeme-url
|
||||||
ingress.qumine.io/portname: minecraft
|
ingress.qumine.io/portname: minecraft
|
||||||
minecluster.dunemask.net/server-name: changeme-server-name
|
minecluster.dunemask.net/id: changeme-server-id
|
||||||
labels:
|
labels:
|
||||||
app: changeme-app
|
app: changeme-app
|
||||||
name: changeme-name
|
name: changeme-name
|
||||||
|
|
|
@ -20,10 +20,10 @@ const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
||||||
const mineclusterManaged = (o) =>
|
const mineclusterManaged = (o) =>
|
||||||
o.metadata &&
|
o.metadata &&
|
||||||
o.metadata.annotations &&
|
o.metadata.annotations &&
|
||||||
o.metadata.annotations["minecluster.dunemask.net/server-name"] !== undefined;
|
o.metadata.annotations["minecluster.dunemask.net/id"] !== undefined;
|
||||||
|
|
||||||
export const serverMatch = (serverName) => (o) =>
|
export const serverMatch = (serverId) => (o) =>
|
||||||
o.metadata.annotations["minecluster.dunemask.net/server-name"] === serverName;
|
o.metadata.annotations["minecluster.dunemask.net/id"] === serverId;
|
||||||
|
|
||||||
export async function getDeployments() {
|
export async function getDeployments() {
|
||||||
const deploymentRes = await k8sDeps.listNamespacedDeployment(namespace);
|
const deploymentRes = await k8sDeps.listNamespacedDeployment(namespace);
|
||||||
|
@ -50,8 +50,9 @@ export async function getVolumes() {
|
||||||
return serverVolumes;
|
return serverVolumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getServerAssets(serverName) {
|
export function getServerAssets(serverId) {
|
||||||
const serverFilter = serverMatch(serverName);
|
const serverFilter = serverMatch(serverId);
|
||||||
|
console.log(serverId);
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
getDeployments(),
|
getDeployments(),
|
||||||
getServices(),
|
getServices(),
|
||||||
|
@ -69,13 +70,9 @@ export function getServerAssets(serverName) {
|
||||||
if (secrets.length > 1) throw Error("Secrets broken!");
|
if (secrets.length > 1) throw Error("Secrets broken!");
|
||||||
const serverAssets = {
|
const serverAssets = {
|
||||||
deployment: deployments[0],
|
deployment: deployments[0],
|
||||||
service: services.find(
|
service: services.find((s) => s.metadata.name.endsWith("-server")),
|
||||||
(s) => s.metadata.name === `mcl-${serverName}-server`,
|
|
||||||
),
|
|
||||||
volume: volumes[0],
|
volume: volumes[0],
|
||||||
rconService: services.find(
|
rconService: services.find((s) => s.metadata.name.endsWith("-rcon")),
|
||||||
(s) => s.metadata.name === `mcl-${serverName}-rcon`,
|
|
||||||
),
|
|
||||||
rconSecret: secrets[0],
|
rconSecret: secrets[0],
|
||||||
};
|
};
|
||||||
for (var k in serverAssets) if (serverAssets[k]) return serverAssets;
|
for (var k in serverAssets) if (serverAssets[k]) return serverAssets;
|
||||||
|
@ -84,30 +81,29 @@ export function getServerAssets(serverName) {
|
||||||
.catch((e) => ERR("SERVER ASSETS", e));
|
.catch((e) => ERR("SERVER ASSETS", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDeployment(serverName) {
|
export async function getDeployment(serverId) {
|
||||||
const servers = await getDeployments();
|
const servers = await getDeployments();
|
||||||
|
console.log(servers.map(({ metadata }) => metadata.annotations));
|
||||||
const serverDeployment = servers.find(
|
const serverDeployment = servers.find(
|
||||||
(s) =>
|
(s) => s.metadata.annotations["minecluster.dunemask.net/id"] === serverId,
|
||||||
s.metadata.annotations["minecluster.dunemask.net/server-name"] ===
|
|
||||||
serverName,
|
|
||||||
);
|
);
|
||||||
if (!serverDeployment)
|
if (!serverDeployment)
|
||||||
throw Error(`MCL Deployment '${serverName}' could not be found!`);
|
throw Error(`MCL Deployment with ID '${serverId}' could not be found!`);
|
||||||
|
|
||||||
return serverDeployment;
|
return serverDeployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getContainers(serverName) {
|
export async function getContainers(serverId) {
|
||||||
const deployment = await getDeployment(serverName);
|
const deployment = await getDeployment(serverId);
|
||||||
return deployment.spec.template.spec.containers;
|
return deployment.spec.template.spec.containers;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function containerControl(serverName, deployment, scaleUp) {
|
async function containerControl(serverId, deployment, scaleUp) {
|
||||||
const { containers } = deployment.spec.template.spec;
|
const { containers } = deployment.spec.template.spec;
|
||||||
const depFtp = containers.find((c) => c.name.endsWith("-ftp"));
|
const depFtp = containers.find((c) => c.name.endsWith("-ftp"));
|
||||||
const depServer = containers.find((c) => c.name.endsWith("-server"));
|
const depServer = containers.find((c) => c.name.endsWith("-server"));
|
||||||
const depBackup = containers.find((c) => c.name.endsWith("-backup"));
|
const depBackup = containers.find((c) => c.name.endsWith("-backup"));
|
||||||
const serverSpec = await getServerEntry(serverName);
|
const serverSpec = await getServerEntry(serverId);
|
||||||
const ftpContainer = depFtp ?? getFtpContainer(serverSpec);
|
const ftpContainer = depFtp ?? getFtpContainer(serverSpec);
|
||||||
const serverContainer = depServer ?? getCoreServerContainer(serverSpec);
|
const serverContainer = depServer ?? getCoreServerContainer(serverSpec);
|
||||||
const backupContainer = depBackup ?? getBackupContainer(serverSpec);
|
const backupContainer = depBackup ?? getBackupContainer(serverSpec);
|
||||||
|
@ -115,10 +111,10 @@ async function containerControl(serverName, deployment, scaleUp) {
|
||||||
return [ftpContainer];
|
return [ftpContainer];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toggleServer(serverName, scaleUp = false) {
|
export async function toggleServer(serverId, scaleUp = false) {
|
||||||
const deployment = await getDeployment(serverName);
|
const deployment = await getDeployment(serverId);
|
||||||
deployment.spec.template.spec.containers = await containerControl(
|
deployment.spec.template.spec.containers = await containerControl(
|
||||||
serverName,
|
serverId,
|
||||||
deployment,
|
deployment,
|
||||||
scaleUp,
|
scaleUp,
|
||||||
);
|
);
|
||||||
|
@ -128,19 +124,3 @@ export async function toggleServer(serverName, scaleUp = false) {
|
||||||
deployment,
|
deployment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scaleDeployment(serverName, scaleUp = false) {
|
|
||||||
const deployment = await getDeployment(serverName);
|
|
||||||
if (deployment.spec.replicas === 1 && scaleUp)
|
|
||||||
return VERB(
|
|
||||||
"KSC",
|
|
||||||
`MCL Deployment '${serverName}' is already scaled! Ignoring scale adjustment.`,
|
|
||||||
);
|
|
||||||
deployment.spec.replicas = scaleUp ? 1 : 0;
|
|
||||||
|
|
||||||
return k8sDeps.replaceNamespacedDeployment(
|
|
||||||
deployment.metadata.name,
|
|
||||||
namespace,
|
|
||||||
deployment,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import yaml from "js-yaml";
|
||||||
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
||||||
|
|
||||||
export function getFtpContainer(serverSpec) {
|
export function getFtpContainer(serverSpec) {
|
||||||
const { name } = serverSpec;
|
const { mclName } = serverSpec;
|
||||||
const ftpContainer = loadYaml("lib/k8s/configs/containers/ftp-server.yml");
|
const ftpContainer = loadYaml("lib/k8s/configs/containers/ftp-server.yml");
|
||||||
ftpContainer.name = `mcl-${name}-ftp`;
|
ftpContainer.name = `mcl-${mclName}-ftp`;
|
||||||
const ftpPortList = [
|
const ftpPortList = [
|
||||||
{ p: 20, n: "ftp-data" },
|
{ p: 20, n: "ftp-data" },
|
||||||
{ p: 21, n: "ftp-commands" },
|
{ p: 21, n: "ftp-commands" },
|
||||||
|
@ -22,10 +22,10 @@ export function getFtpContainer(serverSpec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCoreServerContainer(serverSpec) {
|
export function getCoreServerContainer(serverSpec) {
|
||||||
const { name, version, serverType, memory } = serverSpec;
|
const { mclName, version, serverType, memory } = serverSpec;
|
||||||
const container = loadYaml("lib/k8s/configs/containers/minecraft-server.yml");
|
const container = loadYaml("lib/k8s/configs/containers/minecraft-server.yml");
|
||||||
// Container Updates
|
// Container Updates
|
||||||
container.name = `mcl-${name}-server`;
|
container.name = `mcl-${mclName}-server`;
|
||||||
container.resources.requests.memory = `${memory}Mi`;
|
container.resources.requests.memory = `${memory}Mi`;
|
||||||
|
|
||||||
const findEnv = (k) => container.env.find(({ name: n }) => n === k);
|
const findEnv = (k) => container.env.find(({ name: n }) => n === k);
|
||||||
|
@ -36,7 +36,7 @@ export function getCoreServerContainer(serverSpec) {
|
||||||
updateEnv("VERSION", version);
|
updateEnv("VERSION", version);
|
||||||
updateEnv("MEMORY", `${memory}M`);
|
updateEnv("MEMORY", `${memory}M`);
|
||||||
// RCON
|
// RCON
|
||||||
const rs = `mcl-${name}-rcon-secret`;
|
const rs = `mcl-${mclName}-rcon-secret`;
|
||||||
findEnv("RCON_PASSWORD").valueFrom.secretKeyRef.name = rs;
|
findEnv("RCON_PASSWORD").valueFrom.secretKeyRef.name = rs;
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
@ -50,13 +50,13 @@ export function getServerContainer(serverSpec) {
|
||||||
const updateEnv = (k, v) => (findEnv(k).value = v);
|
const updateEnv = (k, v) => (findEnv(k).value = v);
|
||||||
|
|
||||||
// Enviornment variables
|
// Enviornment variables
|
||||||
updateEnv("DIFFICULTY", difficulty);
|
/*updateEnv("DIFFICULTY", difficulty);
|
||||||
updateEnv("MODE", gamemode);
|
updateEnv("MODE", gamemode);
|
||||||
updateEnv("MOTD", motd);
|
updateEnv("MOTD", motd);
|
||||||
updateEnv("MAX_PLAYERS", maxPlayers);
|
updateEnv("MAX_PLAYERS", maxPlayers);
|
||||||
updateEnv("SEED", seed);
|
updateEnv("SEED", seed);
|
||||||
updateEnv("OPS", ops);
|
updateEnv("OPS", ops);
|
||||||
updateEnv("WHITELIST", whitelist);
|
updateEnv("WHITELIST", whitelist); */
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,19 @@
|
||||||
import k8s from "@kubernetes/client-node";
|
import k8s from "@kubernetes/client-node";
|
||||||
import {
|
import { getDeployments } from "./k8s-server-control.js";
|
||||||
getDeployment,
|
|
||||||
getDeployments,
|
|
||||||
getServerAssets,
|
|
||||||
scaleDeployment,
|
|
||||||
} from "./k8s-server-control.js";
|
|
||||||
import { ERR } from "../util/logging.js";
|
|
||||||
import ExpressClientError from "../util/ExpressClientError.js";
|
|
||||||
const kc = new k8s.KubeConfig();
|
const kc = new k8s.KubeConfig();
|
||||||
kc.loadFromDefault();
|
kc.loadFromDefault();
|
||||||
|
|
||||||
const k8sMetrics = new k8s.Metrics(kc);
|
const k8sMetrics = new k8s.Metrics(kc);
|
||||||
const namespace = process.env.MCL_SERVER_NAMESPACE;
|
const namespace = process.env.MCL_SERVER_NAMESPACE;
|
||||||
|
|
||||||
export async function startServerContainer(serverSpec) {
|
function getServerMetrics(podMetricsRes, serverId, serverAvailable) {
|
||||||
const { name } = serverSpec;
|
const pod = podMetricsRes.items.find(({ metadata: md }) => {
|
||||||
try {
|
return (
|
||||||
await scaleDeployment(name, true);
|
md.annotations &&
|
||||||
} catch (e) {
|
md.annotations["minecluster.dunemask.net/id"] === serverId
|
||||||
ERR("SERVER CONTROL", e);
|
|
||||||
throw new ExpressClientError({
|
|
||||||
c: 500,
|
|
||||||
m: `Error updating server '${name}'!\n`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function stopServerContainer(serverSpec) {
|
|
||||||
const { name } = serverSpec;
|
|
||||||
try {
|
|
||||||
await scaleDeployment(name, false);
|
|
||||||
} catch (e) {
|
|
||||||
ERR("SERVER CONTROL", e);
|
|
||||||
throw new ExpressClientError({
|
|
||||||
c: 500,
|
|
||||||
m: `Error updating server '${name}'!`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getInstances() {
|
|
||||||
const serverDeployments = await getDeployments();
|
|
||||||
const podMetricsResponse = await k8sMetrics.getPodMetrics(namespace);
|
|
||||||
var name, metrics, services, serverAvailable, ftpAvailable;
|
|
||||||
const serverInstances = serverDeployments.map((s) => {
|
|
||||||
name = s.metadata.annotations["minecluster.dunemask.net/server-name"];
|
|
||||||
metrics = null;
|
|
||||||
const { containers } = s.spec.template.spec;
|
|
||||||
services = containers.map(({ name }) => name.split("-").pop());
|
|
||||||
const serverStatusList = s.status.conditions.map(
|
|
||||||
({ type: statusType, status: sts }) => ({ statusType, sts }),
|
|
||||||
);
|
);
|
||||||
const deploymentAvailable =
|
|
||||||
serverStatusList.find(
|
|
||||||
(ss) => ss.statusType === "Available" && ss.sts === "True",
|
|
||||||
) !== undefined;
|
|
||||||
serverAvailable = services.includes(`server`) && deploymentAvailable;
|
|
||||||
ftpAvailable = services.includes("ftp") && deploymentAvailable;
|
|
||||||
|
|
||||||
const pod = podMetricsResponse.items.find(({ metadata: md }) => {
|
|
||||||
return md.labels && md.labels.app && md.labels.app === `mcl-${name}-app`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (serverAvailable && pod) {
|
if (serverAvailable && pod) {
|
||||||
const podCpus = pod.containers.map(
|
const podCpus = pod.containers.map(
|
||||||
({ usage }) => parseInt(usage.cpu) / 1_000_000,
|
({ usage }) => parseInt(usage.cpu) / 1_000_000,
|
||||||
|
@ -73,7 +26,37 @@ export async function getInstances() {
|
||||||
memory: Math.ceil(podMems.reduce((a, b) => a + b)),
|
memory: Math.ceil(podMems.reduce((a, b) => a + b)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { name, metrics, services, serverAvailable, ftpAvailable };
|
}
|
||||||
|
|
||||||
|
export async function getInstances() {
|
||||||
|
const serverDeployments = await getDeployments();
|
||||||
|
const podMetricsRes = await k8sMetrics.getPodMetrics(namespace);
|
||||||
|
var name, serverId, metrics, services, serverAvailable, ftpAvailable;
|
||||||
|
const serverInstances = serverDeployments.map((s) => {
|
||||||
|
serverId = s.metadata.annotations["minecluster.dunemask.net/id"];
|
||||||
|
name = s.metadata.name;
|
||||||
|
metrics = null;
|
||||||
|
|
||||||
|
const { containers } = s.spec.template.spec;
|
||||||
|
services = containers.map(({ name }) => name.split("-").pop());
|
||||||
|
const serverStatusList = s.status.conditions.map(
|
||||||
|
({ type: statusType, status: sts }) => ({ statusType, sts }),
|
||||||
|
);
|
||||||
|
const deploymentAvailable =
|
||||||
|
serverStatusList.find(
|
||||||
|
(ss) => ss.statusType === "Available" && ss.sts === "True",
|
||||||
|
) !== undefined;
|
||||||
|
serverAvailable = services.includes(`server`) && deploymentAvailable;
|
||||||
|
ftpAvailable = services.includes("ftp") && deploymentAvailable;
|
||||||
|
metrics = getServerMetrics(podMetricsRes, serverId, serverAvailable);
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
id: serverId,
|
||||||
|
metrics,
|
||||||
|
services,
|
||||||
|
serverAvailable,
|
||||||
|
ftpAvailable,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
return serverInstances;
|
return serverInstances;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import k8s from "@kubernetes/client-node";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import ExpressClientError from "../util/ExpressClientError.js";
|
|
||||||
import {
|
import {
|
||||||
getFtpContainer,
|
getFtpContainer,
|
||||||
getServerContainer,
|
getServerContainer,
|
||||||
|
@ -20,32 +20,31 @@ const namespace = process.env.MCL_SERVER_NAMESPACE;
|
||||||
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
||||||
|
|
||||||
function createRconSecret(serverSpec) {
|
function createRconSecret(serverSpec) {
|
||||||
const { name } = serverSpec;
|
const { mclName, id } = serverSpec;
|
||||||
const rconYaml = loadYaml("lib/k8s/configs/rcon-secret.yml");
|
const rconYaml = loadYaml("lib/k8s/configs/rcon-secret.yml");
|
||||||
// TODO: Dyamic rconPassword
|
// TODO: Dyamic rconPassword
|
||||||
const rconPassword = bcrypt.hashSync(uuidv4(), 10);
|
const rconPassword = bcrypt.hashSync(uuidv4(), 10);
|
||||||
rconYaml.data["rcon-password"] = Buffer.from(rconPassword).toString("base64");
|
rconYaml.data["rcon-password"] = Buffer.from(rconPassword).toString("base64");
|
||||||
rconYaml.metadata.labels.app = `mcl-${name}-app`;
|
rconYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
||||||
rconYaml.metadata.name = `mcl-${name}-rcon-secret`;
|
rconYaml.metadata.name = `mcl-${mclName}-rcon-secret`;
|
||||||
rconYaml.metadata.namespace = namespace;
|
rconYaml.metadata.namespace = namespace;
|
||||||
rconYaml.metadata.annotations["minecluster.dunemask.net/server-name"] = name;
|
rconYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
||||||
return rconYaml;
|
return rconYaml;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createServerVolume(serverSpec) {
|
function createServerVolume(serverSpec) {
|
||||||
const { name } = serverSpec;
|
const { mclName, id } = serverSpec;
|
||||||
const volumeYaml = loadYaml("lib/k8s/configs/server-pvc.yml");
|
const volumeYaml = loadYaml("lib/k8s/configs/server-pvc.yml");
|
||||||
volumeYaml.metadata.labels.service = `mcl-${name}-server`;
|
volumeYaml.metadata.labels.service = `mcl-${mclName}-server`;
|
||||||
volumeYaml.metadata.name = `mcl-${name}-volume`;
|
volumeYaml.metadata.name = `mcl-${mclName}-volume`;
|
||||||
volumeYaml.metadata.namespace = namespace;
|
volumeYaml.metadata.namespace = namespace;
|
||||||
volumeYaml.metadata.annotations["minecluster.dunemask.net/server-name"] =
|
volumeYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
||||||
name;
|
|
||||||
volumeYaml.spec.resources.requests.storage = "1Gi"; // TODO: Changeme
|
volumeYaml.spec.resources.requests.storage = "1Gi"; // TODO: Changeme
|
||||||
return volumeYaml;
|
return volumeYaml;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createServerDeploy(serverSpec) {
|
function createServerDeploy(serverSpec) {
|
||||||
const { name, host } = serverSpec;
|
const { mclName, id } = serverSpec;
|
||||||
const deployYaml = loadYaml("lib/k8s/configs/server-deployment.yml");
|
const deployYaml = loadYaml("lib/k8s/configs/server-deployment.yml");
|
||||||
const { metadata } = deployYaml;
|
const { metadata } = deployYaml;
|
||||||
const serverContainer = getServerContainer(serverSpec);
|
const serverContainer = getServerContainer(serverSpec);
|
||||||
|
@ -53,19 +52,21 @@ function createServerDeploy(serverSpec) {
|
||||||
const ftpContainer = getFtpContainer(serverSpec);
|
const ftpContainer = getFtpContainer(serverSpec);
|
||||||
|
|
||||||
// Configure Metadata;
|
// Configure Metadata;
|
||||||
metadata.name = `mcl-${name}`;
|
metadata.name = `mcl-${mclName}`;
|
||||||
metadata.namespace = namespace;
|
metadata.namespace = namespace;
|
||||||
metadata.annotations["minecluster.dunemask.net/server-name"] = name;
|
metadata.annotations["minecluster.dunemask.net/id"] = id;
|
||||||
deployYaml.metadata = metadata;
|
deployYaml.metadata = metadata;
|
||||||
|
|
||||||
// Configure Lables & Selectors
|
// Configure Lables & Selectors
|
||||||
deployYaml.spec.selector.matchLabels.app = `mcl-${name}-app`;
|
deployYaml.spec.selector.matchLabels.app = `mcl-${mclName}-app`;
|
||||||
deployYaml.spec.template.metadata.labels.app = `mcl-${name}-app`;
|
deployYaml.spec.template.metadata.labels.app = `mcl-${mclName}-app`;
|
||||||
|
deployYaml.spec.template.metadata.annotations["minecluster.dunemask.net/id"] =
|
||||||
|
id;
|
||||||
|
|
||||||
// Volumes
|
// Volumes
|
||||||
deployYaml.spec.template.spec.volumes.find(
|
deployYaml.spec.template.spec.volumes.find(
|
||||||
({ name }) => name === "datadir",
|
({ name }) => name === "datadir",
|
||||||
).persistentVolumeClaim.claimName = `mcl-${name}-volume`;
|
).persistentVolumeClaim.claimName = `mcl-${mclName}-volume`;
|
||||||
|
|
||||||
// Apply Containers TODO: User control for autostart
|
// Apply Containers TODO: User control for autostart
|
||||||
deployYaml.spec.template.spec.containers.push(serverContainer);
|
deployYaml.spec.template.spec.containers.push(serverContainer);
|
||||||
|
@ -75,17 +76,16 @@ function createServerDeploy(serverSpec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createServerService(serverSpec) {
|
function createServerService(serverSpec) {
|
||||||
const { name, host } = serverSpec;
|
const { mclName, host, id } = serverSpec;
|
||||||
const serviceYaml = loadYaml("lib/k8s/configs/server-svc.yml");
|
const serviceYaml = loadYaml("lib/k8s/configs/server-svc.yml");
|
||||||
serviceYaml.metadata.annotations["ingress.qumine.io/hostname"] = host;
|
serviceYaml.metadata.annotations["ingress.qumine.io/hostname"] = host;
|
||||||
serviceYaml.metadata.annotations["mc-router.itzg.me/externalServerName"] =
|
serviceYaml.metadata.annotations["mc-router.itzg.me/externalServerName"] =
|
||||||
host;
|
host;
|
||||||
serviceYaml.metadata.labels.app = `mcl-${name}-app`;
|
serviceYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
||||||
serviceYaml.metadata.name = `mcl-${name}-server`;
|
serviceYaml.metadata.name = `mcl-${mclName}-server`;
|
||||||
serviceYaml.metadata.namespace = namespace;
|
serviceYaml.metadata.namespace = namespace;
|
||||||
serviceYaml.metadata.annotations["minecluster.dunemask.net/server-name"] =
|
serviceYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
||||||
name;
|
serviceYaml.spec.selector.app = `mcl-${mclName}-app`;
|
||||||
serviceYaml.spec.selector.app = `mcl-${name}-app`;
|
|
||||||
// Port List:
|
// Port List:
|
||||||
const serverPortList = [{ p: 25565, n: "minecraft" }];
|
const serverPortList = [{ p: 25565, n: "minecraft" }];
|
||||||
|
|
||||||
|
@ -107,32 +107,26 @@ function createServerService(serverSpec) {
|
||||||
return serviceYaml;
|
return serviceYaml;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createRconService(serverSpec) {
|
function createRconService(createSpec) {
|
||||||
const { name } = serverSpec;
|
const { id, mclName } = createSpec;
|
||||||
const rconSvcYaml = loadYaml("lib/k8s/configs/rcon-svc.yml");
|
const rconSvcYaml = loadYaml("lib/k8s/configs/rcon-svc.yml");
|
||||||
rconSvcYaml.metadata.labels.app = `mcl-${name}-app`;
|
rconSvcYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
||||||
rconSvcYaml.metadata.name = `mcl-${name}-rcon`;
|
rconSvcYaml.metadata.name = `mcl-${mclName}-rcon`;
|
||||||
rconSvcYaml.metadata.namespace = namespace;
|
rconSvcYaml.metadata.namespace = namespace;
|
||||||
rconSvcYaml.metadata.annotations["minecluster.dunemask.net/server-name"] =
|
rconSvcYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
||||||
name;
|
rconSvcYaml.spec.selector.app = `mcl-${mclName}-app`;
|
||||||
rconSvcYaml.spec.selector.app = `mcl-${name}-app`;
|
|
||||||
return rconSvcYaml;
|
return rconSvcYaml;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function createServerResources(serverSpec) {
|
export default async function createServerResources(createSpec) {
|
||||||
const deploymentRes = await k8sDeps.listNamespacedDeployment(namespace);
|
createSpec.mclName = `${createSpec.host.replaceAll(".", "-")}-${
|
||||||
const deployments = deploymentRes.body.items.map((i) => i.metadata.name);
|
createSpec.id
|
||||||
if (deployments.includes(`mcl-${serverSpec.name}`))
|
}`;
|
||||||
throw new ExpressClientError({ m: "Server already exists!", c: 409 });
|
const rconSecret = createRconSecret(createSpec);
|
||||||
const pvcRes = await k8sCore.listNamespacedPersistentVolumeClaim(namespace);
|
const serverVolume = createServerVolume(createSpec);
|
||||||
const pvcs = pvcRes.body.items.map((i) => i.metadata.name);
|
const serverDeploy = createServerDeploy(createSpec);
|
||||||
if (pvcs.includes(`mcl-${serverSpec.name}-volume`))
|
const serverService = createServerService(createSpec);
|
||||||
throw new ExpressClientError({ m: "Server PVC already exists!", c: 409 });
|
const rconService = createRconService(createSpec);
|
||||||
const rconSecret = createRconSecret(serverSpec);
|
|
||||||
const serverVolume = createServerVolume(serverSpec);
|
|
||||||
const serverDeploy = createServerDeploy(serverSpec);
|
|
||||||
const serverService = createServerService(serverSpec);
|
|
||||||
const rconService = createRconService(serverSpec);
|
|
||||||
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume);
|
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume);
|
||||||
k8sCore.createNamespacedSecret(namespace, rconSecret);
|
k8sCore.createNamespacedSecret(namespace, rconSecret);
|
||||||
k8sCore.createNamespacedService(namespace, serverService);
|
k8sCore.createNamespacedService(namespace, serverService);
|
||||||
|
|
|
@ -22,9 +22,9 @@ function deleteOnExist(o, fn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function deleteServerResources(serverSpec) {
|
export default async function deleteServerResources(serverSpec) {
|
||||||
const { name } = serverSpec;
|
const { id } = serverSpec;
|
||||||
// Ensure deployment exists
|
// Ensure deployment exists
|
||||||
const server = await getServerAssets(name);
|
const server = await getServerAssets(id);
|
||||||
if (!server)
|
if (!server)
|
||||||
throw new ExpressClientError({
|
throw new ExpressClientError({
|
||||||
c: 404,
|
c: 404,
|
||||||
|
|
|
@ -34,8 +34,8 @@ export async function getFtpClient(serverService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function useServerFtp(serverSpec, fn) {
|
export async function useServerFtp(serverSpec, fn) {
|
||||||
const { name } = serverSpec;
|
const { id } = serverSpec;
|
||||||
const server = await getServerAssets(name);
|
const server = await getServerAssets(id);
|
||||||
if (!server)
|
if (!server)
|
||||||
throw new ExpressClientError({
|
throw new ExpressClientError({
|
||||||
c: 404,
|
c: 404,
|
||||||
|
|
|
@ -33,14 +33,14 @@ export default function MineclusterFiles(props) {
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const { server: serverName } = props;
|
const { server: serverId } = props;
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const [dirStack, setDirStack] = useState(["."]);
|
const [dirStack, setDirStack] = useState(["."]);
|
||||||
const [files, setFiles] = useState([]);
|
const [files, setFiles] = useState([]);
|
||||||
|
|
||||||
const updateFiles = () => {
|
const updateFiles = () => {
|
||||||
const dir = dirStack.join("/");
|
const dir = dirStack.join("/");
|
||||||
getServerFiles(serverName, dir).then((f) => {
|
getServerFiles(serverId, dir).then((f) => {
|
||||||
const files = f.map((fi) => ({ ...fi, id: `${dir}/${fi.name}` }));
|
const files = f.map((fi) => ({ ...fi, id: `${dir}/${fi.name}` }));
|
||||||
setFiles(files ?? []);
|
setFiles(files ?? []);
|
||||||
});
|
});
|
||||||
|
@ -70,13 +70,13 @@ export default function MineclusterFiles(props) {
|
||||||
function createFolder() {
|
function createFolder() {
|
||||||
const name = prompt("What is the name of the new folder?");
|
const name = prompt("What is the name of the new folder?");
|
||||||
const path = [...dirStack, name].join("/");
|
const path = [...dirStack, name].join("/");
|
||||||
createServerFolder(serverName, path).then(updateFiles);
|
createServerFolder(serverId, path).then(updateFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteItems(files) {
|
function deleteItems(files) {
|
||||||
Promise.all(
|
Promise.all(
|
||||||
files.map((f) =>
|
files.map((f) =>
|
||||||
deleteServerItem(serverName, [...dirStack, f.name].join("/"), f.isDir),
|
deleteServerItem(serverId, [...dirStack, f.name].join("/"), f.isDir),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.catch((e) => console.error("Error deleting some files!", e))
|
.catch((e) => console.error("Error deleting some files!", e))
|
||||||
|
@ -94,7 +94,7 @@ export default function MineclusterFiles(props) {
|
||||||
async function uploadFile(file) {
|
async function uploadFile(file) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("name", serverName);
|
formData.append("id", serverId);
|
||||||
formData.append("path", [...dirStack, name].join("/"));
|
formData.append("path", [...dirStack, name].join("/"));
|
||||||
await fetch("/api/files/upload", {
|
await fetch("/api/files/upload", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -105,7 +105,7 @@ export default function MineclusterFiles(props) {
|
||||||
async function downloadFiles(files) {
|
async function downloadFiles(files) {
|
||||||
Promise.all(
|
Promise.all(
|
||||||
files.map((f) =>
|
files.map((f) =>
|
||||||
getServerItem(serverName, f.name, [...dirStack, f.name].join("/")),
|
getServerItem(serverId, f.name, [...dirStack, f.name].join("/")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then(() => console.log("Done downloading files!"))
|
.then(() => console.log("Done downloading files!"))
|
||||||
|
|
|
@ -16,7 +16,8 @@ export function useRconDialog(isOpen = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RconDialog(props) {
|
export default function RconDialog(props) {
|
||||||
const { serverName, open, dialogToggle } = props;
|
const { server, open, dialogToggle } = props;
|
||||||
|
const { name: serverName, id: serverId } = server ?? {};
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
return (
|
return (
|
||||||
|
@ -33,7 +34,7 @@ export default function RconDialog(props) {
|
||||||
<Toolbar sx={{ display: { sm: "none" } }} />
|
<Toolbar sx={{ display: { sm: "none" } }} />
|
||||||
<DialogTitle>RCON - {serverName}</DialogTitle>
|
<DialogTitle>RCON - {serverName}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<RconView serverName={serverName} />
|
<RconView serverId={serverId} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button autoFocus onClick={dialogToggle}>
|
<Button autoFocus onClick={dialogToggle}>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { io } from "socket.io-client";
|
import { io } from "socket.io-client";
|
||||||
export default class RconSocket {
|
export default class RconSocket {
|
||||||
constructor(logUpdate, serverName) {
|
constructor(logUpdate, serverId) {
|
||||||
(this.sk = io("/", { query: { serverName } })), (this.logs = []);
|
(this.sk = io("/", { query: { serverId } })), (this.logs = []);
|
||||||
this.logUpdate = logUpdate;
|
this.logUpdate = logUpdate;
|
||||||
this.sk.on("push", this.onPush.bind(this));
|
this.sk.on("push", this.onPush.bind(this));
|
||||||
this.sk.on("connect", this.onConnect.bind(this));
|
this.sk.on("connect", this.onConnect.bind(this));
|
||||||
|
|
|
@ -6,14 +6,14 @@ import RconSocket from "./RconSocket.js";
|
||||||
import "@mcl/css/rcon.css";
|
import "@mcl/css/rcon.css";
|
||||||
|
|
||||||
export default function RconView(props) {
|
export default function RconView(props) {
|
||||||
const { serverName } = props;
|
const { serverId } = props;
|
||||||
const logsRef = useRef(0);
|
const logsRef = useRef(0);
|
||||||
const [cmd, setCmd] = useState("");
|
const [cmd, setCmd] = useState("");
|
||||||
const [logs, setLogs] = useState([]);
|
const [logs, setLogs] = useState([]);
|
||||||
const [rcon, setRcon] = useState({});
|
const [rcon, setRcon] = useState({});
|
||||||
const updateCmd = (e) => setCmd(e.target.value);
|
const updateCmd = (e) => setCmd(e.target.value);
|
||||||
useEffect(function () {
|
useEffect(function () {
|
||||||
setRcon(new RconSocket(setLogs, serverName));
|
setRcon(new RconSocket(setLogs, serverId));
|
||||||
return () => {
|
return () => {
|
||||||
if (rcon && typeof rcon.disconnect === "function") rcon.disconnect();
|
if (rcon && typeof rcon.disconnect === "function") rcon.disconnect();
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,10 +19,10 @@ import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export default function ServerCard(props) {
|
export default function ServerCard(props) {
|
||||||
const { server, openRcon } = props;
|
const { server, openRcon } = props;
|
||||||
const { name, metrics, ftpAvailable, serverAvailable, services } = server;
|
const { name, id, metrics, ftpAvailable, serverAvailable, services } = server;
|
||||||
const startServer = useStartServer(name);
|
const startServer = useStartServer(id);
|
||||||
const stopServer = useStopServer(name);
|
const stopServer = useStopServer(id);
|
||||||
const deleteServer = useDeleteServer(name);
|
const deleteServer = useDeleteServer(id);
|
||||||
function toggleRcon() {
|
function toggleRcon() {
|
||||||
if (!services.includes("server")) return;
|
if (!services.includes("server")) return;
|
||||||
openRcon();
|
openRcon();
|
||||||
|
@ -113,7 +113,7 @@ export default function ServerCard(props) {
|
||||||
aria-label="Edit"
|
aria-label="Edit"
|
||||||
size="large"
|
size="large"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={`/mcl/edit?server=${name}`}
|
to={`/mcl/edit?server=${id}`}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -122,7 +122,7 @@ export default function ServerCard(props) {
|
||||||
aria-label="Files"
|
aria-label="Files"
|
||||||
size="large"
|
size="large"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={`/mcl/files?server=${name}`}
|
to={`/mcl/files?server=${id}`}
|
||||||
disabled={!services.includes("ftp")}
|
disabled={!services.includes("ftp")}
|
||||||
>
|
>
|
||||||
<FolderIcon />
|
<FolderIcon />
|
||||||
|
|
|
@ -50,10 +50,10 @@ export default function Home() {
|
||||||
<Box className="servers">
|
<Box className="servers">
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
servers.map((s, k) => (
|
servers.map((s, k) => (
|
||||||
<ServerCard key={k} server={s} openRcon={openRcon(s.name)} />
|
<ServerCard key={k} server={s} openRcon={openRcon(s)} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
<RconDialog open={rdOpen} dialogToggle={rconToggle} serverName={server} />
|
<RconDialog open={rdOpen} dialogToggle={rconToggle} server={server} />
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/mcl/create"
|
to="/mcl/create"
|
||||||
|
|
|
@ -20,38 +20,38 @@ const fetchApiPost = (subPath, json) => async () =>
|
||||||
body: JSON.stringify(json),
|
body: JSON.stringify(json),
|
||||||
}).then((res) => res.json());
|
}).then((res) => res.json());
|
||||||
|
|
||||||
export const useServerStatus = (server) =>
|
export const useServerStatus = (serverId) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: [`server-status-${server}`],
|
queryKey: [`server-status-${serverId}`],
|
||||||
queryFn: fetchApiPost("/server/status", { name: server }),
|
queryFn: fetchApiPost("/server/status", { id: serverId }),
|
||||||
});
|
});
|
||||||
export const useServerMetrics = (server) =>
|
export const useServerMetrics = (serverId) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: [`server-metrics-${server}`],
|
queryKey: [`server-metrics-${serverId}`],
|
||||||
queryFn: fetchApiPost("/server/metrics", { name: server }),
|
queryFn: fetchApiPost("/server/metrics", { id: serverId }),
|
||||||
refetchInterval: 10000,
|
refetchInterval: 10000,
|
||||||
});
|
});
|
||||||
export const useStartServer = (server) =>
|
export const useStartServer = (serverId) =>
|
||||||
postJsonApi("/server/start", { name: server }, "server-instances");
|
postJsonApi("/server/start", { id: serverId }, "server-instances");
|
||||||
export const useStopServer = (server) =>
|
export const useStopServer = (serverId) =>
|
||||||
postJsonApi("/server/stop", { name: server }, "server-instances");
|
postJsonApi("/server/stop", { id: serverId }, "server-instances");
|
||||||
export const useDeleteServer = (server) =>
|
export const useDeleteServer = (serverId) =>
|
||||||
postJsonApi("/server/delete", { name: server }, "server-instances", "DELETE");
|
postJsonApi("/server/delete", { id: serverId }, "server-instances", "DELETE");
|
||||||
export const useCreateServer = (spec) =>
|
export const useCreateServer = (spec) =>
|
||||||
postJsonApi("/server/create", spec, "server-list");
|
postJsonApi("/server/create", spec, "server-list");
|
||||||
|
|
||||||
export const getServerFiles = async (server, path) =>
|
export const getServerFiles = async (serverId, path) =>
|
||||||
fetchApiCore("/files/list", { name: server, path }, "POST", true);
|
fetchApiCore("/files/list", { id: serverId, path }, "POST", true);
|
||||||
export const createServerFolder = async (server, path) =>
|
export const createServerFolder = async (serverId, path) =>
|
||||||
fetchApiCore("/files/folder", {
|
fetchApiCore("/files/folder", {
|
||||||
name: server,
|
id: serverId,
|
||||||
path,
|
path,
|
||||||
});
|
});
|
||||||
export const deleteServerItem = async (server, path, isDir) =>
|
export const deleteServerItem = async (serverId, path, isDir) =>
|
||||||
fetchApiCore("/files/item", { name: server, path, isDir }, "DELETE");
|
fetchApiCore("/files/item", { id: serverId, path, isDir }, "DELETE");
|
||||||
|
|
||||||
export const getServerItem = async (server, name, path) =>
|
export const getServerItem = async (serverId, name, path) =>
|
||||||
fetchApiCore("/files/item", { name: server, path })
|
fetchApiCore("/files/item", { id: serverId, path })
|
||||||
.then((resp) =>
|
.then((resp) =>
|
||||||
resp.status === 200
|
resp.status === 200
|
||||||
? resp.blob()
|
? resp.blob()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue