You've already forked factorio-server-manager
mirror of
https://github.com/OpenFactorioServerManager/factorio-server-manager.git
synced 2026-06-19 20:06:55 +02:00
- remove Load Latest from save overviews (except under Controls)
- add actual latest save to Load Latest - disable Controls and Load Mods from Save when no save is present
This commit is contained in:
+30
-4
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -84,14 +85,28 @@ func SaveSession(w http.ResponseWriter, r *http.Request, session *sessions.Sessi
|
||||
// Lists all save files in the factorio/saves directory
|
||||
func ListSaves(w http.ResponseWriter, r *http.Request) {
|
||||
var resp interface{}
|
||||
config := bootstrap.GetConfig()
|
||||
defer func() {
|
||||
WriteResponse(w, resp)
|
||||
}()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
savesList, err := factorio.ListSaves(config.FactorioSavesDir)
|
||||
latestParam := r.URL.Query().Get("latest")
|
||||
|
||||
var withLatest bool
|
||||
|
||||
if latestParam != "" {
|
||||
var err error
|
||||
withLatest, err = strconv.ParseBool(latestParam)
|
||||
if err != nil {
|
||||
resp = fmt.Sprintf("Error parsing latestParam: %s", err)
|
||||
log.Println(resp)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
savesList, err := factorio.ListSaves()
|
||||
if err != nil {
|
||||
resp = fmt.Sprintf("Error listing save files: %s", err)
|
||||
log.Println(resp)
|
||||
@@ -99,8 +114,19 @@ func ListSaves(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
loadLatest := factorio.Save{Name: "Load Latest"}
|
||||
savesList = append(savesList, loadLatest)
|
||||
// get actual latest and add name
|
||||
// but only if requested
|
||||
if withLatest && len(savesList) != 0 {
|
||||
latestSave, err := factorio.GetLatestSave()
|
||||
if err != nil {
|
||||
resp = fmt.Sprintf("Error getting latest save: %s", err)
|
||||
log.Println(resp)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
latestSave.Name = fmt.Sprintf("Load Latest (%s)", latestSave.Name)
|
||||
savesList = append(savesList, latestSave)
|
||||
}
|
||||
|
||||
resp = savesList
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Username string `json:"username",gorm:"uniqueIndex,not null"`
|
||||
Password string `json:"password",gorm:"not null"`
|
||||
Role string `json:"role",gorm:"not null"`
|
||||
Username string `json:"username" gorm:"uniqueIndex,not null"`
|
||||
Password string `json:"password" gorm:"not null"`
|
||||
Role string `json:"role" gorm:"not null"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ func readString(r io.Reader, game Version, forceOptimized bool) (s string, err e
|
||||
return string(d), nil
|
||||
}
|
||||
|
||||
func (h SaveHeader) readStats(r io.Reader) (stats map[byte][]map[uint16]uint32, err error) {
|
||||
func (h *SaveHeader) readStats(r io.Reader) (stats map[byte][]map[uint16]uint32, err error) {
|
||||
var scratch [4]byte
|
||||
stats = make(map[byte][]map[uint16]uint32)
|
||||
|
||||
|
||||
+26
-5
@@ -18,14 +18,15 @@ type Save struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (s Save) String() string {
|
||||
func (s *Save) String() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
// Lists save files in factorio/saves
|
||||
func ListSaves(saveDir string) (saves []Save, err error) {
|
||||
func ListSaves() (saves []Save, err error) {
|
||||
config := bootstrap.GetConfig()
|
||||
saves = []Save{}
|
||||
err = filepath.Walk(saveDir, func(path string, info os.FileInfo, err error) error {
|
||||
err = filepath.Walk(config.FactorioSavesDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil || (info.IsDir() && info.Name() == "saves") {
|
||||
return nil
|
||||
}
|
||||
@@ -40,8 +41,7 @@ func ListSaves(saveDir string) (saves []Save, err error) {
|
||||
}
|
||||
|
||||
func FindSave(name string) (*Save, error) {
|
||||
config := bootstrap.GetConfig()
|
||||
saves, err := ListSaves(config.FactorioSavesDir)
|
||||
saves, err := ListSaves()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing saves: %v", err)
|
||||
}
|
||||
@@ -84,3 +84,24 @@ func CreateSave(filePath string) (string, error) {
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetLatestSave() (save Save, err error) {
|
||||
config := bootstrap.GetConfig()
|
||||
|
||||
err = filepath.Walk(config.FactorioSavesDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil || (info.IsDir() && info.Name() == "saves") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if save.LastMod.Before(info.ModTime()) {
|
||||
save = Save{
|
||||
Name: info.Name(),
|
||||
LastMod: info.ModTime(),
|
||||
Size: info.Size(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ func (server *Server) Run() error {
|
||||
ioutil.WriteFile(config.SettingsFile, data, 0644)
|
||||
}
|
||||
|
||||
saves, err := ListSaves(config.FactorioSavesDir)
|
||||
saves, err := ListSaves()
|
||||
if err != nil {
|
||||
log.Println("Failed to get saves list: ", err)
|
||||
}
|
||||
@@ -260,12 +260,12 @@ func (server *Server) Run() error {
|
||||
args = append(args, "--server-adminlist", config.FactorioAdminFile)
|
||||
}
|
||||
|
||||
if server.Savefile == "Load Latest" {
|
||||
if strings.HasPrefix(server.Savefile, "Load Latest") {
|
||||
args = append(args, "--start-server-load-latest")
|
||||
} else {
|
||||
args = append(args, "--start-server", filepath.Join(config.FactorioSavesDir, server.Savefile))
|
||||
}
|
||||
|
||||
|
||||
// Write chat log to a different file if requested (if not it will be mixed-in with the default logfile)
|
||||
if config.ChatLogFile != "" {
|
||||
args = append(args, "--console-log", config.ChatLogFile)
|
||||
@@ -278,7 +278,7 @@ func (server *Server) Run() error {
|
||||
log.Println("Starting server with command: ", config.FactorioBinary, args)
|
||||
server.Cmd = exec.Command(config.FactorioBinary, args...)
|
||||
}
|
||||
|
||||
|
||||
server.StdOut, err = server.Cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Printf("Error opening stdout pipe: %s", err)
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import React, {useState} from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
const Select = ({register, options, className = "", defaultValue = ""}) => {
|
||||
const Select = ({register, options, className = "", defaultValue = "", disabled = undefined}) => {
|
||||
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (value === "") {
|
||||
setValue(defaultValue)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={`${className} relative`}>
|
||||
<select
|
||||
className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
{...register}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={optionElement => setValue(optionElement.target.value)}
|
||||
>
|
||||
{options.map(option => <option value={option.value} key={option.value}>{option.name}</option>)}
|
||||
|
||||
@@ -12,17 +12,14 @@ const Controls = ({serverStatus}) => {
|
||||
|
||||
const factorioVersion = serverStatus.fac_version ? serverStatus.fac_version : 'Unknown';
|
||||
const [saves, setSaves] = useState([]);
|
||||
const [isDisabled, setIsDisabled] = useState(true);
|
||||
const [isStopping, setIsStopping] = useState(false);
|
||||
const [isStarting, setIsStarting] = useState(false);
|
||||
const [isKilling, setIsKilling] = useState(false);
|
||||
|
||||
const { handleSubmit, register, formState: {errors} } = useForm();
|
||||
const { handleSubmit, reset, register, formState: {errors} } = useForm();
|
||||
|
||||
const startServer = async (data) => {
|
||||
if(saves.length === 1 && saves[0].name === "Load Latest") {
|
||||
window.flash("Save must be created before starting server", "red");
|
||||
return;
|
||||
}
|
||||
setIsStarting(true);
|
||||
await server.start(data.ip, parseInt(data.port), data.save);
|
||||
}
|
||||
@@ -38,8 +35,14 @@ const Controls = ({serverStatus}) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
savesResource.list()
|
||||
.then(res => setSaves(res));
|
||||
savesResource.list(true)
|
||||
.then(res => {
|
||||
setSaves(res);
|
||||
if (res.length > 0) {
|
||||
setIsDisabled(undefined);
|
||||
}
|
||||
reset();
|
||||
});
|
||||
}, [])
|
||||
|
||||
return (
|
||||
@@ -80,6 +83,7 @@ const Controls = ({serverStatus}) => {
|
||||
<div className="font-bold">IP</div>
|
||||
<Input
|
||||
defaultValue={"0.0.0.0"}
|
||||
disabled={isDisabled}
|
||||
register={register('ip',{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]?)$'})}
|
||||
/>
|
||||
<Error error={errors.ip} message="IP is required and must be valid."/>
|
||||
@@ -90,6 +94,7 @@ const Controls = ({serverStatus}) => {
|
||||
type="number"
|
||||
min={1}
|
||||
defaultValue={"34197"}
|
||||
disabled={isDisabled}
|
||||
register={register('port',{required: true})}
|
||||
/>
|
||||
<Error error={errors.port} message="Port is required"/>
|
||||
@@ -103,12 +108,14 @@ const Controls = ({serverStatus}) => {
|
||||
<div className="relative">
|
||||
<Select
|
||||
register={register('save',{required: true})}
|
||||
defaultValue="Load Latest"
|
||||
defaultValue={saves.find((save) => save.name.startsWith('Load Latest'))?.name}
|
||||
disabled={isDisabled}
|
||||
options={saves.map(save => new Object({
|
||||
value: save.name,
|
||||
name: save.name
|
||||
}))}
|
||||
/>
|
||||
<Error error={errors.save} message="Save is required and must be valid."/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -122,7 +129,7 @@ const Controls = ({serverStatus}) => {
|
||||
<Button onClick={stopServer} isLoading={isStopping} isDisabled={isKilling} 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} isLoading={isKilling} isDisabled={isStopping} size="sm" type="danger" className="w-full md:w-auto">Kill Server</Button>
|
||||
</>
|
||||
: <Button isSubmit={true} isLoading={isStarting} size="sm" type="success" className="w-full md:w-auto">Start Server</Button>
|
||||
: <Button isSubmit={true} isDisabled={isDisabled} isLoading={isStarting} size="sm" type="success" className="w-full md:w-auto">Start Server</Button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -11,10 +11,15 @@ const LoadMods = ({refreshMods}) => {
|
||||
const [saves, setSaves] = useState([]);
|
||||
const {register, reset, handleSubmit} = useForm();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isDisabled, setIsDisabled] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setSaves(await savesResource.list());
|
||||
const s = await savesResource.list()
|
||||
setSaves(s);
|
||||
if (s.length > 0) {
|
||||
setIsDisabled(false);
|
||||
}
|
||||
reset();
|
||||
})();
|
||||
}, []);
|
||||
@@ -40,12 +45,13 @@ const LoadMods = ({refreshMods}) => {
|
||||
<Select
|
||||
register={register('save')}
|
||||
className="mb-4"
|
||||
disabled={isDisabled}
|
||||
options={saves?.map(save => new Object({
|
||||
name: save.name,
|
||||
value: save.name
|
||||
}))}
|
||||
/>
|
||||
<Button isSubmit={true} isLoading={isLoading}>Load</Button>
|
||||
<Button isSubmit={true} isDisabled={isDisabled} isLoading={isLoading}>Load</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,15 +74,13 @@ const Saves = ({serverStatus}) => {
|
||||
<td className="pr-4">{(new Date(save.last_mod)).toLocaleString()}</td>
|
||||
<td className="pr-4">{parseFloat(save.size / 1024 / 1024).toFixed(3)} MB</td>
|
||||
<td>
|
||||
{ save.name !== 'Load Latest' && <>
|
||||
<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}/>
|
||||
</>}
|
||||
<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>
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import client from "../client";
|
||||
|
||||
export default {
|
||||
list: async () => {
|
||||
const response = await client.get('/api/saves/list');
|
||||
list: async (latest) => {
|
||||
const response = await client.get('/api/saves/list', {
|
||||
params: {
|
||||
latest
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
delete: async (save) => {
|
||||
|
||||
Reference in New Issue
Block a user