added feature to start and stop server, added handlers and ui for starting factorio server

This commit is contained in:
majormjr 2016-04-25 20:38:51 -04:00
parent a19dbef3f6
commit 549d214958
13 changed files with 1034 additions and 98 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
node_modules/
factorio-server-manager

View File

@ -25499,18 +25499,61 @@
function App(props) {
_classCallCheck(this, App);
return _possibleConstructorReturn(this, Object.getPrototypeOf(App).call(this, props));
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(App).call(this, props));
_this.facServStatus = _this.facServStatus.bind(_this);
_this.getSaves = _this.getSaves.bind(_this);
_this.state = {
serverRunning: "stopped",
saves: []
};
return _this;
}
_createClass(App, [{
key: 'facServStatus',
value: function facServStatus() {
var _this2 = this;
$.ajax({
url: "/api/server/status",
dataType: "json",
success: function success(data) {
_this2.setState({ serverRunning: data.data.status });
}
});
}
}, {
key: 'getSaves',
value: function getSaves() {
var _this3 = this;
$.ajax({
url: "/api/saves/list",
dataType: "json",
success: function success(data) {
_this3.setState({ saves: data.data });
},
error: function error(xhr, status, err) {
console.log('api/mods/list', status, err.toString());
}
});
}
}, {
key: 'render',
value: function render() {
return _react2.default.createElement(
'div',
{ className: 'wrapper' },
_react2.default.createElement(_Header2.default, null),
_react2.default.createElement(_Sidebar2.default, null),
_react2.default.cloneElement(this.props.children, { message: "" }),
_react2.default.createElement(_Sidebar2.default, {
serverStatus: this.facServStatus,
serverRunning: this.state.serverRunning
}),
_react2.default.cloneElement(this.props.children, { message: "",
facServerStatus: this.facServStatus,
saves: this.state.saves,
getSaves: this.getSaves }),
_react2.default.createElement(_Footer2.default, null),
_react2.default.createElement(_HiddenSidebar2.default, null)
);
@ -25691,18 +25734,34 @@
var Sidebar = function (_React$Component) {
_inherits(Sidebar, _React$Component);
function Sidebar() {
function Sidebar(props) {
_classCallCheck(this, Sidebar);
return _possibleConstructorReturn(this, Object.getPrototypeOf(Sidebar).apply(this, arguments));
return _possibleConstructorReturn(this, Object.getPrototypeOf(Sidebar).call(this, props));
}
_createClass(Sidebar, [{
key: 'render',
value: function render() {
if (this.props.serverRunning === "running") {
var serverStatus = _react2.default.createElement(
_reactRouter.IndexLink,
{ to: '/' },
_react2.default.createElement('i', { className: 'fa fa-circle text-success' }),
'Server Online'
);
} else {
var serverStatus = _react2.default.createElement(
_reactRouter.IndexLink,
{ to: '/' },
_react2.default.createElement('i', { className: 'fa fa-circle text-danger' }),
'Server Offline'
);
}
return _react2.default.createElement(
'aside',
{ className: 'main-sidebar' },
{ className: 'main-sidebar', style: { height: "100%" } },
_react2.default.createElement(
'section',
{ className: 'sidebar' },
@ -25722,12 +25781,7 @@
null,
'Factorio Server Manager'
),
_react2.default.createElement(
'a',
{ href: '#' },
_react2.default.createElement('i', { className: 'fa fa-circle text-success' }),
'Server Online'
)
serverStatus
)
),
_react2.default.createElement(
@ -25756,6 +25810,20 @@
{ className: 'header' },
'MENU'
),
_react2.default.createElement(
'li',
null,
_react2.default.createElement(
_reactRouter.IndexLink,
{ to: '/', activeClassName: 'active' },
_react2.default.createElement('i', { className: 'fa fa-link' }),
_react2.default.createElement(
'span',
null,
'Server Control'
)
)
),
_react2.default.createElement(
'li',
null,
@ -25824,6 +25892,11 @@
return Sidebar;
}(_react2.default.Component);
Sidebar.propTypes = {
serverStatus: _react2.default.PropTypes.func.isRequired,
serverRunning: _react2.default.PropTypes.string.isRequired
};
exports.default = Sidebar;
/***/ },
@ -26907,33 +26980,13 @@
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: []
};
return _this;
}
_createClass(SavesContent, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.getSaves();
}
}, {
key: 'getSaves',
value: function getSaves() {
var _this2 = this;
$.ajax({
url: "/api/saves/list",
dataType: "json",
success: function success(data) {
_this2.setState({ saves: data.data });
},
error: function error(xhr, status, err) {
console.log('api/mods/list', status, err.toString());
}
});
this.props.getSaves();
}
}, {
key: 'dlSave',
@ -26998,20 +27051,21 @@
'div',
{ className: 'col-md-6' },
_react2.default.createElement(_CreateSave2.default, {
getSaves: this.getSaves
getSaves: this.props.getSaves
})
),
_react2.default.createElement(
'div',
{ className: 'col-md-6' },
_react2.default.createElement(_UploadSave2.default, {
getSaves: this.getSaves
getSaves: this.props.getSaves
})
)
),
_react2.default.createElement(_SavesList2.default, _extends({}, this.state, {
saves: this.props.saves,
dlSave: this.dlSave,
getSaves: this.getSaves
getSaves: this.props.getSaves
}))
)
);
@ -27760,6 +27814,110 @@
/***/ },
/* 243 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = __webpack_require__(1);
var _react2 = _interopRequireDefault(_react);
var _ServerCtl = __webpack_require__(244);
var _ServerCtl2 = _interopRequireDefault(_ServerCtl);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Index = function (_React$Component) {
_inherits(Index, _React$Component);
function Index(props) {
_classCallCheck(this, Index);
return _possibleConstructorReturn(this, Object.getPrototypeOf(Index).call(this, props));
}
_createClass(Index, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.props.facServerStatus();
this.props.getSaves();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.props.facServerStatus();
}
}, {
key: 'render',
value: function render() {
return _react2.default.createElement(
'div',
{ className: 'content-wrapper', style: { height: "100%" } },
_react2.default.createElement(
'section',
{ className: 'content-header' },
_react2.default.createElement(
'h1',
null,
'Index',
_react2.default.createElement(
'small',
null,
'Optional description'
)
),
_react2.default.createElement(
'ol',
{ className: 'breadcrumb' },
_react2.default.createElement(
'li',
null,
_react2.default.createElement(
'a',
{ href: '#' },
_react2.default.createElement('i', { className: 'fa fa-dashboard' }),
' Level'
)
),
_react2.default.createElement(
'li',
{ className: 'active' },
'Here'
)
)
),
_react2.default.createElement(
'section',
{ className: 'content' },
_react2.default.createElement(_ServerCtl2.default, {
saves: this.props.saves,
getSaves: this.props.getSaves
})
)
);
}
}]);
return Index;
}(_react2.default.Component);
exports.default = Index;
/***/ },
/* 244 */
/***/ function(module, exports, __webpack_require__) {
"use strict";
@ -27782,63 +27940,319 @@
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Index = function (_React$Component) {
_inherits(Index, _React$Component);
var ServerCtl = function (_React$Component) {
_inherits(ServerCtl, _React$Component);
function Index() {
_classCallCheck(this, Index);
function ServerCtl(props) {
_classCallCheck(this, ServerCtl);
return _possibleConstructorReturn(this, Object.getPrototypeOf(Index).apply(this, arguments));
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ServerCtl).call(this, props));
_this.startServer = _this.startServer.bind(_this);
_this.incrementAutosave = _this.incrementAutosave.bind(_this);
_this.decrementAutosave = _this.decrementAutosave.bind(_this);
_this.incrementAutosaveSlots = _this.incrementAutosaveSlots.bind(_this);
_this.decrementAutosaveSlots = _this.decrementAutosaveSlots.bind(_this);
_this.incrementPort = _this.incrementPort.bind(_this);
_this.decrementPort = _this.decrementPort.bind(_this);
_this.incrementLatency = _this.incrementLatency.bind(_this);
_this.decrementLatency = _this.decrementLatency.bind(_this);
_this.toggleAllowCmd = _this.toggleAllowCmd.bind(_this);
_this.toggleP2P = _this.toggleP2P.bind(_this);
_this.toggleAutoPause = _this.toggleAutoPause.bind(_this);
_this.state = {
latency: 100,
autosaveInterval: 5,
autosaveSlots: 10,
port: 34197,
disallowCmd: false,
peer2peer: false,
autoPause: false
};
return _this;
}
_createClass(Index, [{
_createClass(ServerCtl, [{
key: "startServer",
value: function startServer(e) {
var serverSettings = {
latency: Number(this.refs.latency.value),
autosave_interval: Number(this.refs.autosaveInterval.value),
autosave_slots: Number(this.refs.autosaveSlots.value),
port: Number(this.refs.port.value),
disallow_cmd: this.refs.allowCmd.checked,
peer2peer: this.refs.p2p.checked,
auto_pause: this.refs.autoPause.checked
};
console.log(serverSettings);
$.ajax({
type: "POST",
url: "/api/server/start",
dataType: "json",
data: JSON.stringify(serverSettings),
success: function success(resp) {
alert(resp);
}
});
e.preventDefault();
}
}, {
key: "incrementAutosave",
value: function incrementAutosave() {
var saveInterval = this.state.autosaveInterval + 1;
this.setState({ autosaveInterval: saveInterval });
}
}, {
key: "decrementAutosave",
value: function decrementAutosave() {
var saveInterval = this.state.autosaveInterval - 1;
this.setState({ autosaveInterval: saveInterval });
}
}, {
key: "incrementAutosaveSlots",
value: function incrementAutosaveSlots() {
var saveSlots = this.state.autosaveSlots + 1;
this.setState({ autosaveSlots: saveSlots });
}
}, {
key: "decrementAutosaveSlots",
value: function decrementAutosaveSlots() {
var saveSlots = this.state.autosaveSlots - 1;
this.setState({ autosaveSlots: saveSlots });
}
}, {
key: "incrementPort",
value: function incrementPort() {
var port = this.state.port + 1;
this.setState({ port: port });
}
}, {
key: "decrementPort",
value: function decrementPort() {
var port = this.state.port - 1;
this.setState({ port: port });
}
}, {
key: "incrementLatency",
value: function incrementLatency() {
var latency = this.state.latency + 1;
this.setState({ latency: latency });
}
}, {
key: "decrementLatency",
value: function decrementLatency() {
var latency = this.state.latency - 1;
this.setState({ latency: latency });
}
}, {
key: "toggleAllowCmd",
value: function toggleAllowCmd() {
var cmd = !this.state.disallowCmd;
this.setState({ disallowCmd: cmd });
}
}, {
key: "toggleP2P",
value: function toggleP2P() {
var p2p = !this.state.peer2peer;
this.setState({ peer2peer: p2p });
}
}, {
key: "toggleAutoPause",
value: function toggleAutoPause() {
var pause = !this.state.autoPause;
this.setState({ autoPause: pause });
}
}, {
key: "render",
value: function render() {
return _react2.default.createElement(
"div",
{ className: "content-wrapper" },
{ className: "box" },
_react2.default.createElement(
"section",
{ className: "content-header" },
"div",
{ className: "box-header" },
_react2.default.createElement(
"h1",
null,
"Index",
_react2.default.createElement(
"small",
null,
"Optional description"
)
),
"h3",
{ className: "box-title" },
"Server Control"
)
),
_react2.default.createElement(
"div",
{ className: "box-body" },
_react2.default.createElement(
"ol",
{ className: "breadcrumb" },
"form",
{ action: "", onSubmit: this.startServer },
_react2.default.createElement(
"li",
null,
"label",
{ "for": "latency" },
"Server latency setting (ms)"
),
_react2.default.createElement(
"div",
{ id: "latency", className: "input-group" },
_react2.default.createElement("input", { ref: "latency", name: "latency", id: "latency", type: "text", className: "form-control", onchange: this.state.latency, value: this.state.latency, placeholder: this.state.latency }),
_react2.default.createElement(
"a",
{ href: "#" },
_react2.default.createElement("i", { className: "fa fa-dashboard" }),
" Level"
"div",
{ className: "input-group-btn" },
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.incrementLatency },
_react2.default.createElement("i", { className: "fa fa-arrow-up" })
),
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.decrementLatency },
_react2.default.createElement("i", { className: "fa fa-arrow-down" })
)
)
),
_react2.default.createElement(
"li",
{ className: "active" },
"Here"
"label",
{ "for": "autosaveInterval" },
"Autosave Interval (mins)"
),
_react2.default.createElement(
"div",
{ id: "autosaveInterval", className: "input-group" },
_react2.default.createElement("input", { ref: "autosaveInterval", name: "autosaveInterval", id: "autosaveInterval", type: "text", className: "form-control", onchange: this.state.autosaveInterval, value: this.state.autosaveInterval, placeholder: this.state.autosaveInterval }),
_react2.default.createElement(
"div",
{ className: "input-group-btn" },
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.incrementAutosave },
_react2.default.createElement("i", { className: "fa fa-arrow-up" })
),
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.decrementAutosave },
_react2.default.createElement("i", { className: "fa fa-arrow-down" })
)
)
),
_react2.default.createElement(
"label",
{ "for": "autosaveSlots" },
"Autosave Slots"
),
_react2.default.createElement(
"div",
{ id: "autosaveSlots", className: "input-group" },
_react2.default.createElement("input", { ref: "autosaveSlots", name: "autosaveSlots", id: "autosaveSlots", type: "text", className: "form-control", onChange: this.state.autosaveSlots, value: this.state.autosaveSlots, placeholder: this.state.autosaveSlots }),
_react2.default.createElement(
"div",
{ className: "input-group-btn" },
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.incrementAutosaveSlots },
_react2.default.createElement("i", { className: "fa fa-arrow-up" })
),
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.decrementAutosaveSlots },
_react2.default.createElement("i", { className: "fa fa-arrow-down" })
)
)
),
_react2.default.createElement(
"label",
{ "for": "port" },
"Factorio Server Port"
),
_react2.default.createElement(
"div",
{ id: "port", className: "input-group" },
_react2.default.createElement("input", { ref: "port", name: "port", id: "port", type: "text", className: "form-control", onChange: this.state.port, value: this.state.port, placeholder: this.state.port }),
_react2.default.createElement(
"div",
{ className: "input-group-btn" },
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.incrementPort },
_react2.default.createElement("i", { className: "fa fa-arrow-up" })
),
_react2.default.createElement(
"button",
{ type: "button", className: "btn btn-primary", onClick: this.decrementPort },
_react2.default.createElement("i", { className: "fa fa-arrow-down" })
)
)
),
_react2.default.createElement(
"div",
{ "class": "form-group" },
_react2.default.createElement(
"div",
{ "class": "checkbox" },
_react2.default.createElement("input", { ref: "autoPause", type: "checkbox", onClick: this.toggleAutoPause }),
_react2.default.createElement(
"label",
null,
"Auto Pause when no players connected"
)
),
_react2.default.createElement(
"div",
{ "class": "checkbox" },
_react2.default.createElement("input", { ref: "p2p", type: "checkbox", onClick: this.toggleP2P }),
_react2.default.createElement(
"label",
null,
"Peer to peer connection method"
)
),
_react2.default.createElement(
"div",
{ "class": "checkbox" },
_react2.default.createElement("input", { ref: "allowCmd", type: "checkbox", onClick: this.toggleAllowCmd }),
_react2.default.createElement(
"label",
null,
"Allow commands on the server"
)
)
),
_react2.default.createElement(
"div",
{ "class": "form-group" },
_react2.default.createElement(
"select",
{ ref: "savefile", "class": "form-control" },
this.props.saves.map(function (save, i) {
return _react2.default.createElement(
"option",
{ key: save.name, value: save.name },
save.name
);
})
),
_react2.default.createElement(
"label",
null,
"Select Save File"
)
),
_react2.default.createElement(
"button",
{ className: "btn btn-block btn-success", type: "submit" },
"Start Factorio Server"
)
)
),
_react2.default.createElement("section", { className: "content" })
)
);
}
}]);
return Index;
return ServerCtl;
}(_react2.default.Component);
exports.default = Index;
exports.default = ServerCtl;
/***/ }
/******/ ]);

