mirror of
https://github.com/OpenFactorioServerManager/factorio-server-manager.git
synced 2025-01-28 05:36:33 +02:00
improved ui for config route, added error reporting to json responses
This commit is contained in:
parent
a77a442513
commit
96139c8cad
10
README.md
10
README.md
@ -15,12 +15,18 @@ This tool runs on a Factorio server and allows management of saves, mods and man
|
||||
Run the server and specify the directory of your Factorio server installation and the interface to run the HTTP server on.
|
||||
```
|
||||
Usage of ./factorio-server-manager:
|
||||
-bin string
|
||||
Location of Factorio Server binary file (default "bin/x64/factorio")
|
||||
-config string
|
||||
Specify location of Factorio config.ini file (default "config/config.ini")
|
||||
-dir string
|
||||
Specify location of Factorio config directory. (default "./")
|
||||
Specify location of Factorio directory. (default "./")
|
||||
-host string
|
||||
Specify IP for webserver to listen on. (default "0.0.0.0")
|
||||
-max-upload int
|
||||
Maximum filesize for uploaded files. (default 100000)
|
||||
-port string
|
||||
Specify a port for the server (default "8080")
|
||||
Specify a port for the server. (default "8080")
|
||||
|
||||
Example:
|
||||
|
||||
|
337
app/bundle.js
337
app/bundle.js
@ -72,7 +72,11 @@
|
||||
|
||||
var _SavesContent2 = _interopRequireDefault(_SavesContent);
|
||||
|
||||
var _Index = __webpack_require__(239);
|
||||
var _ConfigContent = __webpack_require__(239);
|
||||
|
||||
var _ConfigContent2 = _interopRequireDefault(_ConfigContent);
|
||||
|
||||
var _Index = __webpack_require__(241);
|
||||
|
||||
var _Index2 = _interopRequireDefault(_Index);
|
||||
|
||||
@ -87,7 +91,8 @@
|
||||
_react2.default.createElement(_reactRouter.IndexRoute, { component: _Index2.default }),
|
||||
_react2.default.createElement(_reactRouter.Route, { path: '/mods', component: _ModsContent2.default }),
|
||||
_react2.default.createElement(_reactRouter.Route, { path: '/logs', component: _LogsContent2.default }),
|
||||
_react2.default.createElement(_reactRouter.Route, { path: '/saves', component: _SavesContent2.default })
|
||||
_react2.default.createElement(_reactRouter.Route, { path: '/saves', component: _SavesContent2.default }),
|
||||
_react2.default.createElement(_reactRouter.Route, { path: '/config', component: _ConfigContent2.default })
|
||||
)
|
||||
), document.getElementById('app'));
|
||||
|
||||
@ -25505,7 +25510,7 @@
|
||||
{ className: 'wrapper' },
|
||||
_react2.default.createElement(_Header2.default, null),
|
||||
_react2.default.createElement(_Sidebar2.default, null),
|
||||
this.props.children,
|
||||
_react2.default.cloneElement(this.props.children, { message: "" }),
|
||||
_react2.default.createElement(_Footer2.default, null),
|
||||
_react2.default.createElement(_HiddenSidebar2.default, null)
|
||||
);
|
||||
@ -25794,6 +25799,21 @@
|
||||
'Saves'
|
||||
)
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'li',
|
||||
null,
|
||||
_react2.default.createElement(
|
||||
_reactRouter.Link,
|
||||
{ to: '/config', activeClassName: 'active' },
|
||||
_react2.default.createElement('i', { className: 'fa fa-link' }),
|
||||
' ',
|
||||
_react2.default.createElement(
|
||||
'span',
|
||||
null,
|
||||
'Configuration'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -25845,11 +25865,7 @@
|
||||
return _react2.default.createElement(
|
||||
"footer",
|
||||
{ className: "main-footer" },
|
||||
_react2.default.createElement(
|
||||
"div",
|
||||
{ className: "pull-right hidden-xs" },
|
||||
"Anything you want!!!!!!"
|
||||
),
|
||||
_react2.default.createElement("div", { className: "pull-right hidden-xs" }),
|
||||
_react2.default.createElement(
|
||||
"strong",
|
||||
null,
|
||||
@ -26114,7 +26130,7 @@
|
||||
url: "/api/mods/list",
|
||||
dataType: "json",
|
||||
success: function success(data) {
|
||||
_this2.setState({ listMods: data.mods });
|
||||
_this2.setState({ listMods: data.data.mods });
|
||||
},
|
||||
error: function error(xhr, status, err) {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -26130,7 +26146,7 @@
|
||||
url: "/api/mods/list/installed",
|
||||
dataType: "json",
|
||||
success: function success(data) {
|
||||
_this3.setState({ installedMods: data });
|
||||
_this3.setState({ installedMods: data.data });
|
||||
},
|
||||
error: function error(xhr, status, err) {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -26146,7 +26162,7 @@
|
||||
url: "/api/mods/toggle/" + modName,
|
||||
dataType: "json",
|
||||
success: function success(data) {
|
||||
_this4.setState({ listMods: data.mods });
|
||||
_this4.setState({ listMods: data.data.mods });
|
||||
},
|
||||
error: function error(xhr, status, err) {
|
||||
console.log('api/mods/toggle', status, err.toString());
|
||||
@ -26194,7 +26210,7 @@
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'section',
|
||||
{ className: 'content', style: { height: "100%" } },
|
||||
{ className: 'content' },
|
||||
_react2.default.createElement(_InstalledMods2.default, _extends({}, this.state, {
|
||||
loadInstalledModList: this.loadInstalledModList
|
||||
})),
|
||||
@ -26472,6 +26488,9 @@
|
||||
}, {
|
||||
key: 'uploadFile',
|
||||
value: function uploadFile(e) {
|
||||
var _this2 = this;
|
||||
|
||||
e.preventDefault();
|
||||
var fd = new FormData();
|
||||
fd.append('modfile', this.refs.file.files[0]);
|
||||
|
||||
@ -26482,11 +26501,13 @@
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function success(data) {
|
||||
alert(data);
|
||||
var response = JSON.parse(data);
|
||||
console.log(response.success);
|
||||
if (response.success === true) {
|
||||
_this2.updateInstalledMods();
|
||||
}
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
this.updateInstalledMods();
|
||||
}
|
||||
}, {
|
||||
key: 'removeMod',
|
||||
@ -26502,7 +26523,7 @@
|
||||
}, {
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var _this2 = this;
|
||||
var _this3 = this;
|
||||
|
||||
return _react2.default.createElement(
|
||||
'div',
|
||||
@ -26526,12 +26547,21 @@
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'form',
|
||||
{ ref: 'uploadForm', className: 'form', encType: 'multipart/form-data' },
|
||||
{ ref: 'uploadForm', className: 'form-inline', encType: 'multipart/form-data' },
|
||||
_react2.default.createElement(
|
||||
'fieldset',
|
||||
null,
|
||||
_react2.default.createElement('input', { className: 'btn btn-default', ref: 'file', type: 'file', name: 'modfile', id: 'modfile' }),
|
||||
_react2.default.createElement('input', { type: 'button', ref: 'button', value: 'Upload', onClick: this.uploadFile })
|
||||
'div',
|
||||
{ className: 'form-group' },
|
||||
_react2.default.createElement(
|
||||
'label',
|
||||
{ 'for': 'modfile' },
|
||||
'Upload Mod File...'
|
||||
),
|
||||
_react2.default.createElement('input', { className: 'form-control btn btn-default', ref: 'file', type: 'file', name: 'modfile', id: 'modfile' })
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'form-group' },
|
||||
_react2.default.createElement('input', { className: 'form-control btn btn-default', type: 'button', ref: 'button', value: 'Upload', onClick: this.uploadFile })
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
@ -26594,7 +26624,7 @@
|
||||
className: 'btn btn-danger btn-small',
|
||||
ref: 'modInput',
|
||||
type: 'button',
|
||||
onClick: _this2.removeMod.bind(_this2, i) },
|
||||
onClick: _this3.removeMod.bind(_this3, i) },
|
||||
_react2.default.createElement('i', { className: 'fa fa-trash' }),
|
||||
' Delete'
|
||||
)
|
||||
@ -26679,7 +26709,7 @@
|
||||
url: "/api/log/tail",
|
||||
dataType: "json",
|
||||
success: function success(data) {
|
||||
_this2.setState({ log: data });
|
||||
_this2.setState({ log: data.data });
|
||||
},
|
||||
error: function error(xhr, status, err) {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -26702,7 +26732,7 @@
|
||||
_react2.default.createElement(
|
||||
'small',
|
||||
null,
|
||||
'Optional description'
|
||||
'Analyze Factorio Logs'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
@ -26890,7 +26920,7 @@
|
||||
url: "/api/saves/list",
|
||||
dataType: "json",
|
||||
success: function success(data) {
|
||||
_this2.setState({ saves: data });
|
||||
_this2.setState({ saves: data.data });
|
||||
},
|
||||
error: function error(xhr, status, err) {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -26927,7 +26957,7 @@
|
||||
_react2.default.createElement(
|
||||
'small',
|
||||
null,
|
||||
'Optional description'
|
||||
'Factorio Save Files'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
@ -27199,13 +27229,18 @@
|
||||
}
|
||||
|
||||
_createClass(Save, [{
|
||||
key: 'hours12',
|
||||
value: function hours12(date) {
|
||||
return (date.getHours() + 24) % 12 || 12;
|
||||
}
|
||||
}, {
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
var saveLocation = "/api/saves/dl/" + this.props.save.name;
|
||||
var saveSize = parseFloat(this.props.save.size / 1024 / 1024).toFixed(3);
|
||||
var saveLastMod = Date.parse(this.props.save.last_mod);
|
||||
var date = new Date(saveLastMod);
|
||||
var dateFmt = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDay() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
|
||||
var dateFmt = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDay() + ' ' + this.hours12(date) + ':' + date.getMinutes() + ':' + date.getSeconds();
|
||||
|
||||
return _react2.default.createElement(
|
||||
'tr',
|
||||
@ -27267,6 +27302,254 @@
|
||||
|
||||
/***/ },
|
||||
/* 239 */
|
||||
/***/ 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 _Settings = __webpack_require__(240);
|
||||
|
||||
var _Settings2 = _interopRequireDefault(_Settings);
|
||||
|
||||
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 ConfigContent = function (_React$Component) {
|
||||
_inherits(ConfigContent, _React$Component);
|
||||
|
||||
function ConfigContent(props) {
|
||||
_classCallCheck(this, ConfigContent);
|
||||
|
||||
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(ConfigContent).call(this, props));
|
||||
|
||||
_this.getConfig = _this.getConfig.bind(_this);
|
||||
_this.state = {
|
||||
config: {}
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
|
||||
_createClass(ConfigContent, [{
|
||||
key: 'componentDidMount',
|
||||
value: function componentDidMount() {
|
||||
this.getConfig();
|
||||
}
|
||||
}, {
|
||||
key: 'getConfig',
|
||||
value: function getConfig() {
|
||||
var _this2 = this;
|
||||
|
||||
$.ajax({
|
||||
url: "/api/config",
|
||||
dataType: "json",
|
||||
success: function success(data) {
|
||||
_this2.setState({ config: data.data });
|
||||
console.log(_this2.state.config);
|
||||
},
|
||||
error: function error(xhr, status, err) {
|
||||
console.log('/api/config/get', status, err.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
return _react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'content-wrapper' },
|
||||
_react2.default.createElement(
|
||||
'section',
|
||||
{ className: 'content-header' },
|
||||
_react2.default.createElement(
|
||||
'h1',
|
||||
null,
|
||||
'Config',
|
||||
_react2.default.createElement(
|
||||
'small',
|
||||
null,
|
||||
'Manage server configuration'
|
||||
)
|
||||
),
|
||||
_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(
|
||||
'div',
|
||||
{ className: 'box' },
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'box-header' },
|
||||
_react2.default.createElement(
|
||||
'h3',
|
||||
{ className: 'box-title' },
|
||||
'Manage Server Configuration'
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'box-body' },
|
||||
Object.keys(this.state.config).map(function (key) {
|
||||
var conf = this.state.config[key];
|
||||
return _react2.default.createElement(
|
||||
'div',
|
||||
{ className: 'settings-section', key: key },
|
||||
_react2.default.createElement(
|
||||
'h3',
|
||||
null,
|
||||
key
|
||||
),
|
||||
_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,
|
||||
'Setting name'
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'th',
|
||||
null,
|
||||
'Setting value'
|
||||
)
|
||||
)
|
||||
),
|
||||
_react2.default.createElement(_Settings2.default, {
|
||||
section: key,
|
||||
config: conf
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}, this)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}]);
|
||||
|
||||
return ConfigContent;
|
||||
}(_react2.default.Component);
|
||||
|
||||
exports.default = ConfigContent;
|
||||
|
||||
/***/ },
|
||||
/* 240 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
|
||||
|
||||
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);
|
||||
|
||||
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 Settings = function (_React$Component) {
|
||||
_inherits(Settings, _React$Component);
|
||||
|
||||
function Settings(props) {
|
||||
_classCallCheck(this, Settings);
|
||||
|
||||
return _possibleConstructorReturn(this, Object.getPrototypeOf(Settings).call(this, props));
|
||||
}
|
||||
|
||||
_createClass(Settings, [{
|
||||
key: 'render',
|
||||
value: function render() {
|
||||
return _react2.default.createElement(
|
||||
'tbody',
|
||||
null,
|
||||
Object.keys(this.props.config).map(function (key) {
|
||||
console.log(typeof key === 'undefined' ? 'undefined' : _typeof(key));
|
||||
return _react2.default.createElement(
|
||||
'tr',
|
||||
{ key: key },
|
||||
_react2.default.createElement(
|
||||
'td',
|
||||
null,
|
||||
key
|
||||
),
|
||||
_react2.default.createElement(
|
||||
'td',
|
||||
null,
|
||||
this.props.config[key]
|
||||
)
|
||||
);
|
||||
}, this)
|
||||
);
|
||||
}
|
||||
}]);
|
||||
|
||||
return Settings;
|
||||
}(_react2.default.Component);
|
||||
|
||||
Settings.propTypes = {
|
||||
section: _react2.default.PropTypes.string.isRequired,
|
||||
config: _react2.default.PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
exports.default = Settings;
|
||||
|
||||
/***/ },
|
||||
/* 241 */
|
||||
/***/ function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
@ -1,13 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/go-ini/ini"
|
||||
)
|
||||
|
||||
func loadConfig(filename string) ([]byte, error) {
|
||||
func loadConfig(filename string) (map[string]map[string]string, error) {
|
||||
log.Printf("Loading config file: %s", filename)
|
||||
cfg, err := ini.Load(filename)
|
||||
if err != nil {
|
||||
@ -29,11 +28,6 @@ func loadConfig(filename string) ([]byte, error) {
|
||||
result[sectionName] = s.KeysHash()
|
||||
}
|
||||
log.Printf("Encoding config.ini to JSON")
|
||||
resp, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
log.Printf("Error marshaling config.ini to JSON: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
return result, nil
|
||||
}
|
||||
|
185
handlers.go
185
handlers.go
@ -11,23 +11,39 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type JSONResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,string"`
|
||||
}
|
||||
|
||||
func Index(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "hello world")
|
||||
}
|
||||
|
||||
// Returns JSON response of all mods installed in factorio/mods
|
||||
func ListInstalledMods(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
modDir := config.FactorioDir + "/mods"
|
||||
|
||||
mods, err := listInstalledMods(modDir)
|
||||
resp.Data, err = listInstalledMods(modDir)
|
||||
if err != nil {
|
||||
log.Printf("Error in ListInstalledMods handler: %s", err)
|
||||
resp.Data = fmt.Sprintf("Error in ListInstalledMods handler: %s", err)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error in list mods: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(mods); err != nil {
|
||||
resp.Success = true
|
||||
|
||||
fmt.Printf("%+v", resp)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error in list mods: %s", err)
|
||||
}
|
||||
}
|
||||
@ -35,6 +51,11 @@ func ListInstalledMods(w http.ResponseWriter, r *http.Request) {
|
||||
// Toggles mod passed in through mod variable
|
||||
// Updates mod-list.json file to toggle the enabled status of mods
|
||||
func ToggleMod(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
@ -42,41 +63,66 @@ func ToggleMod(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
m, err := parseModList()
|
||||
if err != nil {
|
||||
log.Printf("Could not parse mod list: %s", err)
|
||||
resp.Data = fmt.Sprintf("Could not parse mod list: %s", err)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error in list mods: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = m.toggleMod(modName)
|
||||
if err != nil {
|
||||
log.Printf("Could not toggle mod: %s error: %s", modName, err)
|
||||
resp.Data = fmt.Sprintf("Could not toggle mod: %s error: %s", modName, err)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error in list mods: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(m); err != nil {
|
||||
resp.Success = true
|
||||
resp.Data = m
|
||||
|
||||
if err = json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error in toggle mod: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns JSON response of all mods in the mod-list.json file
|
||||
func ListMods(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
m, err := parseModList()
|
||||
if err != nil {
|
||||
log.Printf("Could not parse mod list: %s", err)
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(m); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
resp.Data, err = parseModList()
|
||||
if err != nil {
|
||||
resp.Data = fmt.Sprintf("Could not parse mod list: %s", err)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error in list mods: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
resp.Success = true
|
||||
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error listing mods: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Uploads mod to the mods directory
|
||||
func UploadMod(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
resp := "Unsupported method"
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
resp.Data = "Unsupported method"
|
||||
if err = json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error listing mods: %s", err)
|
||||
}
|
||||
case "POST":
|
||||
@ -92,7 +138,8 @@ func UploadMod(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
out, err := os.Create(config.FactorioModsDir + "/" + header.Filename)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
resp.Data = err.Error()
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
log.Printf("Error in out")
|
||||
return
|
||||
}
|
||||
@ -100,12 +147,14 @@ func UploadMod(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
_, err = io.Copy(out, file)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
resp.Data = err.Error()
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
log.Printf("Error in io copy")
|
||||
return
|
||||
}
|
||||
log.Printf("Uploaded mod file: %s", header.Filename)
|
||||
resp := "File '" + header.Filename + "' submitted successfully"
|
||||
resp.Data = "File '" + header.Filename + "' submitted successfully"
|
||||
resp.Success = true
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
|
||||
default:
|
||||
@ -114,21 +163,27 @@ func UploadMod(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func RemoveMod(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
modName := vars["mod"]
|
||||
|
||||
err := rmMod(modName)
|
||||
err = rmMod(modName)
|
||||
if err == nil {
|
||||
// No error returned means mod was removed
|
||||
resp := fmt.Sprintf("Removed mod: %s", modName)
|
||||
resp.Data = fmt.Sprintf("Removed mod: %s", modName)
|
||||
resp.Success = true
|
||||
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)
|
||||
resp.Data = 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)
|
||||
@ -152,17 +207,27 @@ func DownloadMod(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Lists all save files in the factorio/saves directory
|
||||
func ListSaves(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
saveDir := config.FactorioDir + "/saves"
|
||||
|
||||
saves, err := listSaves(saveDir)
|
||||
resp.Data, err = listSaves(saveDir)
|
||||
if err != nil {
|
||||
log.Printf("Error in ListSaves handler: %s", err)
|
||||
resp.Data = fmt.Sprintf("Error listing save files: %s", err)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error listing saves: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(saves); err != nil {
|
||||
resp.Success = true
|
||||
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error listing saves: %s", err)
|
||||
}
|
||||
}
|
||||
@ -181,10 +246,16 @@ func DLSave(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func UploadSave(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
resp := "Unsupported method"
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
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":
|
||||
@ -192,17 +263,19 @@ func UploadSave(w http.ResponseWriter, r *http.Request) {
|
||||
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")
|
||||
resp.Success = false
|
||||
resp.Data = err.Error()
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
log.Printf("Error in upload save formfile: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
out, err := os.Create(config.FactorioSavesDir + "/" + header.Filename)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
resp.Success = false
|
||||
resp.Data = err.Error()
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
log.Printf("Error in out: %s", err)
|
||||
return
|
||||
}
|
||||
@ -210,12 +283,15 @@ func UploadSave(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
_, err = io.Copy(out, file)
|
||||
if err != nil {
|
||||
json.NewEncoder(w).Encode(err.Error())
|
||||
resp.Success = false
|
||||
resp.Data = err.Error()
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
log.Printf("Error in io copy: %s", err)
|
||||
return
|
||||
}
|
||||
log.Printf("Uploaded save file: %s", header.Filename)
|
||||
resp := "File '" + header.Filename + "' uploaded successfully"
|
||||
resp.Data = "File '" + header.Filename + "' uploaded successfully"
|
||||
resp.Success = true
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
@ -223,23 +299,28 @@ func UploadSave(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Deletes provided save
|
||||
//TODO sanitize
|
||||
func RemoveSave(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
saveName := vars["save"]
|
||||
|
||||
err := rmSave(saveName)
|
||||
err = rmSave(saveName)
|
||||
if err == nil {
|
||||
// save was removed
|
||||
resp := fmt.Sprintf("Removed save: %s", saveName)
|
||||
resp.Data = fmt.Sprintf("Removed save: %s", saveName)
|
||||
resp.Success = true
|
||||
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)
|
||||
resp.Data = 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)
|
||||
@ -249,34 +330,54 @@ func RemoveSave(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Returns last lines of the factorio-current.log file
|
||||
func LogTail(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
logLines, err := tailLog(config.FactorioLog)
|
||||
resp.Data, err = tailLog(config.FactorioLog)
|
||||
if err != nil {
|
||||
log.Printf("Could not tail %s: %s", config.FactorioLog, err)
|
||||
resp.Data = fmt.Sprintf("Could not tail %s: %s", config.FactorioLog, err)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Could not tail %s: %s", config.FactorioLog, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(logLines); err != nil {
|
||||
resp.Success = true
|
||||
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error tailing logfile", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Return JSON response of config.ini file
|
||||
func LoadConfig(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
resp := JSONResponse{
|
||||
Success: false,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
|
||||
configContents, err := loadConfig(config.FactorioConfigFile)
|
||||
if err != nil {
|
||||
log.Printf("Could not retrieve config.ini: %s", err)
|
||||
resp := "Error getting config.ini"
|
||||
resp.Data = "Error getting config.ini"
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error tailing logfile", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if _, err := w.Write(configContents); err != nil {
|
||||
log.Printf("Error encoding config.ini response: %s", err)
|
||||
|
||||
resp.Data = configContents
|
||||
resp.Success = true
|
||||
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
log.Printf("Error encoding config file JSON reponse: ", err)
|
||||
}
|
||||
|
||||
log.Printf("Sent config.ini response")
|
||||
}
|
||||
|
5
main.go
5
main.go
@ -12,6 +12,7 @@ type Config struct {
|
||||
FactorioModsDir string
|
||||
FactorioConfigFile string
|
||||
FactorioLog string
|
||||
FactorioBinary string
|
||||
ServerIP string
|
||||
ServerPort string
|
||||
MaxUploadSize int64
|
||||
@ -24,7 +25,8 @@ func loadFlags() {
|
||||
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.")
|
||||
factorioConfigFile := flag.String("config", "config/config.ini", "Specify location of Factorio config.ini file")
|
||||
factorioMaxUpload := flag.Int64("max-upload", 100000, "Maximum filesize for uploaded files.")
|
||||
factorioMaxUpload := flag.Int64("max-upload", 1024*1024*20, "Maximum filesize for uploaded files (default 20MB).")
|
||||
factorioBinary := flag.String("bin", "bin/x64/factorio", "Location of Factorio Server binary file")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@ -32,6 +34,7 @@ func loadFlags() {
|
||||
config.FactorioSavesDir = config.FactorioDir + "/saves"
|
||||
config.FactorioModsDir = config.FactorioDir + "/mods"
|
||||
config.FactorioConfigFile = config.FactorioDir + "/" + *factorioConfigFile
|
||||
config.FactorioBinary = config.FactorioDir + *factorioBinary
|
||||
config.ServerIP = *factorioIP
|
||||
config.ServerPort = *factorioPort
|
||||
config.FactorioLog = config.FactorioDir + "/factorio-current.log"
|
||||
|
@ -43,6 +43,10 @@ func NewRouter() *mux.Router {
|
||||
Methods("GET").
|
||||
Name("Logs").
|
||||
Handler(http.StripPrefix("/logs", http.FileServer(http.Dir("./app/"))))
|
||||
r.Path("/config").
|
||||
Methods("GET").
|
||||
Name("Config").
|
||||
Handler(http.StripPrefix("/config", http.FileServer(http.Dir("./app/"))))
|
||||
r.PathPrefix("/").
|
||||
Methods("GET").
|
||||
Name("Index").
|
||||
@ -112,7 +116,7 @@ var apiRoutes = Routes{
|
||||
}, {
|
||||
"LoadConfig",
|
||||
"GET",
|
||||
"/config/get",
|
||||
"/config",
|
||||
LoadConfig,
|
||||
},
|
||||
}
|
||||
|
@ -17,8 +17,11 @@ class App extends React.Component {
|
||||
<Header />
|
||||
|
||||
<Sidebar />
|
||||
|
||||
{this.props.children}
|
||||
|
||||
{React.cloneElement(
|
||||
this.props.children,
|
||||
{message: ""}
|
||||
)}
|
||||
|
||||
<Footer />
|
||||
|
||||
|
31
ui/App/components/Config/Settings.jsx
Normal file
31
ui/App/components/Config/Settings.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
class Settings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<tbody>
|
||||
{Object.keys(this.props.config).map(function(key) {
|
||||
console.log(typeof key);
|
||||
return(
|
||||
<tr key={key}>
|
||||
<td>{key}</td>
|
||||
<td>{this.props.config[key]}</td>
|
||||
</tr>
|
||||
)
|
||||
}, this)}
|
||||
</tbody>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Settings.propTypes = {
|
||||
section: React.PropTypes.string.isRequired,
|
||||
config: React.PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default Settings
|
82
ui/App/components/ConfigContent.jsx
Normal file
82
ui/App/components/ConfigContent.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import Settings from './Config/Settings.jsx';
|
||||
|
||||
class ConfigContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getConfig = this.getConfig.bind(this);
|
||||
this.state = {
|
||||
config: {}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getConfig();
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
$.ajax({
|
||||
url: "/api/config",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({config: data.data})
|
||||
console.log(this.state.config)
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('/api/config/get', status, err.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return(
|
||||
<div className="content-wrapper">
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Config
|
||||
<small>Manage server configuration</small>
|
||||
</h1>
|
||||
<ol className="breadcrumb">
|
||||
<li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li>
|
||||
<li className="active">Here</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Manage Server Configuration</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
|
||||
{Object.keys(this.state.config).map(function(key) {
|
||||
var conf = this.state.config[key]
|
||||
return(
|
||||
<div className="settings-section" key={key}>
|
||||
<h3>{key}</h3>
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting name</th>
|
||||
<th>Setting value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<Settings
|
||||
section={key}
|
||||
config={conf}
|
||||
/>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, this)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigContent
|
@ -5,7 +5,6 @@ class Footer extends React.Component {
|
||||
return(
|
||||
<footer className="main-footer">
|
||||
<div className="pull-right hidden-xs">
|
||||
Anything you want!!!!!!
|
||||
</div>
|
||||
<strong>Copyright © 2016 <a href="https://roote.me">Mitch Roote</a>.</strong> MIT License.
|
||||
</footer>
|
||||
|
@ -20,7 +20,7 @@ class LogsContent extends React.Component {
|
||||
url: "/api/log/tail",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({log: data})
|
||||
this.setState({log: data.data})
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -34,7 +34,7 @@ class LogsContent extends React.Component {
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Logs
|
||||
<small>Optional description</small>
|
||||
<small>Analyze Factorio Logs</small>
|
||||
</h1>
|
||||
<ol className="breadcrumb">
|
||||
<li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li>
|
||||
|
@ -11,6 +11,7 @@ class InstalledMods extends React.Component {
|
||||
}
|
||||
|
||||
uploadFile(e) {
|
||||
e.preventDefault();
|
||||
var fd = new FormData();
|
||||
fd.append('modfile', this.refs.file.files[0]);
|
||||
|
||||
@ -21,11 +22,13 @@ class InstalledMods extends React.Component {
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: (data) => {
|
||||
alert(data)
|
||||
var response = JSON.parse(data)
|
||||
console.log(response.success);
|
||||
if (response.success === true) {
|
||||
this.updateInstalledMods();
|
||||
}
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
this.updateInstalledMods();
|
||||
}
|
||||
|
||||
removeMod(i) {
|
||||
@ -47,12 +50,14 @@ class InstalledMods extends React.Component {
|
||||
|
||||
<div className="box-body">
|
||||
<h4>Upload Mod</h4>
|
||||
<form ref="uploadForm" className="form" encType='multipart/form-data'>
|
||||
<fieldset>
|
||||
<input className="btn btn-default" ref="file" type="file" name="modfile" id="modfile" />
|
||||
|
||||
<input type="button" ref="button" value="Upload" onClick={this.uploadFile} />
|
||||
</fieldset>
|
||||
<form ref="uploadForm" className="form-inline" encType='multipart/form-data'>
|
||||
<div className="form-group">
|
||||
<label for="modfile">Upload Mod File...</label>
|
||||
<input className="form-control btn btn-default" ref="file" type="file" name="modfile" id="modfile" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input className="form-control btn btn-default" type="button" ref="button" value="Upload" onClick={this.uploadFile} />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="table-responsive">
|
||||
|
@ -24,7 +24,7 @@ class ModsContent extends React.Component {
|
||||
url: "/api/mods/list",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({listMods: data.mods})
|
||||
this.setState({listMods: data.data.mods})
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -37,7 +37,7 @@ class ModsContent extends React.Component {
|
||||
url: "/api/mods/list/installed",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({installedMods: data})
|
||||
this.setState({installedMods: data.data})
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -50,7 +50,7 @@ class ModsContent extends React.Component {
|
||||
url: "/api/mods/toggle/" + modName,
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({listMods: data.mods})
|
||||
this.setState({listMods: data.data.mods})
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/toggle', status, err.toString());
|
||||
@ -72,7 +72,7 @@ class ModsContent extends React.Component {
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section className="content" style={{height: "100%"}}>
|
||||
<section className="content">
|
||||
|
||||
<InstalledMods
|
||||
{...this.state}
|
||||
|
@ -1,13 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
class Save extends React.Component {
|
||||
hours12(date) { return (date.getHours() + 24) % 12 || 12; }
|
||||
|
||||
render() {
|
||||
let saveLocation = "/api/saves/dl/" + this.props.save.name
|
||||
let saveSize = parseFloat(this.props.save.size / 1024 / 1024).toFixed(3)
|
||||
let saveLastMod = Date.parse(this.props.save.last_mod);
|
||||
let date = new Date(saveLastMod)
|
||||
let dateFmt = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDay() + ' '
|
||||
+ date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
|
||||
let dateFmt = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDay() + ' '
|
||||
+ this.hours12(date) + ':' + date.getMinutes() + ':' + date.getSeconds();
|
||||
|
||||
return(
|
||||
<tr>
|
||||
|
@ -20,7 +20,7 @@ class SavesContent extends React.Component {
|
||||
url: "/api/saves/list",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({saves: data})
|
||||
this.setState({saves: data.data})
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
@ -47,7 +47,7 @@ class SavesContent extends React.Component {
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Saves
|
||||
<small>Optional description</small>
|
||||
<small>Factorio Save Files</small>
|
||||
</h1>
|
||||
<ol className="breadcrumb">
|
||||
<li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li>
|
||||
|
@ -33,6 +33,7 @@ class Sidebar extends React.Component {
|
||||
<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>
|
||||
<li><Link to="/config" activeClassName="active"><i className="fa fa-link"></i> <span>Configuration</span></Link></li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
|
@ -5,6 +5,7 @@ import App from './App/App.jsx';
|
||||
import ModsContent from './App/components/ModsContent.jsx';
|
||||
import LogsContent from './App/components/LogsContent.jsx';
|
||||
import SavesContent from './App/components/SavesContent.jsx';
|
||||
import ConfigContent from './App/components/ConfigContent.jsx';
|
||||
import Index from './App/components/Index.jsx';
|
||||
|
||||
ReactDOM.render((
|
||||
@ -14,6 +15,7 @@ ReactDOM.render((
|
||||
<Route path="/mods" component={ModsContent}/>
|
||||
<Route path="/logs" component={LogsContent}/>
|
||||
<Route path="/saves" component={SavesContent}/>
|
||||
<Route path="/config" component={ConfigContent}/>
|
||||
</Route>
|
||||
</Router>
|
||||
), document.getElementById('app'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user