mirror of
https://github.com/OpenFactorioServerManager/factorio-server-manager.git
synced 2024-12-29 02:57:24 +02:00
added handlers and views for uploading and removing mods and saves
This commit is contained in:
parent
e0ac8fdbec
commit
4ac1fc0568
264
app/bundle.js
264
app/bundle.js
@ -26093,6 +26093,7 @@
|
||||
|
||||
_this.componentDidMount = _this.componentDidMount.bind(_this);
|
||||
_this.toggleMod = _this.toggleMod.bind(_this);
|
||||
_this.loadInstalledModList = _this.loadInstalledModList.bind(_this);
|
||||
_this.state = {
|
||||
installedMods: [],
|
||||
listMods: []
|
||||
@ -26196,7 +26197,9 @@
|
||||
_react2.default.createElement(
|
||||
'section',
|
||||
{ className: 'content', style: { height: "100%" } },
|
||||
_react2.default.createElement(_InstalledMods2.default, this.state),
|
||||
_react2.default.createElement(_InstalledMods2.default, _extends({}, this.state, {
|
||||
loadInstalledModList: this.loadInstalledModList
|
||||
})),
|
||||
_react2.default.createElement(_ListMods2.default, _extends({}, this.state, {
|
||||
toggleMod: this.toggleMod
|
||||
}))
|
||||
@ -26250,11 +26253,6 @@
|
||||
}
|
||||
|
||||
_createClass(ModList, [{
|
||||
key: 'componentDidMount',
|
||||
value: function componentDidMount() {
|
||||
console.log(this.props.listMods);
|
||||
}
|
||||
}, {
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var _this2 = this;
|
||||
@ -26274,6 +26272,11 @@
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'box-body' },
|
||||
_react2.default.createElement(
|
||||
'h4',
|
||||
null,
|
||||
'Upload Mod'
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'table-responsive' },
|
||||
@ -26433,7 +26436,7 @@
|
||||
/* 233 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
@ -26463,30 +26466,150 @@
|
||||
}
|
||||
|
||||
_createClass(InstalledMods, [{
|
||||
key: "render",
|
||||
key: 'componentDidMount',
|
||||
value: function componentDidMount() {
|
||||
this.uploadFile = this.uploadFile.bind(this);
|
||||
this.removeMod = this.removeMod.bind(this);
|
||||
}
|
||||
}, {
|
||||
key: 'updateInstalledMods',
|
||||
value: function updateInstalledMods() {
|
||||
this.props.loadInstalledModList();
|
||||
}
|
||||
}, {
|
||||
key: 'uploadFile',
|
||||
value: function uploadFile(e) {
|
||||
var fd = new FormData();
|
||||
fd.append('modfile', this.refs.file.files[0]);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/upload",
|
||||
type: "POST",
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function success(data) {
|
||||
alert(data);
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
this.updateInstalledMods();
|
||||
}
|
||||
}, {
|
||||
key: 'removeMod',
|
||||
value: function removeMod(i) {
|
||||
$.ajax({
|
||||
url: "/api/mods/rm/" + this.props.installedMods[i],
|
||||
success: function success(data) {
|
||||
alert(data);
|
||||
}
|
||||
});
|
||||
this.updateInstalledMods();
|
||||
}
|
||||
}, {
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var _this2 = this;
|
||||
|
||||
return _react2.default.createElement(
|
||||
"div",
|
||||
{ className: "box" },
|
||||
'div',
|
||||
{ className: 'box' },
|
||||
_react2.default.createElement(
|
||||
"div",
|
||||
{ className: "box-header" },
|
||||
'div',
|
||||
{ className: 'box-header' },
|
||||
_react2.default.createElement(
|
||||
"h3",
|
||||
{ className: "box-title" },
|
||||
"Installed Mods"
|
||||
'h3',
|
||||
{ className: 'box-title' },
|
||||
'Installed Mods'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
"div",
|
||||
{ className: "box-body" },
|
||||
this.props.installedMods.map(function (mod, i) {
|
||||
return _react2.default.createElement(
|
||||
"p",
|
||||
'div',
|
||||
{ className: 'box-body' },
|
||||
_react2.default.createElement(
|
||||
'form',
|
||||
{ ref: 'uploadForm', className: 'form', encType: 'multipart/form-data' },
|
||||
_react2.default.createElement(
|
||||
'fieldset',
|
||||
null,
|
||||
mod
|
||||
);
|
||||
})
|
||||
_react2.default.createElement(
|
||||
'span',
|
||||
{ className: 'btn btn-default btn-file' },
|
||||
_react2.default.createElement('input', { ref: 'file', type: 'file', name: 'modfile', id: 'modfile' })
|
||||
),
|
||||
_react2.default.createElement('input', { type: 'button', ref: 'button', value: 'Upload', onClick: this.uploadFile })
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'table-responsive' },
|
||||
_react2.default.createElement(
|
||||
'table',
|
||||
{ className: 'table table-striped' },
|
||||
_react2.default.createElement(
|
||||
'thead',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'tr',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'th',
|
||||
null,
|
||||
'Mod Name'
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'th',
|
||||
null,
|
||||
'Download'
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'th',
|
||||
null,
|
||||
'Delete'
|
||||
)
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'tbody',
|
||||
null,
|
||||
this.props.installedMods.map(function (mod, i) {
|
||||
var saveLocation = "/api/mods/dl/" + mod;
|
||||
return _react2.default.createElement(
|
||||
'tr',
|
||||
{ key: i },
|
||||
_react2.default.createElement(
|
||||
'td',
|
||||
null,
|
||||
mod
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'td',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'a',
|
||||
{ className: 'btn btn-default', href: saveLocation },
|
||||
'Download'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'td',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'button',
|
||||
{
|
||||
className: 'btn btn-danger btn-small',
|
||||
ref: 'modInput',
|
||||
type: 'button',
|
||||
onClick: _this2.removeMod.bind(_this2, i) },
|
||||
_react2.default.createElement('i', { className: 'fa fa-trash' }),
|
||||
' Delete'
|
||||
)
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -26496,7 +26619,8 @@
|
||||
}(_react2.default.Component);
|
||||
|
||||
InstalledMods.propTypes = {
|
||||
installedMods: _react2.default.PropTypes.array.isRequired
|
||||
installedMods: _react2.default.PropTypes.array.isRequired,
|
||||
loadInstalledModList: _react2.default.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
exports.default = InstalledMods;
|
||||
@ -26751,6 +26875,7 @@
|
||||
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(SavesContent).call(this, props));
|
||||
|
||||
_this.dlSave = _this.dlSave.bind(_this);
|
||||
_this.getSaves = _this.getSaves.bind(_this);
|
||||
_this.state = {
|
||||
saves: []
|
||||
};
|
||||
@ -26835,7 +26960,8 @@
|
||||
'section',
|
||||
{ className: 'content' },
|
||||
_react2.default.createElement(_SavesList2.default, _extends({}, this.state, {
|
||||
dlSave: this.dlSave
|
||||
dlSave: this.dlSave,
|
||||
getSaves: this.getSaves
|
||||
}))
|
||||
)
|
||||
);
|
||||
@ -26878,15 +27004,58 @@
|
||||
var SavesList = function (_React$Component) {
|
||||
_inherits(SavesList, _React$Component);
|
||||
|
||||
function SavesList() {
|
||||
function SavesList(props) {
|
||||
_classCallCheck(this, SavesList);
|
||||
|
||||
return _possibleConstructorReturn(this, Object.getPrototypeOf(SavesList).apply(this, arguments));
|
||||
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(SavesList).call(this, props));
|
||||
|
||||
_this.updateSavesList = _this.updateSavesList.bind(_this);
|
||||
_this.uploadFile = _this.uploadFile.bind(_this);
|
||||
_this.removeSave = _this.removeSave.bind(_this);
|
||||
return _this;
|
||||
}
|
||||
|
||||
_createClass(SavesList, [{
|
||||
key: 'updateSavesList',
|
||||
value: function updateSavesList() {
|
||||
this.props.getSaves();
|
||||
}
|
||||
}, {
|
||||
key: 'uploadFile',
|
||||
value: function uploadFile(e) {
|
||||
var fd = new FormData();
|
||||
fd.append('savefile', this.refs.file.files[0]);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/saves/upload",
|
||||
type: "POST",
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function success(data) {
|
||||
alert(data);
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
this.updateSavesList();
|
||||
}
|
||||
}, {
|
||||
key: 'removeSave',
|
||||
value: function removeSave(saveName, e) {
|
||||
console.log(e, saveName);
|
||||
$.ajax({
|
||||
url: "/api/saves/rm/" + saveName,
|
||||
success: function success(data) {
|
||||
alert(data);
|
||||
}
|
||||
});
|
||||
this.updateSavesList();
|
||||
}
|
||||
}, {
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var _this2 = this;
|
||||
|
||||
return _react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'box' },
|
||||
@ -26902,6 +27071,20 @@
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'box-body' },
|
||||
_react2.default.createElement(
|
||||
'form',
|
||||
{ ref: 'uploadForm', className: 'form', encType: 'multipart/form-data' },
|
||||
_react2.default.createElement(
|
||||
'fieldset',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'span',
|
||||
{ className: 'btn btn-default btn-file' },
|
||||
_react2.default.createElement('input', { ref: 'file', type: 'file', name: 'modfile', id: 'modfile' })
|
||||
),
|
||||
_react2.default.createElement('input', { type: 'button', ref: 'button', value: 'Upload', onClick: this.uploadFile })
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'table-responsive' },
|
||||
@ -26942,7 +27125,10 @@
|
||||
this.props.saves.map(function (save, i) {
|
||||
return _react2.default.createElement(_Save2.default, {
|
||||
key: i,
|
||||
save: save
|
||||
saves: _this2.props.saves,
|
||||
index: i,
|
||||
save: save,
|
||||
removeSave: _this2.removeSave
|
||||
});
|
||||
})
|
||||
)
|
||||
@ -26958,7 +27144,8 @@
|
||||
|
||||
SavesList.propTypes = {
|
||||
saves: _react2.default.PropTypes.array.isRequired,
|
||||
dlSave: _react2.default.PropTypes.func.isRequired
|
||||
dlSave: _react2.default.PropTypes.func.isRequired,
|
||||
getSaves: _react2.default.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
exports.default = SavesList;
|
||||
@ -27032,6 +27219,20 @@
|
||||
{ className: 'btn btn-default', href: saveLocation },
|
||||
'Download'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'td',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
'button',
|
||||
{
|
||||
className: 'btn btn-danger btn-small',
|
||||
ref: 'saveInput',
|
||||
type: 'button',
|
||||
onClick: this.props.removeSave.bind(this, this.props.saves[this.props.index].name) },
|
||||
_react2.default.createElement('i', { className: 'fa fa-trash' }),
|
||||
' Delete'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -27041,7 +27242,10 @@
|
||||
}(_react2.default.Component);
|
||||
|
||||
Save.propTypes = {
|
||||
save: _react2.default.PropTypes.object.isRequired
|
||||
save: _react2.default.PropTypes.object.isRequired,
|
||||
saves: _react2.default.PropTypes.array.isRequired,
|
||||
index: _react2.default.PropTypes.number.isRequired,
|
||||
removeSave: _react2.default.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
exports.default = Save;
|
||||
|
190
handlers.go
190
handlers.go
@ -3,8 +3,10 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@ -17,7 +19,13 @@ func Index(w http.ResponseWriter, r *http.Request) {
|
||||
func ListInstalledMods(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
mods := listInstalledMods()
|
||||
modDir := config.FactorioDir + "/mods"
|
||||
|
||||
mods, err := listInstalledMods(modDir)
|
||||
if err != nil {
|
||||
log.Printf("Error in ListInstalledMods handler: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(mods); err != nil {
|
||||
log.Printf("Error in list mods: %s", err)
|
||||
@ -61,20 +69,185 @@ func ListMods(w http.ResponseWriter, r *http.Request) {
|
||||
if err := json.NewEncoder(w).Encode(m); err != nil {
|
||||
log.Printf("Error listing mods: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Uploads mod to the mods directory
|
||||
func UploadMod(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
resp := "Unsupported method"
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error listing mods: %s", err)
|
||||
}
|
||||
case "POST":
|
||||
log.Println("Uploading file")
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
file, header, err := r.FormFile("modfile")
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
log.Printf("%+v", file)
|
||||
log.Printf("%+v", header)
|
||||
log.Printf("Error in formfile")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
out, err := os.Create(config.FactorioModsDir + "/" + header.Filename)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
log.Printf("Error in out")
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, file)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
log.Printf("Error in io copy")
|
||||
return
|
||||
}
|
||||
log.Printf("Uploaded mod file: %s", header.Filename)
|
||||
resp := "File '" + header.Filename + "' submitted successfully"
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveMod(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
modName := vars["mod"]
|
||||
|
||||
err := rmMod(modName)
|
||||
if err == nil {
|
||||
// No error returned means mod was removed
|
||||
resp := fmt.Sprintf("Removed mod: %s", modName)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error removing mod: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Error in remove mod handler: %s", err)
|
||||
resp := fmt.Sprintf("Error in remove mod handler: %s", err)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error removing mod: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func DownloadMod(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
mod := vars["mod"]
|
||||
modFile := config.FactorioModsDir + "/" + mod
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", mod))
|
||||
log.Printf("%s downloading: %s", r.Host, modFile)
|
||||
|
||||
http.ServeFile(w, r, modFile)
|
||||
}
|
||||
|
||||
// Lists all save files in the factorio/saves directory
|
||||
func ListSaves(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
saves := listSaves()
|
||||
saveDir := config.FactorioDir + "/saves"
|
||||
|
||||
saves, err := listSaves(saveDir)
|
||||
if err != nil {
|
||||
log.Printf("Error in ListSaves handler: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(saves); err != nil {
|
||||
log.Printf("Error listing saves: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func DLSave(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
save := vars["save"]
|
||||
saveName := config.FactorioSavesDir + "/" + save
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", save))
|
||||
log.Printf("%s downloading: %s", r.Host, saveName)
|
||||
|
||||
http.ServeFile(w, r, saveName)
|
||||
}
|
||||
|
||||
func UploadSave(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
resp := "Unsupported method"
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error listing mods: %s", err)
|
||||
}
|
||||
case "POST":
|
||||
log.Println("Uploading save file")
|
||||
r.ParseMultipartForm(32 << 20)
|
||||
file, header, err := r.FormFile("savefile")
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
log.Printf("%+v", file)
|
||||
log.Printf("%+v", header)
|
||||
log.Printf("Error in formfile")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
out, err := os.Create(config.FactorioSavesDir + "/" + header.Filename)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
log.Printf("Error in out: %s", err)
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, file)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
log.Printf("Error in io copy: %s", err)
|
||||
return
|
||||
}
|
||||
log.Printf("Uploaded save file: %s", header.Filename)
|
||||
resp := "File '" + header.Filename + "' uploaded successfully"
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveSave(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
saveName := vars["save"]
|
||||
|
||||
err := rmSave(saveName)
|
||||
if err == nil {
|
||||
// No error returned means save was removed
|
||||
resp := fmt.Sprintf("Removed save: %s", saveName)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error removing save %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Error in remove save handler: %s", err)
|
||||
resp := fmt.Sprintf("Error in remove save handler: %s", err)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error removing save: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Returns last lines of the factorio-current.log file
|
||||
func LogTail(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
@ -89,16 +262,3 @@ func LogTail(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Error tailing logfile", err)
|
||||
}
|
||||
}
|
||||
|
||||
func DLSave(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
save := vars["save"]
|
||||
saveName := config.FactorioSavesDir + "/" + save
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", save))
|
||||
log.Printf("%s downloading: %s", r.Host, saveName)
|
||||
|
||||
http.ServeFile(w, r, saveName)
|
||||
}
|
||||
|
10
main.go
10
main.go
@ -9,9 +9,11 @@ import (
|
||||
type Config struct {
|
||||
FactorioDir string
|
||||
FactorioSavesDir string
|
||||
FactorioModsDir string
|
||||
FactorioLog string
|
||||
ServerIP string
|
||||
ServerPort string
|
||||
FactorioLog string
|
||||
MaxUploadSize int64
|
||||
}
|
||||
|
||||
var config Config
|
||||
@ -19,14 +21,18 @@ var config Config
|
||||
func loadFlags() {
|
||||
factorioDir := flag.String("dir", "./", "Specify location of Factorio config directory.")
|
||||
factorioIP := flag.String("host", "0.0.0.0", "Specify IP for webserver to listen on.")
|
||||
factorioPort := flag.String("port", "8080", "Specify a port for the server")
|
||||
factorioPort := flag.String("port", "8080", "Specify a port for the server.")
|
||||
factorioMaxUpload := flag.Int64("max-upload", 100000, "Maximum filesize for uploaded files.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
config.FactorioDir = *factorioDir
|
||||
config.FactorioSavesDir = config.FactorioDir + "/saves"
|
||||
config.FactorioModsDir = config.FactorioDir + "/mods"
|
||||
config.ServerIP = *factorioIP
|
||||
config.ServerPort = *factorioPort
|
||||
config.FactorioLog = config.FactorioDir + "/factorio-current.log"
|
||||
config.MaxUploadSize = *factorioMaxUpload
|
||||
log.Printf(config.FactorioSavesDir)
|
||||
}
|
||||
|
||||
|
43
mods.go
43
mods.go
@ -2,8 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ModList struct {
|
||||
@ -16,14 +20,13 @@ type Mod struct {
|
||||
}
|
||||
|
||||
// List mods installed in the factorio/mods directory
|
||||
func listInstalledMods() []string {
|
||||
modDir := config.FactorioDir + "/mods"
|
||||
func listInstalledMods(modDir string) ([]string, error) {
|
||||
result := []string{}
|
||||
|
||||
files, err := ioutil.ReadDir(modDir)
|
||||
if err != nil {
|
||||
log.Printf("Error listing installed mods")
|
||||
return result
|
||||
return result, err
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Name() == "mod-list.json" {
|
||||
@ -32,7 +35,39 @@ func listInstalledMods() []string {
|
||||
result = append(result, f.Name())
|
||||
}
|
||||
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func rmMod(modName string) error {
|
||||
removed := false
|
||||
if modName == "" {
|
||||
return errors.New("No mod name provided.")
|
||||
}
|
||||
installedMods, err := listInstalledMods(config.FactorioModsDir)
|
||||
if err != nil {
|
||||
log.Printf("Error in remove mod list: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mod := range installedMods {
|
||||
log.Printf("Checking if %s in %s", mod, modName)
|
||||
if strings.Contains(mod, modName) {
|
||||
err := os.Remove(config.FactorioModsDir + "/" + mod)
|
||||
if err != nil {
|
||||
log.Printf("Error removing mod %s: %s", mod, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Removed mod: %s", mod)
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !removed {
|
||||
log.Printf("Did not remove mod: %s", modName)
|
||||
return errors.New(fmt.Sprintf("Did not remove mod: %s", modName))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parses mod-list.json file in factorio/mods
|
||||
|
38
routes.go
38
routes.go
@ -30,6 +30,19 @@ func NewRouter() *mux.Router {
|
||||
|
||||
// Serves the frontend application from the app directory
|
||||
// Uses basic file server to serve index.html and Javascript application
|
||||
// Routes match the ones defined in React application
|
||||
r.Path("/mods").
|
||||
Methods("GET").
|
||||
Name("Mods").
|
||||
Handler(http.StripPrefix("/mods", http.FileServer(http.Dir("./app/"))))
|
||||
r.Path("/saves").
|
||||
Methods("GET").
|
||||
Name("Saves").
|
||||
Handler(http.StripPrefix("/saves", http.FileServer(http.Dir("./app/"))))
|
||||
r.Path("/logs").
|
||||
Methods("GET").
|
||||
Name("Logs").
|
||||
Handler(http.StripPrefix("/logs", http.FileServer(http.Dir("./app/"))))
|
||||
r.PathPrefix("/").
|
||||
Methods("GET").
|
||||
Name("Index").
|
||||
@ -56,6 +69,21 @@ var apiRoutes = Routes{
|
||||
"GET",
|
||||
"/mods/toggle/{mod}",
|
||||
ToggleMod,
|
||||
}, {
|
||||
"UploadMod",
|
||||
"POST",
|
||||
"/mods/upload",
|
||||
UploadMod,
|
||||
}, {
|
||||
"RemoveMod",
|
||||
"GET",
|
||||
"/mods/rm/{mod}",
|
||||
RemoveMod,
|
||||
}, {
|
||||
"DownloadMod",
|
||||
"GET",
|
||||
"/mods/dl/{mod}",
|
||||
DownloadMod,
|
||||
}, {
|
||||
"ListSaves",
|
||||
"GET",
|
||||
@ -66,6 +94,16 @@ var apiRoutes = Routes{
|
||||
"GET",
|
||||
"/saves/dl/{save}",
|
||||
DLSave,
|
||||
}, {
|
||||
"UploadSave",
|
||||
"POST",
|
||||
"/saves/upload",
|
||||
UploadSave,
|
||||
}, {
|
||||
"RemoveSave",
|
||||
"GET",
|
||||
"/saves/rm/{save}",
|
||||
RemoveSave,
|
||||
}, {
|
||||
"LogTail",
|
||||
"GET",
|
||||
|
48
saves.go
48
saves.go
@ -1,8 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -12,15 +16,18 @@ type Save struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (s Save) String() string {
|
||||
return fmt.Sprintf("%s", s.Name)
|
||||
}
|
||||
|
||||
// Lists save files in factorio/saves
|
||||
func listSaves() []Save {
|
||||
saveDir := config.FactorioDir + "/saves"
|
||||
func listSaves(saveDir string) ([]Save, error) {
|
||||
result := []Save{}
|
||||
|
||||
files, err := ioutil.ReadDir(saveDir)
|
||||
if err != nil {
|
||||
log.Printf("Error listing save directory: %s", err)
|
||||
return result
|
||||
return result, err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
@ -28,5 +35,38 @@ func listSaves() []Save {
|
||||
result = append(result, save)
|
||||
}
|
||||
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func rmSave(saveName string) error {
|
||||
removed := false
|
||||
if saveName == "" {
|
||||
return errors.New("No save name provided")
|
||||
}
|
||||
|
||||
saves, err := listSaves(config.FactorioSavesDir)
|
||||
if err != nil {
|
||||
log.Printf("Error in remove save: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, save := range saves {
|
||||
log.Printf("Checking if %s in %s", save, saveName)
|
||||
if strings.Contains(save.Name, saveName) {
|
||||
err := os.Remove(config.FactorioSavesDir + "/" + save.Name)
|
||||
if err != nil {
|
||||
log.Printf("Error removing save %s: %s", saveName, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Deleted save: %s", save)
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !removed {
|
||||
log.Printf("Did not remove save: %s", saveName)
|
||||
return errors.New(fmt.Sprintf("Did not remove save: %s", saveName))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,7 +1,45 @@
|
||||
import React from 'react';
|
||||
|
||||
class InstalledMods extends React.Component {
|
||||
componentDidMount() {
|
||||
this.uploadFile = this.uploadFile.bind(this);
|
||||
this.removeMod = this.removeMod.bind(this);
|
||||
}
|
||||
|
||||
updateInstalledMods() {
|
||||
this.props.loadInstalledModList();
|
||||
}
|
||||
|
||||
uploadFile(e) {
|
||||
var fd = new FormData();
|
||||
fd.append('modfile', this.refs.file.files[0]);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/upload",
|
||||
type: "POST",
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: (data) => {
|
||||
alert(data)
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
this.updateInstalledMods();
|
||||
}
|
||||
|
||||
removeMod(i) {
|
||||
$.ajax({
|
||||
url: "/api/mods/rm/" + this.props.installedMods[i],
|
||||
success: (data) => {
|
||||
alert(data)
|
||||
}
|
||||
});
|
||||
this.updateInstalledMods();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
@ -9,11 +47,53 @@ class InstalledMods extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
{this.props.installedMods.map ( (mod, i) => {
|
||||
return(
|
||||
<p>{mod}</p>
|
||||
)
|
||||
})}
|
||||
<form ref="uploadForm" className="form" encType='multipart/form-data'>
|
||||
<fieldset>
|
||||
<span className="btn btn-default btn-file">
|
||||
<input ref="file" type="file" name="modfile" id="modfile" />
|
||||
</span>
|
||||
|
||||
<input type="button" ref="button" value="Upload" onClick={this.uploadFile} />
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mod Name</th>
|
||||
<th>Download</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.installedMods.map ( (mod, i) => {
|
||||
var saveLocation = "/api/mods/dl/" + mod;
|
||||
return(
|
||||
<tr key={i}>
|
||||
<td>
|
||||
{mod}
|
||||
</td>
|
||||
<td>
|
||||
<a className="btn btn-default" href={saveLocation}>Download</a>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-danger btn-small"
|
||||
ref="modInput"
|
||||
type="button"
|
||||
onClick={this.removeMod.bind(this, i)}>
|
||||
<i className="fa fa-trash"></i>
|
||||
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -21,7 +101,8 @@ class InstalledMods extends React.Component {
|
||||
}
|
||||
|
||||
InstalledMods.propTypes = {
|
||||
installedMods: React.PropTypes.array.isRequired
|
||||
installedMods: React.PropTypes.array.isRequired,
|
||||
loadInstalledModList: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default InstalledMods
|
||||
|
@ -2,10 +2,6 @@ import React from 'react';
|
||||
import Mod from './Mod.jsx'
|
||||
|
||||
class ModList extends React.Component {
|
||||
componentDidMount() {
|
||||
console.log(this.props.listMods);
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="box">
|
||||
@ -14,6 +10,7 @@ class ModList extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<h4>Upload Mod</h4>
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
|
@ -7,6 +7,7 @@ class ModsContent extends React.Component {
|
||||
super(props);
|
||||
this.componentDidMount = this.componentDidMount.bind(this);
|
||||
this.toggleMod = this.toggleMod.bind(this);
|
||||
this.loadInstalledModList = this.loadInstalledModList.bind(this);
|
||||
this.state = {
|
||||
installedMods: [],
|
||||
listMods: []
|
||||
@ -75,6 +76,7 @@ class ModsContent extends React.Component {
|
||||
|
||||
<InstalledMods
|
||||
{...this.state}
|
||||
loadInstalledModList={this.loadInstalledModList}
|
||||
/>
|
||||
<ModList
|
||||
{...this.state}
|
||||
|
@ -17,13 +17,27 @@ class Save extends React.Component {
|
||||
<td>
|
||||
<a className="btn btn-default" href={saveLocation}>Download</a>
|
||||
</td>
|
||||
</tr>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-danger btn-small"
|
||||
ref="saveInput"
|
||||
type="button"
|
||||
onClick={this.props.removeSave.bind(this, this.props.saves[this.props.index].name)}>
|
||||
<i className="fa fa-trash"></i>
|
||||
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Save.propTypes = {
|
||||
save: React.PropTypes.object.isRequired
|
||||
save: React.PropTypes.object.isRequired,
|
||||
saves: React.PropTypes.array.isRequired,
|
||||
index: React.PropTypes.number.isRequired,
|
||||
removeSave: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default Save
|
||||
|
@ -2,6 +2,46 @@ import React from 'react';
|
||||
import Save from './Save.jsx';
|
||||
|
||||
class SavesList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.updateSavesList = this.updateSavesList.bind(this);
|
||||
this.uploadFile = this.uploadFile.bind(this);
|
||||
this.removeSave = this.removeSave.bind(this);
|
||||
}
|
||||
|
||||
updateSavesList () {
|
||||
this.props.getSaves();
|
||||
}
|
||||
|
||||
uploadFile(e) {
|
||||
var fd = new FormData();
|
||||
fd.append('savefile', this.refs.file.files[0]);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/saves/upload",
|
||||
type: "POST",
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: (data) => {
|
||||
alert(data)
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
this.updateSavesList();
|
||||
}
|
||||
|
||||
removeSave(saveName, e) {
|
||||
console.log(e, saveName);
|
||||
$.ajax({
|
||||
url: "/api/saves/rm/" + saveName,
|
||||
success: (data) => {
|
||||
alert(data)
|
||||
}
|
||||
})
|
||||
this.updateSavesList();
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="box">
|
||||
@ -10,6 +50,16 @@ class SavesList extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<form ref="uploadForm" className="form" encType='multipart/form-data'>
|
||||
<fieldset>
|
||||
<span className="btn btn-default btn-file">
|
||||
<input ref="file" type="file" name="modfile" id="modfile" />
|
||||
</span>
|
||||
|
||||
<input type="button" ref="button" value="Upload" onClick={this.uploadFile} />
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
@ -25,7 +75,10 @@ class SavesList extends React.Component {
|
||||
return(
|
||||
<Save
|
||||
key={i}
|
||||
saves={this.props.saves}
|
||||
index={i}
|
||||
save={save}
|
||||
removeSave={this.removeSave}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
@ -40,7 +93,8 @@ class SavesList extends React.Component {
|
||||
|
||||
SavesList.propTypes = {
|
||||
saves: React.PropTypes.array.isRequired,
|
||||
dlSave: React.PropTypes.func.isRequired
|
||||
dlSave: React.PropTypes.func.isRequired,
|
||||
getSaves: React.PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default SavesList
|
||||
|
@ -5,6 +5,7 @@ class SavesContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.dlSave = this.dlSave.bind(this);
|
||||
this.getSaves = this.getSaves.bind(this);
|
||||
this.state = {
|
||||
saves: []
|
||||
}
|
||||
@ -59,6 +60,7 @@ class SavesContent extends React.Component {
|
||||
<SavesList
|
||||
{...this.state}
|
||||
dlSave={this.dlSave}
|
||||
getSaves={this.getSaves}
|
||||
/>
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user