Merge pull request #244 from OpenFactorioServerManager/disable-while-factorio-running

Disable Mods while factorio is running
This commit is contained in:
knoxfighter 2021-02-10 23:22:22 +01:00 committed by GitHub
commit 635f695629
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 228 additions and 89 deletions

View File

@ -1,9 +1,9 @@
package api
import (
"net/http"
"github.com/OpenFactorioServerManager/factorio-server-manager/api/websocket"
"github.com/OpenFactorioServerManager/factorio-server-manager/factorio"
"net/http"
"github.com/gorilla/mux"
)
@ -13,10 +13,24 @@ type Route struct {
Method string
Pattern string
HandlerFunc http.HandlerFunc
ServerOff bool // Set to `true' if factorio server has to be turned off to call this
}
type Routes []Route
func ServerOffMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// only run if server is turned off
server := factorio.GetFactorioServer()
if server.GetRunning() {
http.Error(w, "factorio server still running", http.StatusLocked)
} else {
next.ServeHTTP(w, r)
}
return
})
}
func NewRouter() *mux.Router {
r := mux.NewRouter().StrictSlash(true)
@ -28,8 +42,20 @@ func NewRouter() *mux.Router {
// Serves all JSON REST handlers prefixed with /api
s := r.PathPrefix("/api").Subrouter()
s.Use(AuthMiddleware)
// use subrouter for calls, that run only, when server is turned off
so := s.NewRoute().Subrouter()
so.Use(ServerOffMiddleware)
s.NewRoute().Subrouter()
for _, route := range apiRoutes {
s.Methods(route.Method).
var router *mux.Router
if route.ServerOff {
router = so
} else {
router = s
}
router.Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(route.HandlerFunc)
@ -39,7 +65,6 @@ func NewRouter() *mux.Router {
r.Path("/api/login").
Methods("POST").
Name("LoginUser").
//HandlerFunc(LoginUser)
HandlerFunc(LoginUser)
// Route for initializing websocket connection
@ -115,106 +140,127 @@ var apiRoutes = Routes{
"GET",
"/saves/list",
ListSaves,
false,
}, {
"DlSave",
"GET",
"/saves/dl/{save}",
DLSave,
false,
}, {
"UploadSave",
"POST",
"/saves/upload",
UploadSave,
false,
}, {
"RemoveSave",
"GET",
"/saves/rm/{save}",
RemoveSave,
false,
}, {
"CreateSave",
"GET",
"/saves/create/{save}",
CreateSaveHandler,
true,
}, {
"LoadModsFromSave",
"POST",
"/saves/mods",
LoadModsFromSaveHandler,
true,
}, {
"LogTail",
"GET",
"/log/tail",
LogTail,
false,
}, {
"LoadConfig",
"GET",
"/config",
LoadConfig,
false,
}, {
"StartServer",
"POST",
"/server/start",
StartServer,
true,
}, {
"StopServer",
"GET",
"/server/stop",
StopServer,
false,
}, {
"KillServer",
"GET",
"/server/kill",
KillServer,
false,
}, {
"RunningServer",
"GET",
"/server/status",
CheckServer,
false,
}, {
"FactorioVersion",
"GET",
"/server/facVersion",
FactorioVersion,
false,
}, {
"LogoutUser",
"GET",
"/logout",
LogoutUser,
false,
}, {
"StatusUser",
"GET",
"/user/status",
GetCurrentLogin,
false,
}, {
"ListUsers",
"GET",
"/user/list",
ListUsers,
false,
}, {
"AddUser",
"POST",
"/user/add",
AddUser,
false,
}, {
"RemoveUser",
"POST",
"/user/remove",
RemoveUser,
false,
}, {
"ChangePassword",
"POST",
"/user/password",
ChangePassword,
false,
}, {
"GetServerSettings",
"GET",
"/settings",
GetServerSettings,
false,
}, {
"UpdateServerSettings",
"POST",
"/settings/update",
UpdateServerSettings,
false,
},
// Mod Portal Stuff
{
@ -222,36 +268,43 @@ var apiRoutes = Routes{
"GET",
"/mods/portal/list",
ModPortalListModsHandler,
false,
}, {
"ModPortalGetModInfo",
"GET",
"/mods/portal/info/{mod}",
ModPortalModInfoHandler,
false,
}, {
"ModPortalInstallMod",
"POST",
"/mods/portal/install",
ModPortalInstallHandler,
true,
}, {
"ModPortalLogin",
"POST",
"/mods/portal/login",
ModPortalLoginHandler,
false,
}, {
"ModPortalLoginStatus",
"GET",
"/mods/portal/loginstatus",
ModPortalLoginStatusHandler,
false,
}, {
"ModPortalLogout",
"GET",
"/mods/portal/logout",
ModPortalLogoutHandler,
false,
}, {
"ModPortalInstallMultiple",
"POST",
"/mods/portal/install/multiple",
ModPortalInstallMultipleHandler,
true,
},
// Mods Stuff
{
@ -259,36 +312,43 @@ var apiRoutes = Routes{
"GET",
"/mods/list",
ListInstalledModsHandler,
false,
}, {
"ToggleMod",
"POST",
"/mods/toggle",
ModToggleHandler,
true,
}, {
"DeleteMod",
"POST",
"/mods/delete",
ModDeleteHandler,
true,
}, {
"DeleteAllMods",
"POST",
"/mods/delete/all",
ModDeleteAllHandler,
true,
}, {
"UpdateMod",
"POST",
"/mods/update",
ModUpdateHandler,
true,
}, {
"UploadMod",
"POST",
"/mods/upload",
ModUploadHandler,
true,
}, {
"DownloadMods",
"GET",
"/mods/download",
ModDownloadHandler,
false,
},
// Mod Packs
{
@ -296,26 +356,31 @@ var apiRoutes = Routes{
"GET",
"/mods/packs/list",
ModPackListHandler,
false,
}, {
"ModPackCreate",
"POST",
"/mods/packs/create",
ModPackCreateHandler,
false,
}, {
"ModPackDelete",
"POST",
"/mods/packs/{modpack}/delete",
ModPackDeleteHandler,
false,
}, {
"ModPackDownload",
"GET",
"/mods/packs/{modpack}/download",
ModPackDownloadHandler,
false,
}, {
"LoadModPack",
"POST",
"/mods/packs/{modpack}/load",
ModPackLoadHandler,
true,
},
// Mods inside Mod Packs
{
@ -323,40 +388,48 @@ var apiRoutes = Routes{
"GET",
"/mods/packs/{modpack}/list",
ModPackModListHandler,
false,
}, {
"ModPackToggleMod",
"POST",
"/mods/packs/{modpack}/mod/toggle",
ModPackModToggleHandler,
false,
}, {
"ModPackDeleteMod",
"POST",
"/mods/packs/{modpack}/mod/delete",
ModPackModDeleteHandler,
false,
}, {
"ModPackDeleteAllMod",
"POST",
"/mods/packs/{modpack}/mod/delete/all",
ModPackModDeleteAllHandler,
false,
}, {
"ModPackUpdateMod",
"POST",
"/mods/packs/{modpack}/mod/update",
ModPackModUpdateHandler,
false,
}, {
"ModPackUploadMod",
"POST",
"/mods/packs/{modpack}/mod/upload",
ModPackModUploadHandler,
false,
}, {
"ModPackModPortalInstallMod",
"POST",
"/mods/packs/{modpack}/portal/install",
ModPackModPortalInstallHandler,
false,
}, {
"ModPackModPortalInstallMultiple",
"POST",
"/mods/packs/{modpack}/portal/install/multiple",
ModPackModPortalInstallMultipleHandler,
false,
},
}

View File

@ -13,7 +13,7 @@ import CreateModPack from "./components/CreateModPack";
import ModPack from "./components/ModPack";
import ModList from "./components/ModList";
const Mods = () => {
const Mods = ({serverStatus}) => {
const [installedMods, setInstalledMods] = useState([]);
const [modPacks, setModPacks] = useState([])
@ -103,31 +103,55 @@ const Mods = () => {
.then(fetchInstalledMods)
}
let disabled = serverStatus.status !== "stopped"
return (
<div>
<TabControl>
<Tab title="Install Mod">
<AddMod refetchInstalledMods={fetchInstalledMods} fuse={fuse}/>
</Tab>
<Tab title="Upload Mod">
<UploadMod refetchInstalledMods={fetchInstalledMods}/>
</Tab>
<Tab title="Load Mod from Save">
<LoadMods refreshMods={fetchInstalledMods}/>
</Tab>
</TabControl>
{disabled ?
<Panel className="mb-6"
content={
<div className="text-red font-bold text-xl">
Changing mods is disabled while the server is running!
</div>
}
/>
:
<TabControl>
<Tab title="Install Mod">
<AddMod refetchInstalledMods={fetchInstalledMods} fuse={fuse}/>
</Tab>
<Tab title="Upload Mod">
<UploadMod refetchInstalledMods={fetchInstalledMods}/>
</Tab>
<Tab title="Load Mod from Save">
<LoadMods refreshMods={fetchInstalledMods}/>
</Tab>
</TabControl>
}
<Panel
title="Mods"
className="mb-6"
content={
<ModList addUpdatableMod={addUpdatableMod} toggleMod={toggleMod} updateMod={updateMod} deleteMod={deleteMod} mods={installedMods} factorioVersion={factorioVersion}/>
<ModList addUpdatableMod={addUpdatableMod}
toggleMod={toggleMod}
updateMod={updateMod}
deleteMod={deleteMod}
mods={installedMods}
factorioVersion={factorioVersion}
disabled={disabled}
/>
}
actions={
<>
<Button size="sm" className="mr-2" type="danger" isLoading={isDeletingAllMods} onClick={deleteAllMods}>Delete all Mods</Button>
<Button size="sm" className="mr-2" isLoading={isUpdatingAllMods} onClick={updateAllMods}>Update all Mods</Button>
<a className="bg-gray-light py-1 px-2 hover:glow-orange hover:bg-orange inline-block accentuated text-black font-bold" href={modsResource.downloadAllURL}>Download all Mods</a>
{
!disabled &&
<Button size="sm" className="mr-2" type="danger" isLoading={isDeletingAllMods}
onClick={deleteAllMods}>Delete all Mods</Button> &&
<Button size="sm" className="mr-2" isLoading={isUpdatingAllMods}
onClick={updateAllMods}>Update all Mods</Button>
}
<a className="bg-gray-light py-1 px-2 hover:glow-orange hover:bg-orange inline-block accentuated text-black font-bold"
href={modsResource.downloadAllURL}>Download all Mods</a>
</>
}
/>
@ -136,7 +160,16 @@ const Mods = () => {
title="Mod packs"
className="mb-6"
content={
modPacks.map((pack, i) => <ModPack factorioVersion={factorioVersion} key={i} modPack={pack} reloadMods={fetchInstalledMods} reloadModPacks={fetchModPacks}/>)
modPacks.map(
(pack, i) =>
<ModPack factorioVersion={factorioVersion}
key={i}
modPack={pack}
reloadMods={fetchInstalledMods}
reloadModPacks={fetchModPacks}
disabled={disabled}
/>
)
}
actions={
<CreateModPack onSuccess={fetchModPacks}/>

View File

@ -12,64 +12,77 @@ import modsResource from "../../../../api/resources/mods";
import React, {useEffect, useState} from "react";
import {coerce, gt, satisfies} from "semver";
const Mod = ({mod, factorioVersion, toggleMod, deleteMod, updateMod, addUpdatableMod}) => {
const Mod = ({mod, factorioVersion, toggleMod, deleteMod, updateMod, addUpdatableMod, disabled = false}) => {
const [newVersion, setNewVersion] = useState(null)
const [icon, setIcon] = useState(faArrowCircleUp)
useEffect(() => {
(async () => {
const data = await modsResource.portal.info(mod.name)
if (!disabled) {
(async () => {
const data = await modsResource.portal.info(mod.name)
//get newest COMPATIBLE release
let newestRelease;
data.releases.forEach(release => {
if (
gt(
coerce(release.version),
coerce(mod.version)
) && (
satisfies(factorioVersion, "~"+coerce(release.info_json.factorio_version).version) ||
(
satisfies(factorioVersion, "1.0.0") &&
satisfies(coerce(release.info_json.factorio_version), "0.18.x")
//get newest COMPATIBLE release
let newestRelease;
data.releases.forEach(release => {
if (
gt(
coerce(release.version),
coerce(mod.version)
) && (
satisfies(factorioVersion, "~" + coerce(release.info_json.factorio_version).version) ||
(
satisfies(factorioVersion, "1.0.0") &&
satisfies(coerce(release.info_json.factorio_version), "0.18.x")
)
)
)
) {
if (!newestRelease) {
newestRelease = release;
} else if (gt(coerce(release.version).version, coerce(newestRelease.version).version)) {
newestRelease = release;
) {
if (!newestRelease) {
newestRelease = release;
} else if (gt(coerce(release.version).version, coerce(newestRelease.version).version)) {
newestRelease = release;
}
}
}
});
});
if (newestRelease && newestRelease.version !== mod.version) {
const installableVersion = {
downloadUrl: newestRelease.download_url,
fileName: newestRelease.file_name,
modName: mod.name
if (newestRelease && newestRelease.version !== mod.version) {
const installableVersion = {
downloadUrl: newestRelease.download_url,
fileName: newestRelease.file_name,
modName: mod.name
}
setNewVersion(installableVersion);
if (addUpdatableMod !== null) {
addUpdatableMod(installableVersion)
}
} else {
setNewVersion(null);
}
setNewVersion(installableVersion);
if (addUpdatableMod !== null) {
addUpdatableMod(installableVersion)
}
} else {
setNewVersion(null);
}
})();
})();
}
}, [mod]);
return (
<tr className="py-1">
<td className="pr-4">{mod.title}</td>
<td className="pr-4">
{mod.enabled
? <FontAwesomeIcon className="cursor-pointer hover:text-green-light text-green" icon={faToggleOn}
onClick={() => toggleMod(mod.name)}/>
: <FontAwesomeIcon className="cursor-pointer hover:text-red-light text-red" icon={faToggleOff}
onClick={() => toggleMod(mod.name)}/>
{
disabled
?
mod.enabled
? <FontAwesomeIcon className="text-green" icon={faCheck}/>
: <FontAwesomeIcon className="text-red" icon={faTimes}/>
:
mod.enabled
? <FontAwesomeIcon className="cursor-pointer hover:text-green-light text-green"
icon={faToggleOn}
onClick={() => toggleMod(mod.name)}/>
:
<FontAwesomeIcon className="cursor-pointer hover:text-red-light text-red"
icon={faToggleOff}
onClick={() => toggleMod(mod.name)}/>
}
</td>
<td className="pr-4">
@ -78,19 +91,24 @@ const Mod = ({mod, factorioVersion, toggleMod, deleteMod, updateMod, addUpdatabl
: <FontAwesomeIcon className="text-red" icon={faTimes}/>
}
</td>
<td className="pr-4">{mod.version} {newVersion && <FontAwesomeIcon spin={icon === faSpinner}
onClick={() => {
setIcon(faSpinner)
updateMod(newVersion)
.finally(() => setIcon(faArrowCircleUp))
}}
className="hover:text-orange cursor-pointer ml-1"
icon={icon}/>}</td>
<td className="pr-4">{mod.factorio_version}</td>
<td className="pr-4">
<FontAwesomeIcon className={"text-red cursor-pointer hover:text-red-light"}
onClick={() => deleteMod(mod.name)} icon={faTrashAlt}/>
</td>
{mod.version}
{!disabled && newVersion && <FontAwesomeIcon spin={icon === faSpinner}
onClick={() => {
setIcon(faSpinner)
updateMod(newVersion)
.finally(() => setIcon(faArrowCircleUp))
}}
className="hover:text-orange cursor-pointer ml-1"
icon={icon}/>}</td>
<td className="pr-4">{mod.factorio_version}</td>
{
!disabled &&
<td className="pr-4">
<FontAwesomeIcon className={"text-red cursor-pointer hover:text-red-light"}
onClick={() => deleteMod(mod.name)} icon={faTrashAlt}/>
</td>
}
</tr>
)
}

View File

@ -2,7 +2,7 @@ import Mod from "./Mod";
import React from "react";
const ModList = ({mods, factorioVersion, updateMod, toggleMod, deleteMod, addUpdatableMod = null}) => {
const ModList = ({mods, factorioVersion, updateMod, toggleMod, deleteMod, addUpdatableMod = null, disabled = false}) => {
return (
<table className="w-full">
@ -25,7 +25,9 @@ const ModList = ({mods, factorioVersion, updateMod, toggleMod, deleteMod, addUpd
toggleMod={toggleMod}
deleteMod={deleteMod}
addUpdatableMod={addUpdatableMod}
factorioVersion={factorioVersion}/>
factorioVersion={factorioVersion}
disabled={disabled}
/>
)
}
</tbody>

View File

@ -1,11 +1,11 @@
import React, {useState} from "react";
import {faTrashAlt, faSpinner, faUpload} from "@fortawesome/free-solid-svg-icons";
import {faSpinner, faTrashAlt, faUpload} from "@fortawesome/free-solid-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import modsResource from "../../../../api/resources/mods";
import ModList from "./ModList";
import ConfirmDialog from "../../../components/ConfirmDialog";
const ModPack = ({modPack, reloadModPacks, factorioVersion, reloadMods}) => {
const ModPack = ({modPack, reloadModPacks, factorioVersion, reloadMods, disabled = false}) => {
const [isLoading, setIsLoading] = useState(false);
const [isLoadModPackDialogOpen, setIsLoadModPackDialogOpen] = useState(false);
@ -54,21 +54,34 @@ const ModPack = ({modPack, reloadModPacks, factorioVersion, reloadMods}) => {
<div className="flex items-center justify-between">
<h2 className="text-lg text-dirty-white mb-1 inline">{modPack.name}</h2>
<div className="flex space-x-2">
<FontAwesomeIcon className="text-blue cursor-pointer hover:text-blue-light inline"
onClick={() => setIsLoadModPackDialogOpen(true)} spin={isLoading} icon={isLoading ? faSpinner : faUpload}/>
<ConfirmDialog
title="Load ModPack"
content={`Loading the ModPack ${modPack.name} will remove all installed Mods.`}
isOpen={isLoadModPackDialogOpen}
close={() => setIsLoadModPackDialogOpen(false)}
onSuccess={() => loadModPack(modPack.name)}
/>
{
!disabled &&
<>
<FontAwesomeIcon className="text-blue cursor-pointer hover:text-blue-light inline"
onClick={() => setIsLoadModPackDialogOpen(true)}
spin={isLoading}
icon={isLoading ? faSpinner : faUpload}
/>
<ConfirmDialog
title="Load ModPack"
content={`Loading the ModPack ${modPack.name} will remove all installed Mods.`}
isOpen={isLoadModPackDialogOpen}
close={() => setIsLoadModPackDialogOpen(false)}
onSuccess={() => loadModPack(modPack.name)}
/>
</>
}
<FontAwesomeIcon className="text-red cursor-pointer hover:text-red-light inline"
onClick={() => deleteModPack(modPack.name)} icon={faTrashAlt}/>
</div>
</div>
<ModList mods={modPack.mods.mods} factorioVersion={factorioVersion} toggleMod={toggleMod} updateMod={updateMod} deleteMod={deleteMod} />
<ModList mods={modPack.mods.mods}
factorioVersion={factorioVersion}
toggleMod={toggleMod}
updateMod={updateMod}
deleteMod={deleteMod}
/>
</div>
)
}