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
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:
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 |
@ -4,7 +4,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Factorio Server Manager</title>
|
||||
<link rel="icon" type="image/png" href="./images/favicon.ico">
|
||||
<!-- 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">
|
||||
|
||||
|
@ -5,7 +5,18 @@ module.exports = {
|
||||
purge: [],
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
width: {
|
||||
72: "18rem",
|
||||
80: "20rem",
|
||||
88: "22rem",
|
||||
96: "24rem",
|
||||
},
|
||||
margin: {
|
||||
72: "18rem",
|
||||
80: "20rem",
|
||||
88: "22rem",
|
||||
96: "24rem",
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
"gray": {
|
||||
|
@ -1,10 +1,13 @@
|
||||
import React, {useEffect} from "react";
|
||||
import server from "../../api/resources/server";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {NavLink} from "react-router-dom";
|
||||
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 [isNavCollapsed, setIsNavCollapsed] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
updateServerStatus()
|
||||
@ -34,6 +37,7 @@ const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => {
|
||||
const Link = ({children, to, last}) => {
|
||||
return (
|
||||
<NavLink
|
||||
onClick={() => setIsNavCollapsed(true)}
|
||||
exact={true}
|
||||
to={to}
|
||||
activeClassName="bg-orange"
|
||||
@ -42,55 +46,62 @@ const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => {
|
||||
}
|
||||
|
||||
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*/}
|
||||
<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">
|
||||
{children}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -50,75 +50,86 @@ const Controls = ({serverStatus, updateServerStatus}) => {
|
||||
<Panel
|
||||
title="Server Status"
|
||||
content={
|
||||
<div className="flex">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="text-left py-1">
|
||||
<th>Status</th>
|
||||
<th>IP</th>
|
||||
<th>Port</th>
|
||||
<th>Factorio Version</th>
|
||||
<th>Save File</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isRunning
|
||||
? <tr className="py-1">
|
||||
<td className="pr-4 py-2">{serverStatus.status}</td>
|
||||
<td className="pr-4 py-2">{serverStatus.address}</td>
|
||||
<td className="pr-4 py-2">{serverStatus.port}</td>
|
||||
<td className="pr-4 py-2">{factorioVersion}</td>
|
||||
<td className="pr-4 py-2">{serverStatus.savefile}</td>
|
||||
</tr>
|
||||
: <tr className="py-1">
|
||||
<td className="pr-4 py-2">{serverStatus.status}</td>
|
||||
<td className="pr-4">
|
||||
<input
|
||||
name="ip"
|
||||
className="shadow appearance-none w-full py-2 px-3 text-black"
|
||||
type="text"
|
||||
defaultValue={"0.0.0.0"}
|
||||
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]?)$'})}
|
||||
/>
|
||||
{errors.ip && <span className="block text-red">IP is required and must be valid.</span>}
|
||||
</td>
|
||||
<td className="pr-4">
|
||||
<input
|
||||
name="port"
|
||||
className="shadow appearance-none w-full py-2 px-3 text-black"
|
||||
type="number"
|
||||
min={1}
|
||||
defaultValue={"34197"}
|
||||
ref={register({required: true})}
|
||||
/>
|
||||
{errors.port && <span className="block text-red">Port is required</span>}
|
||||
</td>
|
||||
<td className="pr-4 py-2">{factorioVersion}</td>
|
||||
<td className="pr-4 py-2">
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="lg:flex">
|
||||
{ isRunning
|
||||
? <>
|
||||
<div className="lg:w-1/5 mb-2">
|
||||
<div className="font-bold">Status</div>
|
||||
<div>{serverStatus.status}</div>
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2">
|
||||
<div className="font-bold">IP</div>
|
||||
<div>{serverStatus.address}</div>
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2">
|
||||
<div className="font-bold">Port</div>
|
||||
<div>{serverStatus.port}</div>
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2">
|
||||
<div className="font-bold">Factorio Version</div>
|
||||
<div>{factorioVersion}</div>
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2">
|
||||
<div className="font-bold">Save</div>
|
||||
<div>{serverStatus.savefile}</div>
|
||||
</div>
|
||||
</>
|
||||
: <>
|
||||
<div className="lg:w-1/5 mb-2">
|
||||
<div className="font-bold">Status</div>
|
||||
<div>{serverStatus.status}</div>
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2 mr-0 lg:mr-4">
|
||||
<div className="font-bold">IP</div>
|
||||
<input
|
||||
name="ip"
|
||||
className="shadow appearance-none w-full mr-2 py-2 px-3 text-black"
|
||||
type="text"
|
||||
defaultValue={"0.0.0.0"}
|
||||
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]?)$'})}
|
||||
/>
|
||||
{errors.ip && <span className="block text-red">IP is required and must be valid.</span>}
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2 mr-0 lg:mr-4">
|
||||
<div className="font-bold">Port</div>
|
||||
<input
|
||||
name="port"
|
||||
className="shadow appearance-none w-full py-2 px-3 text-black"
|
||||
type="number"
|
||||
min={1}
|
||||
defaultValue={"34197"}
|
||||
ref={register({required: true})}
|
||||
/>
|
||||
{errors.port && <span className="block text-red">Port is required</span>}
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2 mr-0 lg:mr-4">
|
||||
<div className="font-bold">Factorio Version</div>
|
||||
<div>{factorioVersion}</div>
|
||||
</div>
|
||||
<div className="lg:w-1/5 mb-2">
|
||||
<div className="font-bold">Save</div>
|
||||
<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>
|
||||
}
|
||||
actions={
|
||||
<div className="flex">
|
||||
<div className="md:flex">
|
||||
{isRunning
|
||||
? <>
|
||||
<Button onClick={stopServer} size="sm" className="mr-2" type="default">Save & Stop Server</Button>
|
||||
<Button onClick={killServer} size="sm" type="danger">Kill 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" 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>
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {useHistory, useLocation} from "react-router";
|
||||
import Panel from "../components/Panel";
|
||||
import Input from "../components/Input";
|
||||
import Label from "../components/Label";
|
||||
import {Flash} from "../components/Flash";
|
||||
|
||||
const Login = ({handleLogin}) => {
|
||||
const {register, handleSubmit, errors} = useForm();
|
||||
@ -32,7 +33,7 @@ const Login = ({handleLogin}) => {
|
||||
}, [])
|
||||
|
||||
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
|
||||
title="Login"
|
||||
content={
|
||||
@ -58,6 +59,7 @@ const Login = ({handleLogin}) => {
|
||||
</form>
|
||||
}
|
||||
/>
|
||||
<Flash/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -11,12 +11,12 @@ const Saves = ({serverStatus}) => {
|
||||
const [saves, setSaves] = useState([]);
|
||||
|
||||
const updateList = () => {
|
||||
savesResource.list()
|
||||
.then(res => {
|
||||
if (res) {
|
||||
setSaves(res);
|
||||
}
|
||||
})
|
||||
savesResource.list()
|
||||
.then(res => {
|
||||
if (res) {
|
||||
setSaves(res);
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@ -33,10 +33,10 @@ const Saves = ({serverStatus}) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex mb-6">
|
||||
<div className="lg:flex mb-6">
|
||||
<Panel
|
||||
title="Create Save"
|
||||
className="w-1/2 mr-3"
|
||||
className="lg:w-1/2 lg:mr-3 mb-6 lg:mb-0"
|
||||
content={
|
||||
serverStatus.status === "running"
|
||||
? <p className="text-red-light pt-4 pb-24">
|
||||
@ -48,7 +48,7 @@ const Saves = ({serverStatus}) => {
|
||||
/>
|
||||
<Panel
|
||||
title="Upload Save"
|
||||
className="w-1/2 ml-3"
|
||||
className="lg:w-1/2 lg:ml-3"
|
||||
content={<UploadSaveForm onSuccess={updateList}/>}
|
||||
/>
|
||||
</div>
|
||||
@ -57,31 +57,36 @@ const Saves = ({serverStatus}) => {
|
||||
className="mb-4"
|
||||
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>
|
||||
<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>
|
||||
<div className="overflow-x-auto">
|
||||
<table style={{"width" : "max-content"}}>
|
||||
<thead>
|
||||
<tr className="text-left py-1">
|
||||
<th>Name</th>
|
||||
<th>Last Modified At</th>
|
||||
<th>Size</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
|
@ -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;
|
@ -2,11 +2,6 @@
|
||||
@import "tailwindcss/utilities";
|
||||
@import "tailwindcss/components";
|
||||
|
||||
.bg-banner {
|
||||
background-image: url("/images/factorio-main-banner.jpg");
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.accentuated-t {
|
||||
border-top: 2px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
Reference in New Issue
Block a user