Merge remote-tracking branch 'jannaahs/rework-ui' into rework-ui-cleanup-mod-portal

# Conflicts:
#	src/handlers.go
#	src/mods_handler.go
#	src/routes.go
#	ui/App/views/Controls.jsx
#	ui/App/views/Saves/Saves.jsx
This commit is contained in:
knoxfighter
2020-09-10 03:34:30 +02:00
11 changed files with 192 additions and 149 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -4,7 +4,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Factorio Server Manager</title> <title>Factorio Server Manager</title>
<link rel="icon" type="image/png" href="./images/favicon.ico">
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">

View File

@ -5,7 +5,18 @@ module.exports = {
purge: [], purge: [],
theme: { theme: {
extend: { extend: {
width: {
72: "18rem",
80: "20rem",
88: "22rem",
96: "24rem",
},
margin: {
72: "18rem",
80: "20rem",
88: "22rem",
96: "24rem",
}
}, },
colors: { colors: {
"gray": { "gray": {

View File

@ -1,10 +1,13 @@
import React, {useEffect} from "react"; import React, {useEffect, useState} from "react";
import server from "../../api/resources/server";
import {NavLink} from "react-router-dom"; import {NavLink} from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faBars} from "@fortawesome/free-solid-svg-icons";
const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => { const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => {
const [isNavCollapsed, setIsNavCollapsed] = useState(true);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
updateServerStatus() updateServerStatus()
@ -34,6 +37,7 @@ const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => {
const Link = ({children, to, last}) => { const Link = ({children, to, last}) => {
return ( return (
<NavLink <NavLink
onClick={() => setIsNavCollapsed(true)}
exact={true} exact={true}
to={to} to={to}
activeClassName="bg-orange" activeClassName="bg-orange"
@ -42,55 +46,62 @@ const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => {
} }
return ( return (
<div className="flex md:flex-row-reverse flex-wrap"> <>
{/*Sidebar*/}
<div className="w-full md:w-88 md:fixed md:top-0 md:left-0 bg-gray-dark md:h-screen">
<div className="py-4 px-2 accentuated">
<div className="mx-4 justify-between flex text-center">
<span className="text-dirty-white text-xl">Factorio Server Manager</span>
<button
className="md:hidden cursor-pointer text-white hover:text-dirty-white"
onClick={() => setIsNavCollapsed(!isNavCollapsed)}
>
<FontAwesomeIcon icon={faBars}/>
</button>
</div>
</div>
<div className={isNavCollapsed ? "hidden md:block" : "block"}>
<div className="py-4 px-2 accentuated">
<h1 className="text-dirty-white text-lg mb-2 mx-4">Server Status</h1>
<div className="mx-4 mb-4 text-center">
<Status info={serverStatus}/>
</div>
</div>
<div className="py-4 px-2 accentuated">
<h1 className="text-dirty-white text-lg mb-2 mx-4">Server Management</h1>
<div className="text-white text-center rounded-sm bg-black shadow-inner mx-4 p-1">
<Link to="/">Controls</Link>
<Link to="/saves">Saves</Link>
<Link to="/mods">Mods</Link>
<Link to="/server-settings">Server Settings</Link>
<Link to="/game-settings">Game Settings</Link>
<Link to="/console">Console</Link>
<Link to="/logs" last={true}>Logs</Link>
</div>
</div>
<div className="py-4 px-2 accentuated">
<h1 className="text-dirty-white text-lg mb-2 mx-4">FSM Administration</h1>
<div className="text-white text-center rounded-sm bg-black shadow-inner mx-4 p-1">
<Link to="/user-management">Users</Link>
<Link to="/help" last={true}>Help</Link>
</div>
</div>
<div className="py-4 px-2 accentuated">
<div className="text-white text-center rounded-sm bg-black shadow-inner mx-4 p-1">
<Button type="danger" className="w-full" onClick={handleLogout}>Logout</Button>
</div>
</div>
<div className="accentuated-t accentuated-x md:block hidden"/>
</div>
</div>
{/*Main*/} {/*Main*/}
<div className="w-full md:w-5/6 bg-gray-100 bg-banner bg-fixed min-h-screen"> <div className="md:ml-88 bg-gray-100 bg-black min-h-screen">
<div className="container mx-auto bg-gray-100 pt-16 px-6"> <div className="container mx-auto bg-gray-100 pt-16 px-6">
{children} {children}
</div> </div>
</div> </div>
</>
{/*Sidebar*/}
<div
className="w-full md:w-1/6 bg-gray-dark fixed bottom-0 md:top-0 md:left-0 h-16 md:h-screen">
<div className="py-4 px-2 accentuated items-center text-center">
<img src="/images/factorio.jpg" className="inline h-8" alt="Factorio Logo"/>
<span className="text-dirty-white pl-2 text-xl">Factorio Server Manager</span>
</div>
<div className="py-4 px-2 accentuated">
<h1 className="text-dirty-white text-lg mb-2 mx-4">Server Status</h1>
<div className="mx-4 mb-4 text-center">
<Status info={serverStatus}/>
</div>
</div>
<div className="py-4 px-2 accentuated">
<h1 className="text-dirty-white text-lg mb-2 mx-4">Server Management</h1>
<div className="text-white text-center rounded-sm bg-black shadow-inner mx-4 p-1">
<Link to="/">Controls</Link>
<Link to="/saves">Saves</Link>
<Link to="/mods">Mods</Link>
<Link to="/server-settings">Server Settings</Link>
<Link to="/game-settings">Game Settings</Link>
<Link to="/console">Console</Link>
<Link to="/logs" last={true}>Logs</Link>
</div>
</div>
<div className="py-4 px-2 accentuated">
<h1 className="text-dirty-white text-lg mb-2 mx-4">FSM Administration</h1>
<div className="text-white text-center rounded-sm bg-black shadow-inner mx-4 p-1">
<Link to="/user-management">Users</Link>
<Link to="/help" last={true}>Help</Link>
</div>
</div>
<div className="py-4 px-2 accentuated">
<div className="text-white text-center rounded-sm bg-black shadow-inner mx-4 p-1">
<Button type="danger" className="w-full" onClick={handleLogout}>Logout</Button>
</div>
</div>
<div className="accentuated h-full"/>
</div>
</div>
); );
} }

View File

@ -50,75 +50,86 @@ const Controls = ({serverStatus, updateServerStatus}) => {
<Panel <Panel
title="Server Status" title="Server Status"
content={ content={
<div className="flex"> <div className="lg:flex">
<table className="w-full"> { isRunning
<thead> ? <>
<tr className="text-left py-1"> <div className="lg:w-1/5 mb-2">
<th>Status</th> <div className="font-bold">Status</div>
<th>IP</th> <div>{serverStatus.status}</div>
<th>Port</th> </div>
<th>Factorio Version</th> <div className="lg:w-1/5 mb-2">
<th>Save File</th> <div className="font-bold">IP</div>
</tr> <div>{serverStatus.address}</div>
</thead> </div>
<tbody> <div className="lg:w-1/5 mb-2">
{isRunning <div className="font-bold">Port</div>
? <tr className="py-1"> <div>{serverStatus.port}</div>
<td className="pr-4 py-2">{serverStatus.status}</td> </div>
<td className="pr-4 py-2">{serverStatus.address}</td> <div className="lg:w-1/5 mb-2">
<td className="pr-4 py-2">{serverStatus.port}</td> <div className="font-bold">Factorio Version</div>
<td className="pr-4 py-2">{factorioVersion}</td> <div>{factorioVersion}</div>
<td className="pr-4 py-2">{serverStatus.savefile}</td> </div>
</tr> <div className="lg:w-1/5 mb-2">
: <tr className="py-1"> <div className="font-bold">Save</div>
<td className="pr-4 py-2">{serverStatus.status}</td> <div>{serverStatus.savefile}</div>
<td className="pr-4"> </div>
<input </>
name="ip" : <>
className="shadow appearance-none w-full py-2 px-3 text-black" <div className="lg:w-1/5 mb-2">
type="text" <div className="font-bold">Status</div>
defaultValue={"0.0.0.0"} <div>{serverStatus.status}</div>
ref={register({required: true, pattern: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'})} </div>
/> <div className="lg:w-1/5 mb-2 mr-0 lg:mr-4">
{errors.ip && <span className="block text-red">IP is required and must be valid.</span>} <div className="font-bold">IP</div>
</td> <input
<td className="pr-4"> name="ip"
<input className="shadow appearance-none w-full mr-2 py-2 px-3 text-black"
name="port" type="text"
className="shadow appearance-none w-full py-2 px-3 text-black" defaultValue={"0.0.0.0"}
type="number" ref={register({required: true, pattern: '^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'})}
min={1} />
defaultValue={"34197"} {errors.ip && <span className="block text-red">IP is required and must be valid.</span>}
ref={register({required: true})} </div>
/> <div className="lg:w-1/5 mb-2 mr-0 lg:mr-4">
{errors.port && <span className="block text-red">Port is required</span>} <div className="font-bold">Port</div>
</td> <input
<td className="pr-4 py-2">{factorioVersion}</td> name="port"
<td className="pr-4 py-2"> className="shadow appearance-none w-full py-2 px-3 text-black"
<div className="relative"> type="number"
<Select min={1}
name="save" defaultValue={"34197"}
inputRef={register({required: true})} ref={register({required: true})}
> />
{saves.map(save => ( {errors.port && <span className="block text-red">Port is required</span>}
<option value={save.name} key={save.name}>{save.name}</option>))} </div>
</Select> <div className="lg:w-1/5 mb-2 mr-0 lg:mr-4">
</div> <div className="font-bold">Factorio Version</div>
</td> <div>{factorioVersion}</div>
</tr> </div>
} <div className="lg:w-1/5 mb-2">
</tbody> <div className="font-bold">Save</div>
</table> <div className="relative">
<Select
name="save"
inputRef={register({required: true})}
>
{saves.map(save => (
<option value={save.name} key={save.name}>{save.name}</option>))}
</Select>
</div>
</div>
</>
}
</div> </div>
} }
actions={ actions={
<div className="flex"> <div className="md:flex">
{isRunning {isRunning
? <> ? <>
<Button onClick={stopServer} size="sm" className="mr-2" type="default">Save & Stop Server</Button> <Button onClick={stopServer} size="sm" className="w-full md:w-auto mb-2 md:mb-0 md:mr-2" type="default">Save & Stop Server</Button>
<Button onClick={killServer} size="sm" type="danger">Kill Server</Button> <Button onClick={killServer} size="sm" type="danger" className="w-full md:w-auto">Kill Server</Button>
</> </>
: <Button isSubmit={true} size="sm" type="success">Start Server</Button> : <Button isSubmit={true} size="sm" type="success" className="w-full md:w-auto">Start Server</Button>
} }
</div> </div>
} }

View File

@ -6,6 +6,7 @@ import {useHistory, useLocation} from "react-router";
import Panel from "../components/Panel"; import Panel from "../components/Panel";
import Input from "../components/Input"; import Input from "../components/Input";
import Label from "../components/Label"; import Label from "../components/Label";
import {Flash} from "../components/Flash";
const Login = ({handleLogin}) => { const Login = ({handleLogin}) => {
const {register, handleSubmit, errors} = useForm(); const {register, handleSubmit, errors} = useForm();
@ -32,7 +33,7 @@ const Login = ({handleLogin}) => {
}, []) }, [])
return ( return (
<div className="h-screen overflow-hidden flex items-center justify-center bg-banner"> <div className="h-screen overflow-hidden flex items-center justify-center bg-black">
<Panel <Panel
title="Login" title="Login"
content={ content={
@ -58,6 +59,7 @@ const Login = ({handleLogin}) => {
</form> </form>
} }
/> />
<Flash/>
</div> </div>
); );
}; };

View File

@ -11,12 +11,12 @@ const Saves = ({serverStatus}) => {
const [saves, setSaves] = useState([]); const [saves, setSaves] = useState([]);
const updateList = () => { const updateList = () => {
savesResource.list() savesResource.list()
.then(res => { .then(res => {
if (res) { if (res) {
setSaves(res); setSaves(res);
} }
}) })
} }
@ -33,10 +33,10 @@ const Saves = ({serverStatus}) => {
return ( return (
<> <>
<div className="flex mb-6"> <div className="lg:flex mb-6">
<Panel <Panel
title="Create Save" title="Create Save"
className="w-1/2 mr-3" className="lg:w-1/2 lg:mr-3 mb-6 lg:mb-0"
content={ content={
serverStatus.status === "running" serverStatus.status === "running"
? <p className="text-red-light pt-4 pb-24"> ? <p className="text-red-light pt-4 pb-24">
@ -48,7 +48,7 @@ const Saves = ({serverStatus}) => {
/> />
<Panel <Panel
title="Upload Save" title="Upload Save"
className="w-1/2 ml-3" className="lg:w-1/2 lg:ml-3"
content={<UploadSaveForm onSuccess={updateList}/>} content={<UploadSaveForm onSuccess={updateList}/>}
/> />
</div> </div>
@ -57,31 +57,36 @@ const Saves = ({serverStatus}) => {
className="mb-4" className="mb-4"
title="Saves" title="Saves"
content={ content={
<table className="w-full"> <div className="overflow-x-auto">
<thead> <table style={{"width" : "max-content"}}>
<tr className="text-left py-1"> <thead>
<th>Name</th> <tr className="text-left py-1">
<th>Last Modified At</th> <th>Name</th>
<th>Size</th> <th>Last Modified At</th>
<th>Actions</th> <th>Size</th>
</tr> <th>Actions</th>
</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>
<a href={`/api/saves/dl/${save.name}`} className="mr-2">
<FontAwesomeIcon className="text-gray-light cursor-pointer hover:text-orange" icon={faDownload}/>
</a>
<FontAwesomeIcon className="text-red cursor-pointer hover:text-red-light mr-2" onClick={() => deleteSave(save)} icon={faTrashAlt}/>
</td>
</tr> </tr>
)} </thead>
</tbody> <tbody>
</table> {saves.map(save =>
<tr className="py-2 md: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 className="pr-4">{parseFloat(save.size / 1024 / 1024).toFixed(3)} MB</td>
<td>
<a href={`/api/saves/dl/${save.name}`} className="mr-2">
<FontAwesomeIcon
className="text-gray-light cursor-pointer hover:text-orange"
icon={faDownload}/>
</a>
<FontAwesomeIcon className="text-red cursor-pointer hover:text-red-light mr-2"
onClick={() => deleteSave(save)} icon={faTrashAlt}/>
</td>
</tr>
)}
</tbody>
</table>
</div>
} }
/> />
</> </>

View File

@ -7,4 +7,13 @@ const client = Axios.create({
} }
}); });
client.interceptors.response.use(res => res, err => {
if(err.response.status === 502) {
window.flash("Service not available", "red");
} else if (err.response.status !== 401) {
window.flash(err.response.data, "red");
}
return Promise.reject(err);
});
export default client; export default client;

View File

@ -2,11 +2,6 @@
@import "tailwindcss/utilities"; @import "tailwindcss/utilities";
@import "tailwindcss/components"; @import "tailwindcss/components";
.bg-banner {
background-image: url("/images/factorio-main-banner.jpg");
background-position: center center;
}
.accentuated-t { .accentuated-t {
border-top: 2px solid rgba(255, 255, 255, 0.1); border-top: 2px solid rgba(255, 255, 255, 0.1);
} }