You've already forked factorio-server-manager
mirror of
https://github.com/OpenFactorioServerManager/factorio-server-manager.git
synced 2025-07-17 01:22:28 +02:00
wip
This commit is contained in:
@ -8,6 +8,9 @@ import {BrowserRouter} from "react-router-dom";
|
|||||||
import Logs from "./views/Logs";
|
import Logs from "./views/Logs";
|
||||||
import Saves from "./views/Saves";
|
import Saves from "./views/Saves";
|
||||||
import Layout from "./components/Layout";
|
import Layout from "./components/Layout";
|
||||||
|
import server from "../api/resources/server";
|
||||||
|
import Mods from "./views/Mods";
|
||||||
|
import UserManagement from "./views/UserManagment";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|
||||||
@ -15,6 +18,13 @@ const App = () => {
|
|||||||
const [serverStatus, setServerStatus] = useState(null);
|
const [serverStatus, setServerStatus] = useState(null);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
const updateServerStatus = async () => {
|
||||||
|
const status = await server.status();
|
||||||
|
if (status.success) {
|
||||||
|
setServerStatus(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleAuthenticationStatus = async () => {
|
const handleAuthenticationStatus = async () => {
|
||||||
const status = await user.status();
|
const status = await user.status();
|
||||||
setIsAuthenticated(status.success);
|
setIsAuthenticated(status.success);
|
||||||
@ -31,7 +41,7 @@ const App = () => {
|
|||||||
const ProtectedRoute = useCallback(({component: Component, ...rest}) => (
|
const ProtectedRoute = useCallback(({component: Component, ...rest}) => (
|
||||||
<Route {...rest} render={(props) => (
|
<Route {...rest} render={(props) => (
|
||||||
isAuthenticated
|
isAuthenticated
|
||||||
? <Component serverStatus={serverStatus} {...props} />
|
? <Component serverStatus={serverStatus} updateServerStatus={updateServerStatus} {...props} />
|
||||||
: <Redirect to={{
|
: <Redirect to={{
|
||||||
pathname: '/login',
|
pathname: '/login',
|
||||||
state: {from: props.location}
|
state: {from: props.location}
|
||||||
@ -44,15 +54,15 @@ const App = () => {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/login" render={() => (<Login handleLogin={handleAuthenticationStatus}/>)}/>
|
<Route path="/login" render={() => (<Login handleLogin={handleAuthenticationStatus}/>)}/>
|
||||||
|
|
||||||
<Layout handleLogout={handleLogout} serverStatus={serverStatus} setServerStatus={setServerStatus}>
|
<Layout handleLogout={handleLogout} serverStatus={serverStatus} updateServerStatus={updateServerStatus}>
|
||||||
<ProtectedRoute exact path="/" component={Controls}/>
|
<ProtectedRoute exact path="/" component={Controls}/>
|
||||||
<ProtectedRoute path="/saves" component={Saves}/>
|
<ProtectedRoute path="/saves" component={Saves}/>
|
||||||
<ProtectedRoute path="/mods" component={Controls}/>
|
<ProtectedRoute path="/mods" component={Mods}/>
|
||||||
<ProtectedRoute path="/server-settings" component={Controls}/>
|
<ProtectedRoute path="/server-settings" component={Controls}/>
|
||||||
<ProtectedRoute path="/game-settings" component={Controls}/>
|
<ProtectedRoute path="/game-settings" component={Controls}/>
|
||||||
<ProtectedRoute path="/console" component={Controls}/>
|
<ProtectedRoute path="/console" component={Controls}/>
|
||||||
<ProtectedRoute path="/logs" component={Logs}/>
|
<ProtectedRoute path="/logs" component={Logs}/>
|
||||||
<ProtectedRoute path="/user-management" component={Controls}/>
|
<ProtectedRoute path="/user-management" component={UserManagement}/>
|
||||||
<ProtectedRoute path="/help" component={Controls}/>
|
<ProtectedRoute path="/help" component={Controls}/>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
@ -3,14 +3,11 @@ import server from "../../api/resources/server";
|
|||||||
import {NavLink} from "react-router-dom";
|
import {NavLink} from "react-router-dom";
|
||||||
import Button from "../elements/Button";
|
import Button from "../elements/Button";
|
||||||
|
|
||||||
const Layout = ({children, handleLogout, serverStatus, setServerStatus}) => {
|
const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const status = await server.status();
|
updateServerStatus()
|
||||||
if (status.success) {
|
|
||||||
setServerStatus(status)
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const Button = ({ children, type, onClick, isSubmit, className }) => {
|
const Button = ({ children, type, onClick, isSubmit, className, size }) => {
|
||||||
|
|
||||||
let color = '';
|
let color = '';
|
||||||
|
let padding = '';
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'success':
|
case 'success':
|
||||||
@ -15,9 +16,17 @@ const Button = ({ children, type, onClick, isSubmit, className }) => {
|
|||||||
color = 'bg-gray-light hover:glow-orange hover:bg-orange'
|
color = 'bg-gray-light hover:glow-orange hover:bg-orange'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case 'sm':
|
||||||
|
padding = 'py-1 px-2';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
padding = 'py-2 px-4'
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button onClick={onClick} className={`${className} ${color} accentuated text-black font-bold py-2 px-4`}
|
<button onClick={onClick} className={`${className ? className: null} ${padding} ${color} inline-block accentuated text-black font-bold`}
|
||||||
type={isSubmit ? 'submit' : 'button'}>
|
type={isSubmit ? 'submit' : 'button'}>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,36 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const ButtonLink = () => {
|
const ButtonLink = ({children, href, type, target, className, size}) => {
|
||||||
|
|
||||||
|
let color = '';
|
||||||
|
let padding = '';
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'success':
|
||||||
|
color = 'bg-green hover:glow-green hover:bg-green-light';
|
||||||
|
break;
|
||||||
|
case 'danger':
|
||||||
|
color = 'bg-red hover:glow-red hover:bg-red-light';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
color = 'bg-gray-light hover:glow-orange hover:bg-orange'
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case 'sm':
|
||||||
|
padding = 'py-1 px-2';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
padding = 'py-2 px-4'
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className="accentuated bg-green hover:glow-green hover:bg-green-light text-black font-bold py-2 px-4 w-full"
|
<a
|
||||||
type="submit">
|
href={href}
|
||||||
Sign In
|
target={target ? target : '_self'}
|
||||||
</button>
|
className={`${className ? className : null} ${color} ${padding} inline-block accentuated text-black font-bold`}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const Panel = ({title, content, actions}) => {
|
const Panel = ({title, content, actions, className}) => {
|
||||||
return (
|
return (
|
||||||
<div className="accentuated rounded-sm bg-gray-dark shadow-xl pb-4">
|
<div className={(className ? className : null) + ' accentuated rounded-sm bg-gray-dark shadow-xl pb-4'}>
|
||||||
<div className="px-4 py-2 text-xl text-dirty-white font-bold">
|
<div className="px-4 py-2 text-xl text-dirty-white font-bold">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
|
35
ui/App/forms/CreateSaveForm.jsx
Normal file
35
ui/App/forms/CreateSaveForm.jsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {useForm} from "react-hook-form";
|
||||||
|
import Button from "../elements/Button";
|
||||||
|
import React from "react";
|
||||||
|
import saves from "../../api/resources/saves";
|
||||||
|
|
||||||
|
const CreateSaveForm = ({onSuccess}) => {
|
||||||
|
const {register, handleSubmit, errors} = useForm();
|
||||||
|
|
||||||
|
|
||||||
|
const onSubmit = async data => {
|
||||||
|
const res = await saves.create(data.savefile);
|
||||||
|
if (res.success) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-white text-sm font-bold mb-2" htmlFor="password">
|
||||||
|
Savefile Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="shadow appearance-none w-full py-2 px-3 text-black"
|
||||||
|
ref={register({required: true})}
|
||||||
|
name="savefile"
|
||||||
|
id="savefile" type="text"/>
|
||||||
|
{errors.savefile && <span className="block text-red">Savefile Name is required</span>}
|
||||||
|
</div>
|
||||||
|
<Button type="success" isSubmit={true}>Create Save</Button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateSaveForm;
|
35
ui/App/forms/UploadSaveForm.jsx
Normal file
35
ui/App/forms/UploadSaveForm.jsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Button from "../elements/Button";
|
||||||
|
import React from "react";
|
||||||
|
import {useForm} from "react-hook-form";
|
||||||
|
import saves from "../../api/resources/saves";
|
||||||
|
|
||||||
|
|
||||||
|
const UploadSaveForm = ({onSuccess}) => {
|
||||||
|
|
||||||
|
const {register, handleSubmit, errors} = useForm();
|
||||||
|
const onSubmit = async data => {
|
||||||
|
const res = await saves.upload(data.savefile);
|
||||||
|
if (res.success) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-white text-sm font-bold mb-2" htmlFor="password">
|
||||||
|
Savefile Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="shadow appearance-none w-full py-2 px-3 text-black"
|
||||||
|
ref={register({required: true})}
|
||||||
|
name="savefile"
|
||||||
|
id="savefile" type="file"/>
|
||||||
|
{errors.savefile && <span className="block text-red">Savefile Name is required</span>}
|
||||||
|
</div>
|
||||||
|
<Button type="success">Upload Save</Button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadSaveForm;
|
@ -1,21 +1,26 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||||
import Panel from "../elements/Panel";
|
import Panel from "../elements/Panel";
|
||||||
import Button from "../elements/Button";
|
import Button from "../elements/Button";
|
||||||
import server from "../../api/resources/server";
|
import server from "../../api/resources/server";
|
||||||
|
|
||||||
const Controls = ({serverStatus}) => {
|
const Controls = ({serverStatus, updateServerStatus}) => {
|
||||||
|
|
||||||
const [factorioVersion, setFactorioVersion] = useState('unknown');
|
const [factorioVersion, setFactorioVersion] = useState('unknown');
|
||||||
const isRunning = () => {
|
const isRunning = serverStatus.data.status === 'running';
|
||||||
return serverStatus.data.status === 'running';
|
|
||||||
|
const startServer = async () => {
|
||||||
|
await server.start('0.0.0.0',34197,'DEATH.zip');
|
||||||
|
await updateServerStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
const startServer = () => {
|
const stopServer = async () => {
|
||||||
|
await server.stop();
|
||||||
|
await updateServerStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopServer = () => {
|
const killServer = async () => {
|
||||||
|
await server.kill();
|
||||||
|
await updateServerStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -31,7 +36,7 @@ const Controls = ({serverStatus}) => {
|
|||||||
<Panel
|
<Panel
|
||||||
title="Server Status"
|
title="Server Status"
|
||||||
content={
|
content={
|
||||||
<div className="flex" slot="content">
|
<div className="flex">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="text-left py-1">
|
<tr className="text-left py-1">
|
||||||
@ -54,10 +59,10 @@ const Controls = ({serverStatus}) => {
|
|||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{ isRunning()
|
{ isRunning
|
||||||
? <>
|
? <>
|
||||||
<Button onClick={stopServer} className="mr-2" type="default">Save & Stop Server</Button>
|
<Button onClick={stopServer} className="mr-2" type="default">Save & Stop Server</Button>
|
||||||
<Button onClick={stopServer} type="danger">Stop Server</Button>
|
<Button onClick={killServer} type="danger">Kill Server</Button>
|
||||||
</>
|
</>
|
||||||
: <Button onClick={startServer} type="success">Start Server</Button>
|
: <Button onClick={startServer} type="success">Start Server</Button>
|
||||||
}
|
}
|
||||||
|
22
ui/App/views/Mods.jsx
Normal file
22
ui/App/views/Mods.jsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Panel from "../elements/Panel";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Mods = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Panel
|
||||||
|
title="Mods"
|
||||||
|
className="mb-6"
|
||||||
|
content={<h1>Test</h1>}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Panel
|
||||||
|
title="Mod Packs"
|
||||||
|
content={<h1>Test</h1>}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Mods;
|
@ -1,44 +1,87 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import save from "../../api/resources/saves";
|
import saveClient from "../../api/resources/saves";
|
||||||
import Panel from "../elements/Panel";
|
import Panel from "../elements/Panel";
|
||||||
|
import ButtonLink from "../elements/ButtonLink";
|
||||||
|
import Button from "../elements/Button";
|
||||||
|
import {useForm} from "react-hook-form";
|
||||||
|
import CreateSaveForm from "../forms/CreateSaveForm";
|
||||||
|
import UploadSaveForm from "../forms/UploadSaveForm";
|
||||||
|
|
||||||
const Saves = () => {
|
const Saves = ({serverStatus}) => {
|
||||||
|
|
||||||
const [saves, setSaves] = useState([]);
|
const [saves, setSaves] = useState([]);
|
||||||
|
const {register, handleSubmit, errors} = useForm();
|
||||||
|
|
||||||
|
const updateList = async () => {
|
||||||
|
const res = await saveClient.list();
|
||||||
|
if (res.success) {
|
||||||
|
setSaves(res.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
updateList()
|
||||||
const list = await save.list();
|
}, []);
|
||||||
if (list.success) {
|
|
||||||
console.log(list)
|
const deleteSave = async (save) => {
|
||||||
setSaves(list.data);
|
const res = await saveClient.delete(save);
|
||||||
}
|
if (res.success) {
|
||||||
})()
|
updateList()
|
||||||
}, [])
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<>
|
||||||
title="Saves"
|
<div className="flex mb-6">
|
||||||
content={<table className="w-full">
|
<Panel
|
||||||
<thead>
|
title="Create Save"
|
||||||
<tr className="text-left py-1">
|
className="w-1/2 mr-3"
|
||||||
<th>Name</th>
|
content={
|
||||||
<th>Last Modified At</th>
|
serverStatus.data.status === "running"
|
||||||
<th>Size</th>
|
? <p className="text-red-light pt-4 pb-24">
|
||||||
<th>Actions</th>
|
Create a new Save is only possible if the Factorio server is
|
||||||
</tr>
|
not running.
|
||||||
</thead>
|
</p>
|
||||||
<tbody>
|
: <CreateSaveForm onSuccess={updateList}/>
|
||||||
{saves.map(save =>
|
}
|
||||||
<tr className="py-1" key={save.name}>
|
/>
|
||||||
<td className="pr-4">{save.name}</td>
|
<Panel
|
||||||
<td className="pr-4">{save.last_mod}</td>
|
title="Upload Save"
|
||||||
<td>{parseFloat(save.size / 1024 / 1024).toFixed(3)} MB</td>
|
className="w-1/2 ml-3"
|
||||||
</tr>
|
content={<UploadSaveForm/>}
|
||||||
)}
|
/>
|
||||||
</tbody>
|
</div>
|
||||||
</table>}
|
|
||||||
/>
|
<Panel
|
||||||
|
title="Saves"
|
||||||
|
content={
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-left py-1">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Last Modified At</th>
|
||||||
|
<th>Size</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{saves.map(save =>
|
||||||
|
<tr className="py-1" key={save.name}>
|
||||||
|
<td className="pr-4">{save.name}</td>
|
||||||
|
<td className="pr-4">{(new Date(save.last_mod)).toISOString().replace('T', ' ').split('.')[0]}</td>
|
||||||
|
<td>{parseFloat(save.size / 1024 / 1024).toFixed(3)} MB</td>
|
||||||
|
<td>
|
||||||
|
<ButtonLink size="sm" href={`/api/saves/dl/${save.name}`}
|
||||||
|
className="mr-2">Download</ButtonLink>
|
||||||
|
<Button size="sm" onClick={() => deleteSave(save)} type="danger">Delete</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
ui/App/views/UserManagment.jsx
Normal file
20
ui/App/views/UserManagment.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Panel from "../elements/Panel";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const UserManagement = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Panel
|
||||||
|
title="List of Users"
|
||||||
|
content="test"
|
||||||
|
className="mb-4"
|
||||||
|
/>
|
||||||
|
<Panel
|
||||||
|
title="Create User"
|
||||||
|
content="test"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserManagement;
|
@ -5,4 +5,12 @@ export default {
|
|||||||
const response = await client.get('/api/saves/list');
|
const response = await client.get('/api/saves/list');
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
delete: async (save) => {
|
||||||
|
const response = await client.get(`/api/saves/rm/${save.name}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
create: async (name) => {
|
||||||
|
const response = await client.get(`/api/saves/create/${name}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
}
|
}
|
@ -9,4 +9,20 @@ export default {
|
|||||||
const response = await client.get('/api/server/status');
|
const response = await client.get('/api/server/status');
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
stop: async () => {
|
||||||
|
const response = await client.post('/api/server/stop');
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
start: async (ip, port, savefile) => {
|
||||||
|
const response = await client.post('/api/server/start', {
|
||||||
|
bindip: ip,
|
||||||
|
savefile,
|
||||||
|
port
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
kill: async () => {
|
||||||
|
const response = await client.get('/api/server/kill');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user