BIN
factorio-server-manager Executable file

Binary file not shown.

BIN
factorio-server-manger.exe Executable file

Binary file not shown.

View File

@ -4,9 +4,11 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"github.com/gorilla/mux"
)
@ -417,3 +419,134 @@ func LoadConfig(w http.ResponseWriter, r *http.Request) {
log.Printf("Sent config.ini response")
}
func StartServer(w http.ResponseWriter, r *http.Request) {
var err error
resp := JSONResponse{
Success: false,
}
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
if FactorioServ.Running {
resp.Data = "Factorio server is already running"
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding JSON response: ", err)
}
return
}
switch r.Method {
case "GET":
log.Printf("GET not supported for startserver handler")
resp.Data = "Unsupported method"
resp.Success = false
if err = json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error listing mods: %s", err)
}
case "POST":
log.Printf("Starting Factorio server.")
// TODO get form parameters for starting server
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error in starting factorio server handler body: %s", err)
return
}
log.Printf("Starting Factorio server with settings: %v", string(body))
err = json.Unmarshal(body, &FactorioServ)
if err != nil {
log.Printf("Error unmarshaling server settings JSON: %s", err)
return
}
go func() {
err = FactorioServ.Run()
if err != nil {
log.Printf("Error starting Factorio server: %s", err)
resp.Data = fmt.Sprintf("Error starting Factorio server: %s", err)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
return
}
}()
if FactorioServ.Running {
log.Printf("Factorio server started on port: %s", FactorioServ.Port)
}
resp.Data = fmt.Sprintf("Factorio server started on port: %s", FactorioServ.Port)
resp.Success = true
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
}
}
func StopServer(w http.ResponseWriter, r *http.Request) {
resp := JSONResponse{
Success: false,
}
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
if FactorioServ.Running {
err := FactorioServ.Stop()
if err != nil {
log.Printf("Error in stop server handler: %s", err)
resp.Data = fmt.Sprintf("Error in stop server handler: %s", err)
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
return
}
log.Printf("Stopped Factorio server.")
resp.Success = true
resp.Data = fmt.Sprintf("Factorio server stopped")
} else {
resp.Data = "Factorio server is not running"
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
return
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
}
func RunningServer(w http.ResponseWriter, r *http.Request) {
resp := JSONResponse{
Success: false,
}
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
if FactorioServ.Running {
log.Printf("Creating server status response")
resp.Success = true
status := map[string]string{}
status["status"] = "running"
status["port"] = strconv.Itoa(FactorioServ.Port)
status["savefile"] = FactorioServ.Savefile
resp.Data = status
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
log.Printf("Server status sent with data: %+v", resp.Data)
} else {
log.Printf("Server not running, creating status response")
resp.Success = true
status := map[string]string{}
status["status"] = "stopped"
resp.Data = status
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Printf("Error encoding config file JSON reponse: ", err)
}
}
}

View File

@ -18,7 +18,10 @@ type Config struct {
MaxUploadSize int64
}
var config Config
var (
config Config
FactorioServ *FactorioServer
)
func loadFlags() {
factorioDir := flag.String("dir", "./", "Specify location of Factorio directory.")
@ -44,6 +47,10 @@ func loadFlags() {
func main() {
loadFlags()
FactorioServ = initFactorio()
FactorioServ.Port = 12345
FactorioServ.Savefile = "testingsaves"
router := NewRouter()
log.Fatal(http.ListenAndServe(config.ServerIP+":"+config.ServerPort, router))

View File

@ -19,7 +19,7 @@ func NewRouter() *mux.Router {
r := mux.NewRouter().StrictSlash(true)
// API subrouter
// Serves all REST handlers prefixed with /api
// Serves all JSON REST handlers prefixed with /api
s := r.PathPrefix("/api").Subrouter()
for _, route := range apiRoutes {
s.Methods(route.Method).
@ -123,5 +123,25 @@ var apiRoutes = Routes{
"GET",
"/config",
LoadConfig,
}, {
"StartServer",
"GET",
"/server/start",
StartServer,
}, {
"StartServer",
"POST",
"/server/start",
StartServer,
}, {
"StopServer",
"GET",
"/server/stop",
StopServer,
}, {
"RunningServer",
"GET",
"/server/status",
RunningServer,
},
}

104
server.go
View File

@ -1,10 +1,30 @@
package main
import (
"io"
"log"
"os"
"os/exec"
"strconv"
"syscall"
)
type FactorioServer struct {
Cmd *exec.Cmd
Savefile string
Latency int `json:"latency"`
AutosaveInterval int `json:"autosave_interval"`
AutosaveSlots int `json:"autosave_slots"`
Port int `json:"port"`
DisallowCmd bool `json:"disallow_cmd"`
Running bool
PeerToPeer bool `json:"peer2peer"`
AutoPause bool `json:"auto_pause"`
StdOut io.ReadCloser
StdErr io.ReadCloser
StdIn io.WriteCloser
}
func createSave(saveName string) (string, error) {
args := []string{"--create", saveName}
@ -18,3 +38,87 @@ func createSave(saveName string) (string, error) {
return result, nil
}
func initFactorio() *FactorioServer {
// TODO move values to config struct
f := FactorioServer{
Latency: 100,
AutosaveInterval: 5,
AutosaveSlots: 10,
}
return &f
}
func (f *FactorioServer) Run() error {
var err error
args := []string{"--start-server", f.Savefile,
"--latency-ms", strconv.Itoa(f.Latency),
"--autosave-interval", strconv.Itoa(f.AutosaveInterval),
"--autosave-slots", strconv.Itoa(f.AutosaveSlots),
"--port", strconv.Itoa(f.Port)}
if f.DisallowCmd {
args = append(args, "--disallow-commands")
}
if f.PeerToPeer {
args = append(args, "--peer-to-peer")
}
if f.AutoPause {
args = append(args, "--no-auto-pause")
}
log.Println("Starting server with command: ", config.FactorioBinary, args)
f.Cmd = exec.Command(config.FactorioBinary, args...)
f.StdOut, err = f.Cmd.StdoutPipe()
if err != nil {
log.Printf("Error opening stdout pipe: %s", err)
return err
}
f.StdIn, err = f.Cmd.StdinPipe()
if err != nil {
log.Printf("Error opening stdin pipe: %s", err)
return err
}
f.StdErr, err = f.Cmd.StderrPipe()
if err != nil {
log.Printf("Error opening stderr pipe: %s", err)
return err
}
go io.Copy(os.Stdout, f.StdOut)
go io.Copy(os.Stderr, f.StdErr)
err = f.Cmd.Start()
if err != nil {
log.Printf("Error starting server process: %s", err)
return err
}
f.Running = true
err = f.Cmd.Wait()
if err != nil {
log.Printf("Command exited with error: %s", err)
return err
}
return nil
}
func (f *FactorioServer) Stop() error {
err := f.Cmd.Process.Signal(syscall.SIGINT)
if err != nil {
log.Printf("Error sending SIGINT to Factorio process: %s", err)
}
f.Running = false
log.Printf("Sent SIGINT to Factorio process")
return nil
}

View File

@ -8,6 +8,35 @@ import HiddenSidebar from './components/HiddenSidebar.jsx';
class App extends React.Component {
constructor(props) {
super(props);
this.facServStatus = this.facServStatus.bind(this);
this.getSaves = this.getSaves.bind(this);
this.state = {
serverRunning: "stopped",
saves: [],
}
}
facServStatus() {
$.ajax({
url: "/api/server/status",
dataType: "json",
success: (data) => {
this.setState({serverRunning: data.data.status})
}
})
}
getSaves() {
$.ajax({
url: "/api/saves/list",
dataType: "json",
success: (data) => {
this.setState({saves: data.data})
},
error: (xhr, status, err) => {
console.log('api/mods/list', status, err.toString());
}
})
}
render() {
@ -16,11 +45,17 @@ class App extends React.Component {
<Header />
<Sidebar />
<Sidebar
serverStatus={this.facServStatus}
serverRunning={this.state.serverRunning}
/>
{React.cloneElement(
this.props.children,
{message: ""}
{message: "",
facServerStatus: this.facServStatus,
saves: this.state.saves,
getSaves: this.getSaves}
)}
<Footer />

View File

@ -1,9 +1,24 @@
import React from 'react';
import ServerCtl from './ServerCtl/ServerCtl.jsx';
class Index extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.facServerStatus();
this.props.getSaves();
}
componentWillUnmount() {
this.props.facServerStatus();
}
render() {
return(
<div className="content-wrapper">
<div className="content-wrapper" style={{height: "100%"}}>
<section className="content-header">
<h1>
Index
@ -17,6 +32,11 @@ class Index extends React.Component {
<section className="content">
<ServerCtl
saves={this.props.saves}
getSaves={this.props.getSaves}
/>
</section>
</div>

View File

@ -7,28 +7,12 @@ class SavesContent extends React.Component {
constructor(props) {
super(props);
this.dlSave = this.dlSave.bind(this);
this.getSaves = this.getSaves.bind(this);
this.state = {
saves: []
}
}
componentDidMount() {
this.getSaves();
this.props.getSaves();
}
getSaves() {
$.ajax({
url: "/api/saves/list",
dataType: "json",
success: (data) => {
this.setState({saves: data.data})
},
error: (xhr, status, err) => {
console.log('api/mods/list', status, err.toString());
}
})
}
dlSave(saveName) {
$.ajax({
@ -61,20 +45,21 @@ class SavesContent extends React.Component {
<div className="row">
<div className="col-md-6">
<CreateSave
getSaves={this.getSaves}
getSaves={this.props.getSaves}
/>
</div>
<div className="col-md-6">
<UploadSave
getSaves={this.getSaves}
getSaves={this.props.getSaves}
/>
</div>
</div>
<SavesList
{...this.state}
saves={this.props.saves}
dlSave={this.dlSave}
getSaves={this.getSaves}
getSaves={this.props.getSaves}
/>

View File

@ -0,0 +1,199 @@
import React from 'react';
class ServerCtl extends React.Component {
constructor(props) {
super(props);
this.startServer = this.startServer.bind(this);
this.incrementAutosave = this.incrementAutosave.bind(this);
this.decrementAutosave = this.decrementAutosave.bind(this);
this.incrementAutosaveSlots = this.incrementAutosaveSlots.bind(this);
this.decrementAutosaveSlots = this.decrementAutosaveSlots.bind(this);
this.incrementPort = this.incrementPort.bind(this);
this.decrementPort = this.decrementPort.bind(this);
this.incrementLatency = this.incrementLatency.bind(this);
this.decrementLatency = this.decrementLatency.bind(this);
this.toggleAllowCmd = this.toggleAllowCmd.bind(this);
this.toggleP2P = this.toggleP2P.bind(this);
this.toggleAutoPause = this.toggleAutoPause.bind(this);
this.state = {
latency: 100,
autosaveInterval: 5,
autosaveSlots: 10,
port: 34197,
disallowCmd: false,
peer2peer: false,
autoPause: false,
}
}
startServer(e) {
let serverSettings = {
latency: Number(this.refs.latency.value),
autosave_interval: Number(this.refs.autosaveInterval.value),
autosave_slots: Number(this.refs.autosaveSlots.value),
port: Number(this.refs.port.value),
disallow_cmd: this.refs.allowCmd.checked,
peer2peer: this.refs.p2p.checked,
auto_pause: this.refs.autoPause.checked,
}
console.log(serverSettings);
$.ajax({
type: "POST",
url: "/api/server/start",
dataType: "json",
data: JSON.stringify(serverSettings),
success: (resp) => {
alert(resp)
}
})
e.preventDefault();
}
incrementAutosave() {
let saveInterval = this.state.autosaveInterval + 1;
this.setState({autosaveInterval: saveInterval})
}
decrementAutosave() {
let saveInterval = this.state.autosaveInterval - 1;
this.setState({autosaveInterval: saveInterval})
}
incrementAutosaveSlots() {
let saveSlots = this.state.autosaveSlots + 1;
this.setState({autosaveSlots: saveSlots})
}
decrementAutosaveSlots() {
let saveSlots = this.state.autosaveSlots - 1;
this.setState({autosaveSlots: saveSlots})
}
incrementPort() {
let port = this.state.port + 1;
this.setState({port: port})
}
decrementPort() {
let port = this.state.port - 1;
this.setState({port: port})
}
incrementLatency() {
let latency = this.state.latency + 1;
this.setState({latency: latency})
}
decrementLatency() {
let latency= this.state.latency- 1;
this.setState({latency: latency})
}
toggleAllowCmd() {
let cmd = !this.state.disallowCmd
this.setState({disallowCmd: cmd})
}
toggleP2P() {
let p2p = !this.state.peer2peer;
this.setState({peer2peer: p2p})
}
toggleAutoPause() {
let pause = !this.state.autoPause;
this.setState({autoPause: pause})
}
render() {
return(
<div className="box">
<div className="box-header">
<h3 className="box-title">Server Control</h3>
</div>
<div className="box-body">
<form action="" onSubmit={this.startServer}>
<label for="latency">Server latency setting (ms)</label>
<div id="latency" className="input-group">
<input ref="latency" name="latency" id="latency" type="text" className="form-control" onchange={this.state.latency} value={this.state.latency} placeholder={this.state.latency} />
<div className="input-group-btn">
<button type="button" className="btn btn-primary" onClick={this.incrementLatency}><i className="fa fa-arrow-up"></i></button>
<button type="button" className="btn btn-primary" onClick={this.decrementLatency}><i className="fa fa-arrow-down"></i></button>
</div>
</div>
<label for="autosaveInterval">Autosave Interval (mins)</label>
<div id="autosaveInterval" className="input-group">
<input ref="autosaveInterval" name="autosaveInterval" id="autosaveInterval" type="text" className="form-control" onchange={this.state.autosaveInterval} value={this.state.autosaveInterval} placeholder={this.state.autosaveInterval} />
<div className="input-group-btn">
<button type="button" className="btn btn-primary" onClick={this.incrementAutosave}><i className="fa fa-arrow-up"></i></button>
<button type="button" className="btn btn-primary" onClick={this.decrementAutosave}><i className="fa fa-arrow-down"></i></button>
</div>
</div>
<label for="autosaveSlots">Autosave Slots</label>
<div id="autosaveSlots" className="input-group">
<input ref="autosaveSlots" name="autosaveSlots" id="autosaveSlots" type="text" className="form-control" onChange={this.state.autosaveSlots} value={this.state.autosaveSlots} placeholder={this.state.autosaveSlots} />
<div className="input-group-btn">
<button type="button" className="btn btn-primary" onClick={this.incrementAutosaveSlots}><i className="fa fa-arrow-up"></i></button>
<button type="button" className="btn btn-primary" onClick={this.decrementAutosaveSlots}><i className="fa fa-arrow-down"></i></button>
</div>
</div>
<label for="port">Factorio Server Port</label>
<div id="port" className="input-group">
<input ref="port" name="port" id="port" type="text" className="form-control" onChange={this.state.port} value={this.state.port} placeholder={this.state.port} />
<div className="input-group-btn">
<button type="button" className="btn btn-primary" onClick={this.incrementPort}><i className="fa fa-arrow-up"></i></button>
<button type="button" className="btn btn-primary" onClick={this.decrementPort}><i className="fa fa-arrow-down"></i></button>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input ref="autoPause" type="checkbox" onClick={this.toggleAutoPause} />
<label>
Auto Pause when no players connected
</label>
</div>
<div class="checkbox">
<input ref="p2p" type="checkbox" onClick={this.toggleP2P} />
<label>
Peer to peer connection method
</label>
</div>
<div class="checkbox">
<input ref="allowCmd" type="checkbox" onClick={this.toggleAllowCmd} />
<label>
Allow commands on the server
</label>
</div>
</div>
<div class="form-group">
<select ref="savefile" class="form-control">
{this.props.saves.map( (save, i) => {
return(
<option key={save.name} value={save.name}>{save.name}</option>
)
})}
</select>
<label>Select Save File</label>
</div>
<button className="btn btn-block btn-success" type="submit">Start Factorio Server</button>
</form>
</div>
</div>
)
}
}
export default ServerCtl

View File

@ -1,10 +1,22 @@
import React from 'react';
import {Link} from 'react-router';
import {Link, IndexLink} from 'react-router';
class Sidebar extends React.Component {
constructor(props) {
super(props);
}
render() {
if (this.props.serverRunning === "running") {
var serverStatus =
<IndexLink to="/"><i className="fa fa-circle text-success"></i>Server Online</IndexLink>
} else {
var serverStatus =
<IndexLink to="/"><i className="fa fa-circle text-danger"></i>Server Offline</IndexLink>
}
return(
<aside className="main-sidebar">
<aside className="main-sidebar" style={{height: "100%"}}>
<section className="sidebar">
@ -14,7 +26,9 @@ class Sidebar extends React.Component {
</div>
<div className="pull-left info">
<p>Factorio Server Manager</p>
<a href="#"><i className="fa fa-circle text-success"></i>Server Online</a>
{serverStatus}
</div>
</div>
@ -30,6 +44,7 @@ class Sidebar extends React.Component {
<ul className="sidebar-menu">
<li className="header">MENU</li>
<li><IndexLink to="/" activeClassName="active"><i className="fa fa-link"></i><span>Server Control</span></IndexLink></li>
<li><Link to="/mods" activeClassName="active"><i className="fa fa-link"></i><span>Mods</span></Link></li>
<li><Link to="/logs" activeClassName="active"><i className="fa fa-link"></i> <span>Logs</span></Link></li>
<li><Link to="/saves" activeClassName="active"><i className="fa fa-link"></i> <span>Saves</span></Link></li>
@ -41,4 +56,9 @@ class Sidebar extends React.Component {
}
}
Sidebar.propTypes = {
serverStatus: React.PropTypes.func.isRequired,
serverRunning: React.PropTypes.string.isRequired,
}
export default Sidebar