This commit is contained in:
Jan Naahs
2020-06-20 23:31:05 +02:00
parent 07123eb03a
commit 774c40bfa5
13 changed files with 287 additions and 62 deletions

View File

@ -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>

View File

@ -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)
}
})(); })();
}, []); }, []);

View File

@ -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>
); );

View File

@ -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>
); );
} }

View File

@ -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>

View 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;

View 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;

View File

@ -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
View 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;

View File

@ -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>
}
/>
</>
) )
} }

View 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;

View File

@ -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;
}
} }

View File

@ -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;
}
} }