[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
|
@ -1,152 +1,12 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Select from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import InputLabel from "@mui/material/InputLabel";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import { useCreateServer, useVersionList } from "@mcl/queries";
|
||||
|
||||
const defaultServer = {
|
||||
version: "latest",
|
||||
name: "example",
|
||||
serverType: "VANILLA",
|
||||
difficulty: "easy",
|
||||
maxPlayers: "20",
|
||||
gamemode: "survival",
|
||||
memory: "1024",
|
||||
motd: "Minecluster Server Hosting",
|
||||
};
|
||||
|
||||
import CreateOptions from "./CreateOptions.jsx";
|
||||
export default function Create() {
|
||||
const [spec, setSpec] = useState(defaultServer);
|
||||
const versionList = useVersionList();
|
||||
const [versions, setVersions] = useState(["latest"]);
|
||||
const createServer = useCreateServer(spec);
|
||||
const updateSpec = (attr, val) => {
|
||||
const s = { ...spec };
|
||||
s[attr] = val;
|
||||
setSpec(s);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!versionList.data) return;
|
||||
setVersions([
|
||||
"latest",
|
||||
...versionList.data.versions
|
||||
.filter(({ type: releaseType }) => releaseType === "release")
|
||||
.map(({ id }) => id),
|
||||
]);
|
||||
}, [versionList.data]);
|
||||
|
||||
const coreUpdate = (attr) => (e) => updateSpec(attr, e.target.value);
|
||||
|
||||
function upsertSpec() {
|
||||
if (validateSpec() !== "validated") return;
|
||||
createServer(spec);
|
||||
}
|
||||
|
||||
function validateSpec() {
|
||||
console.log("TODO CREATE VALIDATION");
|
||||
if (!spec.name) return alertValidationError("Name not included");
|
||||
if (!spec.version) return alertValidationError("Version cannot be blank");
|
||||
if (!spec.url) return alertValidationError("Url cannot be blank");
|
||||
return "validated";
|
||||
}
|
||||
|
||||
function alertValidationError(reason) {
|
||||
alert(`Could not validate spec because: ${reason}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="create">
|
||||
<FormControl fullWidth>
|
||||
<TextField
|
||||
label="Name"
|
||||
onChange={coreUpdate("name")}
|
||||
defaultValue={spec.name}
|
||||
required
|
||||
/>
|
||||
<TextField label="URL" onChange={coreUpdate("url")} required />
|
||||
<TextField
|
||||
label="Version"
|
||||
onChange={coreUpdate("version")}
|
||||
value={spec.version}
|
||||
select
|
||||
required
|
||||
SelectProps={{ MenuProps: { sx: { maxHeight: "12rem" } } }}
|
||||
>
|
||||
{versions.map((v, k) => (
|
||||
<MenuItem value={v} key={k}>
|
||||
{v}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
label="Server Type"
|
||||
onChange={coreUpdate("serverType")}
|
||||
value={spec.serverType}
|
||||
select
|
||||
required
|
||||
>
|
||||
<MenuItem value={"VANILLA"}>Vanilla</MenuItem>
|
||||
<MenuItem value={"FABRIC"}>Fabric</MenuItem>
|
||||
<MenuItem value={"PAPER"}>Paper</MenuItem>
|
||||
<MenuItem value={"SPIGOT"}>Spigot</MenuItem>
|
||||
</TextField>
|
||||
<TextField
|
||||
label="Difficulty"
|
||||
onChange={coreUpdate("difficulty")}
|
||||
value={spec.difficulty}
|
||||
select
|
||||
required
|
||||
>
|
||||
<MenuItem value={"peaceful"}>Peaceful</MenuItem>
|
||||
<MenuItem value={"easy"}>Easy</MenuItem>
|
||||
<MenuItem value={"medium"}>Medium</MenuItem>
|
||||
<MenuItem value={"hard"}>Hard</MenuItem>
|
||||
</TextField>
|
||||
|
||||
<TextField label="Whitelist" onChange={coreUpdate("whitelist")} />
|
||||
<TextField label="Ops" onChange={coreUpdate("ops")} />
|
||||
<TextField label="Icon" onChange={coreUpdate("icon")} required />
|
||||
<TextField
|
||||
label="Max Players"
|
||||
onChange={coreUpdate("maxPlayers")}
|
||||
defaultValue={spec.maxPlayers}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Gamemode"
|
||||
onChange={coreUpdate("gamemode")}
|
||||
value={spec.gamemode}
|
||||
select
|
||||
required
|
||||
>
|
||||
<MenuItem value={"survival"}>Survival</MenuItem>
|
||||
<MenuItem value={"creative"}>Creative</MenuItem>
|
||||
<MenuItem value={"adventure"}>Adventure</MenuItem>
|
||||
<MenuItem value={"spectator"}>Spectator</MenuItem>
|
||||
</TextField>
|
||||
<TextField label="Seed" onChange={coreUpdate("seed")} />
|
||||
<TextField label="Modpack" onChange={coreUpdate("modpack")} />
|
||||
<TextField
|
||||
label="Memory"
|
||||
onChange={coreUpdate("memory")}
|
||||
defaultValue={spec.memory}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="MOTD"
|
||||
onChange={coreUpdate("motd")}
|
||||
defaultValue={spec.motd}
|
||||
required
|
||||
/>
|
||||
<Button onClick={upsertSpec} variant="contained">
|
||||
Create
|
||||
</Button>
|
||||
</FormControl>
|
||||
{/*<CreateMenu />*/}
|
||||
<Box className="create-wrapper" sx={{ display: "flex" }}>
|
||||
<CreateOptions />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
248
src/pages/CreateOptions.jsx
Normal file
248
src/pages/CreateOptions.jsx
Normal file
|
@ -0,0 +1,248 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Chip from "@mui/material/Chip";
|
||||
import Select from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import InputLabel from "@mui/material/InputLabel";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import { useCreateServer, useVersionList } from "@mcl/queries";
|
||||
|
||||
const defaultServer = {
|
||||
version: "latest",
|
||||
serverType: "VANILLA",
|
||||
difficulty: "easy",
|
||||
maxPlayers: "5",
|
||||
gamemode: "survival",
|
||||
memory: "512",
|
||||
motd: `\\u00A7e\\u00A7ka\\u00A7l\\u00A7aMine\\u00A76Cluster\\u00A7r\\u00A78\\u00A7b\\u00A7ka`,
|
||||
};
|
||||
|
||||
export default function Create() {
|
||||
const [wl, setWl] = useState([]);
|
||||
const [ops, setOps] = useState([]);
|
||||
const [spec, setSpec] = useState(defaultServer);
|
||||
const versionList = useVersionList();
|
||||
const [versions, setVersions] = useState(["latest"]);
|
||||
const createServer = useCreateServer(spec);
|
||||
const updateSpec = (attr, val) => {
|
||||
const s = { ...spec };
|
||||
s[attr] = val;
|
||||
setSpec(s);
|
||||
console.log(s);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!versionList.data) return;
|
||||
setVersions([
|
||||
"latest",
|
||||
...versionList.data.versions
|
||||
.filter(({ type: releaseType }) => releaseType === "release")
|
||||
.map(({ id }) => id),
|
||||
]);
|
||||
}, [versionList.data]);
|
||||
|
||||
const coreUpdate = (attr) => (e) => updateSpec(attr, e.target.value);
|
||||
|
||||
function opsAdd(e) {
|
||||
const opEntry = e.target.innerHTML ?? e.target.value;
|
||||
if (!opEntry) return;
|
||||
const newOps = [...ops, opEntry];
|
||||
setOps(newOps);
|
||||
updateSpec("ops", newOps.join(","));
|
||||
}
|
||||
|
||||
function whitelistAdd(e) {
|
||||
const wlEntry = e.target.value;
|
||||
if (!wlEntry) return;
|
||||
const newWl = [...wl, wlEntry];
|
||||
setWl(newWl);
|
||||
updateSpec("whitelist", newWl.join(","));
|
||||
}
|
||||
|
||||
const opsRemove =
|
||||
(name, { onDelete: updateAutoComplete }) =>
|
||||
(e) => {
|
||||
updateAutoComplete(e);
|
||||
const newOps = [...ops];
|
||||
const entryIndex = newOps.indexOf(name);
|
||||
if (entryIndex === -1) return;
|
||||
newOps.splice(entryIndex, 1);
|
||||
setOps(newOps);
|
||||
updateSpec("ops", newOps.join(","));
|
||||
};
|
||||
|
||||
const whitelistRemove =
|
||||
(name, { onDelete: updateAutocomplete }) =>
|
||||
(e) => {
|
||||
updateAutocomplete(e);
|
||||
const newWl = [...wl];
|
||||
const entryIndex = newWl.indexOf(name);
|
||||
if (entryIndex === -1) return;
|
||||
newWl.splice(entryIndex, 1);
|
||||
setWl(newWl);
|
||||
updateSpec("whitelist", newWl.join(","));
|
||||
};
|
||||
|
||||
const opUpdate = (e) => alert("Op not implimented");
|
||||
|
||||
function upsertSpec() {
|
||||
if (validateSpec() !== "validated") return;
|
||||
createServer(spec);
|
||||
}
|
||||
|
||||
function validateSpec() {
|
||||
console.log("TODO CREATE VALIDATION");
|
||||
if (!spec.name) return alertValidationError("Name not included");
|
||||
if (!spec.version) return alertValidationError("Version cannot be blank");
|
||||
if (!spec.host) return alertValidationError("Host cannot be blank");
|
||||
return "validated";
|
||||
}
|
||||
|
||||
function alertValidationError(reason) {
|
||||
alert(`Could not validate spec because: ${reason}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="create-options"
|
||||
sx={{ width: "100%", maxWidth: "600px", margin: "auto" }}
|
||||
>
|
||||
<FormControl fullWidth sx={{ mt: "2rem", display: "flex", gap: ".5rem" }}>
|
||||
<TextField
|
||||
label="Name"
|
||||
onChange={coreUpdate("name")}
|
||||
helperText="Example: My Survival World"
|
||||
defaultValue={spec.name}
|
||||
FormHelperTextProps={{ sx: { ml: 0 } }}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Host"
|
||||
onChange={coreUpdate("host")}
|
||||
helperText="Example: host.mc.example.com"
|
||||
FormHelperTextProps={{ sx: { ml: 0 } }}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Version"
|
||||
onChange={coreUpdate("version")}
|
||||
value={spec.version}
|
||||
select
|
||||
required
|
||||
SelectProps={{ MenuProps: { sx: { maxHeight: "20rem" } } }}
|
||||
>
|
||||
{versions.map((v, k) => (
|
||||
<MenuItem value={v} key={k}>
|
||||
{v}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
label="Server Type"
|
||||
onChange={coreUpdate("serverType")}
|
||||
value={spec.serverType}
|
||||
select
|
||||
required
|
||||
>
|
||||
<MenuItem value={"VANILLA"}>Vanilla</MenuItem>
|
||||
<MenuItem value={"FABRIC"}>Fabric</MenuItem>
|
||||
<MenuItem value={"PAPER"}>Paper</MenuItem>
|
||||
<MenuItem value={"SPIGOT"}>Spigot</MenuItem>
|
||||
</TextField>
|
||||
<TextField
|
||||
label="Difficulty"
|
||||
onChange={coreUpdate("difficulty")}
|
||||
value={spec.difficulty}
|
||||
select
|
||||
required
|
||||
>
|
||||
<MenuItem value={"peaceful"}>Peaceful</MenuItem>
|
||||
<MenuItem value={"easy"}>Easy</MenuItem>
|
||||
<MenuItem value={"medium"}>Medium</MenuItem>
|
||||
<MenuItem value={"hard"}>Hard</MenuItem>
|
||||
</TextField>
|
||||
|
||||
<Autocomplete
|
||||
multiple
|
||||
id="whitelist-autocomplete"
|
||||
options={[]}
|
||||
onChange={whitelistAdd}
|
||||
freeSolo
|
||||
renderInput={(p) => <TextField {...p} label="Whitelist" />}
|
||||
renderTags={(value, getTagProps) =>
|
||||
value.map((option, index) => {
|
||||
const defaultChipProps = getTagProps({ index });
|
||||
return (
|
||||
<Chip
|
||||
label={option}
|
||||
{...defaultChipProps}
|
||||
onDelete={whitelistRemove(option, defaultChipProps)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Autocomplete
|
||||
filterSelectedOptions={true}
|
||||
multiple
|
||||
id="ops-autocomplete"
|
||||
options={wl}
|
||||
onChange={opsAdd}
|
||||
renderInput={(p) => <TextField {...p} label="Ops" />}
|
||||
renderTags={(value, getTagProps) =>
|
||||
value.map((option, index) => {
|
||||
const defaultChipProps = getTagProps({ index });
|
||||
return (
|
||||
<Chip
|
||||
label={option}
|
||||
{...defaultChipProps}
|
||||
onDelete={opsRemove(option, defaultChipProps)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
/>
|
||||
{/*<TextField label="Ops" onChange={coreUpdate("ops")} />*/}
|
||||
{/*<TextField label="Icon" onChange={coreUpdate("icon")} required />*/}
|
||||
<TextField
|
||||
label="Max Players"
|
||||
onChange={coreUpdate("maxPlayers")}
|
||||
defaultValue={spec.maxPlayers}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Gamemode"
|
||||
onChange={coreUpdate("gamemode")}
|
||||
value={spec.gamemode}
|
||||
select
|
||||
required
|
||||
>
|
||||
<MenuItem value={"survival"}>Survival</MenuItem>
|
||||
<MenuItem value={"creative"}>Creative</MenuItem>
|
||||
<MenuItem value={"adventure"}>Adventure</MenuItem>
|
||||
<MenuItem value={"spectator"}>Spectator</MenuItem>
|
||||
</TextField>
|
||||
<TextField label="Seed" onChange={coreUpdate("seed")} />
|
||||
{/*<TextField label="Modpack" onChange={coreUpdate("modpack")} />*/}
|
||||
<TextField
|
||||
label="Memory"
|
||||
onChange={coreUpdate("memory")}
|
||||
defaultValue={spec.memory}
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="MOTD"
|
||||
onChange={coreUpdate("motd")}
|
||||
defaultValue={spec.motd}
|
||||
required
|
||||
/>
|
||||
<Button onClick={upsertSpec} variant="contained">
|
||||
Create
|
||||
</Button>
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
}
|
20
src/pages/Files.jsx
Normal file
20
src/pages/Files.jsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { useEffect } from "react";
|
||||
import { useSearchParams, useNavigate } from "react-router-dom";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import MineclusterFiles from "@mcl/components/files/MineclusterFiles.jsx";
|
||||
|
||||
export default function Files() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const currentServer = searchParams.get("server");
|
||||
const nav = useNavigate();
|
||||
useEffect(() => {
|
||||
if (!currentServer) nav("/");
|
||||
}, [currentServer]);
|
||||
return (
|
||||
<Box className="edit" sx={{ height: "100%" }}>
|
||||
<MineclusterFiles server={currentServer} />
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -1,19 +1,25 @@
|
|||
import { Link } from "react-router-dom";
|
||||
import { useState, useEffect } from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import ServerCard from "../servers/ServerCard.jsx";
|
||||
import RconDialog, { useRconDialog } from "../servers/RconDialog.jsx";
|
||||
import Overview from "../overview/Overview.jsx";
|
||||
import ServerCard from "@mcl/components/servers/ServerCard.jsx";
|
||||
import RconDialog, {
|
||||
useRconDialog,
|
||||
} from "@mcl/components/servers/RconDialog.jsx";
|
||||
import Overview from "@mcl/components/overview/Overview.jsx";
|
||||
import Button from "@mui/material/Button";
|
||||
import SpeedDialIcon from "@mui/material/SpeedDialIcon";
|
||||
import "@mcl/css/server-card.css";
|
||||
import "@mcl/css/overview.css";
|
||||
import { useServerInstances } from "@mcl/queries";
|
||||
|
||||
export default function Home() {
|
||||
const clusterMetrics = { cpu: 0, memory: 0 };
|
||||
const [server, setServer] = useState();
|
||||
const [servers, setServers] = useState([]);
|
||||
const [rdOpen, rconToggle] = useRconDialog();
|
||||
const { isLoading, data: serversData } = useServerInstances();
|
||||
const { servers: serverInstances, clusterMetrics } = serversData ?? {};
|
||||
const serverInstances = serversData ?? [];
|
||||
useEffect(() => {
|
||||
if (!serverInstances) return;
|
||||
serverInstances.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
@ -53,6 +59,23 @@ export default function Home() {
|
|||
dialogToggle={rconToggle}
|
||||
serverName={server}
|
||||
/>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/mcl/create"
|
||||
color="primary"
|
||||
variant="contained"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
padding: "1rem",
|
||||
borderRadius: "100%",
|
||||
height: "4rem",
|
||||
width: "4rem",
|
||||
}}
|
||||
>
|
||||
<SpeedDialIcon />
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue