[FEATURE] Basic System with file manager (#4)

Co-authored-by: dunemask <dunemask@gmail.com>
Co-authored-by: Dunemask <dunemask@gmail.com>
Reviewed-on: https://gitea.dunemask.dev/elysium/minecluster/pulls/4
This commit is contained in:
dunemask 2023-12-20 03:20:04 +00:00
parent 8fb5b34c77
commit 4f19cf19d9
62 changed files with 5910 additions and 1190 deletions

View file

@ -0,0 +1,45 @@
import { useState } from "react";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import Button from "@mui/material/Button";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import Dialog from "@mui/material/Dialog";
import Toolbar from "@mui/material/Toolbar";
import RconView from "./RconView.jsx";
export function useRconDialog(isOpen = false) {
const [open, setOpen] = useState(isOpen);
const dialogToggle = () => setOpen(!open);
return [open, dialogToggle];
}
export default function RconDialog(props) {
const { serverName, open, dialogToggle } = props;
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
return (
<Dialog
sx={
fullScreen
? {}
: { "& .MuiDialog-paper": { width: "80%", maxHeight: 525 } }
}
maxWidth="xs"
open={open}
fullScreen={fullScreen}
>
<Toolbar sx={{ display: { sm: "none" } }} />
<DialogTitle>RCON - {serverName}</DialogTitle>
<DialogContent>
<RconView serverName={serverName} />
</DialogContent>
<DialogActions>
<Button autoFocus onClick={dialogToggle}>
Close
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -0,0 +1,27 @@
import { io } from "socket.io-client";
export default class RconSocket {
constructor(logUpdate, serverName) {
(this.sk = io("/", { query: { serverName } })), (this.logs = []);
this.logUpdate = logUpdate;
this.sk.on("push", this.onPush.bind(this));
this.sk.on("connect", this.onConnect.bind(this));
}
onPush(p) {
this.logs = [...this.logs, p];
this.logUpdate(this.logs);
}
send(m) {
this.sk.emit("msg", m);
}
onConnect() {
this.logs = [];
}
disconnect() {
if (!this.sk) return;
this.sk.disconnect();
}
}

View file

@ -0,0 +1,53 @@
import { useState, useEffect, useRef } from "react";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import RconSocket from "./RconSocket.js";
import "@mcl/css/rcon.css";
export default function RconView(props) {
const { serverName } = props;
const logsRef = useRef(0);
const [cmd, setCmd] = useState("");
const [logs, setLogs] = useState([]);
const [rcon, setRcon] = useState({});
const updateCmd = (e) => setCmd(e.target.value);
useEffect(function () {
setRcon(new RconSocket(setLogs, serverName));
return () => {
if (rcon && typeof rcon.disconnect === "function") rcon.disconnect();
};
}, []);
useEffect(() => {
logsRef.current.scrollTo(0, logsRef.current.scrollHeight);
}, [rcon.logs]);
function sendCommand() {
rcon.send(cmd);
setCmd("");
}
return (
<Box>
<div className="rconLogsWrapper" ref={logsRef}>
{logs.map((v, k) => (
<Box key={k}>
{v}
<br />
</Box>
))}
</div>
<Box className="rconActions">
<TextField
id="outlined-basic"
label="Command"
variant="outlined"
value={cmd}
onChange={updateCmd}
/>
<Button onClick={sendCommand}>Send</Button>
</Box>
</Box>
);
}

View file

@ -0,0 +1,129 @@
import React from "react";
import { useStartServer, useStopServer, useDeleteServer } from "@mcl/queries";
import Box from "@mui/material/Box";
import Card from "@mui/material/Card";
import CardActions from "@mui/material/CardActions";
import CardContent from "@mui/material/CardContent";
import Chip from "@mui/material/Chip";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import StopIcon from "@mui/icons-material/Stop";
import TerminalIcon from "@mui/icons-material/Terminal";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import PendingIcon from "@mui/icons-material/Pending";
import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
import EditIcon from "@mui/icons-material/Edit";
import FolderIcon from "@mui/icons-material/Folder";
import { Link } from "react-router-dom";
export default function ServerCard(props) {
const { server, openRcon } = props;
const { name, metrics, started } = server;
const startServer = useStartServer(name);
const stopServer = useStopServer(name);
const deleteServer = useDeleteServer(name);
function toggleRcon() {
if (!started) return;
openRcon();
}
return (
<Card className="server-card">
<CardContent className="server-card-header server-card-element">
<Typography
gutterBottom
variant="h5"
component="div"
className="server-card-title"
>
{name}
</Typography>
{metrics && (
<Box className="server-card-metrics">
<Typography
gutterBottom
variant="body2"
component="div"
className="server-card-metrics-info"
>
CPU: {metrics.cpu}
</Typography>
<Typography
gutterBottom
variant="body2"
component="div"
className="server-card-metrics-info"
>
MEM: {metrics.memory}MB
</Typography>
</Box>
)}
<Chip
label={started ? "Online" : "Offline"}
color={started ? "success" : "error"}
className="server-card-status-indicator"
/>
</CardContent>
<div className="server-card-actions-wrapper">
<CardActions className="server-card-actions server-card-element">
{started && (
<IconButton
color="error"
aria-label="Stop Server"
onClick={stopServer}
size="large"
>
<StopIcon />
</IconButton>
)}
{!started && (
<IconButton
color="success"
aria-label="Start Server"
onClick={startServer}
size="large"
>
<PlayArrowIcon />
</IconButton>
)}
<IconButton
color="primary"
aria-label="RCON"
onClick={toggleRcon}
size="large"
disabled={!started}
>
<TerminalIcon />
</IconButton>
<IconButton
color="primary"
aria-label="Edit"
size="large"
component={Link}
to={`/mcl/edit?server=${name}`}
>
<EditIcon />
</IconButton>
<IconButton
color="primary"
aria-label="Files"
size="large"
component={Link}
to={`/mcl/files?server=${name}`}
>
<FolderIcon />
</IconButton>
<IconButton
color="error"
aria-label="Delete Server"
onClick={deleteServer}
size="large"
>
<DeleteForeverIcon />
</IconButton>
</CardActions>
</div>
</Card>
);
}