[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:
parent
8fb5b34c77
commit
4f19cf19d9
62 changed files with 5910 additions and 1190 deletions
45
src/components/servers/RconDialog.jsx
Normal file
45
src/components/servers/RconDialog.jsx
Normal 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>
|
||||
);
|
||||
}
|
27
src/components/servers/RconSocket.js
Normal file
27
src/components/servers/RconSocket.js
Normal 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();
|
||||
}
|
||||
}
|
53
src/components/servers/RconView.jsx
Normal file
53
src/components/servers/RconView.jsx
Normal 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>
|
||||
);
|
||||
}
|
129
src/components/servers/ServerCard.jsx
Normal file
129
src/components/servers/ServerCard.jsx
Normal 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>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue