- 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:
knox
2024-07-11 22:21:14 +02:00
parent 47dcfc9d62
commit f78a9a4005
10 changed files with 110 additions and 41 deletions
+30 -4
View File
@@ -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
}
+3 -3
View File
@@ -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"`
}
+1 -1
View File
@@ -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
View File
@@ -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
}
+4 -4
View File
@@ -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)
+9 -2
View File
@@ -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>)}
+16 -9
View File
@@ -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>
}
+8 -2
View File
@@ -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>
)
}
+7 -9
View File
@@ -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>
)}
+6 -2
View File
@@ -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) => {