mirror of
https://github.com/OpenFactorioServerManager/factorio-server-manager.git
synced 2025-01-04 03:49:23 +02:00
wip
This commit is contained in:
parent
12509e2bd1
commit
738bdf4f99
@ -6,11 +6,15 @@ import {Redirect, Route, Switch, useHistory} from "react-router";
|
||||
import Controls from "./views/Controls";
|
||||
import {BrowserRouter} from "react-router-dom";
|
||||
import Logs from "./views/Logs";
|
||||
import Saves from "./views/Saves";
|
||||
import Saves from "./views/Saves/Saves";
|
||||
import Layout from "./components/Layout";
|
||||
import server from "../api/resources/server";
|
||||
import Mods from "./views/Mods";
|
||||
import UserManagement from "./views/UserManagment";
|
||||
import UserManagement from "./views/UserManagement/UserManagment";
|
||||
import ServerSettings from "./views/ServerSettings";
|
||||
import GameSettings from "./views/GameSettings";
|
||||
import Console from "./views/Console";
|
||||
import Help from "./views/Help";
|
||||
|
||||
const App = () => {
|
||||
|
||||
@ -51,7 +55,7 @@ const App = () => {
|
||||
), [isAuthenticated, serverStatus]);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<BrowserRouter basename="/rework">
|
||||
<Switch>
|
||||
<Route path="/login" render={() => (<Login handleLogin={handleAuthenticationStatus}/>)}/>
|
||||
|
||||
@ -59,12 +63,12 @@ const App = () => {
|
||||
<ProtectedRoute exact path="/" component={Controls}/>
|
||||
<ProtectedRoute path="/saves" component={Saves}/>
|
||||
<ProtectedRoute path="/mods" component={Mods}/>
|
||||
<ProtectedRoute path="/server-settings" component={Controls}/>
|
||||
<ProtectedRoute path="/game-settings" component={Controls}/>
|
||||
<ProtectedRoute path="/console" component={Controls}/>
|
||||
<ProtectedRoute path="/server-settings" component={ServerSettings}/>
|
||||
<ProtectedRoute path="/game-settings" component={GameSettings}/>
|
||||
<ProtectedRoute path="/console" component={Console}/>
|
||||
<ProtectedRoute path="/logs" component={Logs}/>
|
||||
<ProtectedRoute path="/user-management" component={UserManagement}/>
|
||||
<ProtectedRoute path="/help" component={Controls}/>
|
||||
<ProtectedRoute path="/help" component={Help}/>
|
||||
</Layout>
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
|
@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class Settings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<tbody>
|
||||
{Object.keys(this.props.config).map(function(key) {
|
||||
return(
|
||||
<tr key={key}>
|
||||
<td>{key}</td>
|
||||
<td>{this.props.config[key]}</td>
|
||||
</tr>
|
||||
)
|
||||
}, this)}
|
||||
</tbody>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Settings.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
config: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default Settings
|
@ -1,362 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import Settings from './Config/Settings.jsx';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
//https://stackoverflow.com/a/1414175
|
||||
function stringToBoolean(string) {
|
||||
switch(string.toLowerCase().trim()) {
|
||||
case "true":
|
||||
case "yes":
|
||||
case "1":
|
||||
return true;
|
||||
case "false":
|
||||
case "no":
|
||||
case "0":
|
||||
case null:
|
||||
return false;
|
||||
default:
|
||||
return Boolean(string);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getConfig = this.getConfig.bind(this);
|
||||
this.getServerSettings = this.getServerSettings.bind(this);
|
||||
this.updateServerSettings = this.updateServerSettings.bind(this);
|
||||
this.handleServerSettingsChange = this.handleServerSettingsChange.bind(this);
|
||||
this.formTypeField = this.formTypeField.bind(this);
|
||||
this.capitalizeFirstLetter = this.capitalizeFirstLetter.bind(this)
|
||||
this.state = {
|
||||
config: {},
|
||||
serverSettings: {}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getConfig();
|
||||
this.getServerSettings();
|
||||
}
|
||||
|
||||
capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
handleServerSettingsChange(name, e) {
|
||||
let fieldValue
|
||||
var change = this.state.serverSettings;
|
||||
|
||||
// if true, false and something else can be used, dont switch to boolean type!
|
||||
if(e.target.id == "allow_commands") {
|
||||
fieldValue = e.target.value;
|
||||
} else if(e.target.value === "true" || e.target.value === "false") {
|
||||
// Ensure Boolean type is used if required
|
||||
if(e.target.id === "lan" || e.target.id === "public") {
|
||||
if(e.target.value == "true") {
|
||||
fieldValue = true
|
||||
} else {
|
||||
fieldValue = false
|
||||
}
|
||||
change["visibility"][e.target.id] = fieldValue
|
||||
this.setState({serverSettings: change});
|
||||
return;
|
||||
}
|
||||
fieldValue = stringToBoolean(e.target.value)
|
||||
} else if(e.target.id === "admins" || e.target.id === "tags") {
|
||||
// Split settings values that are stored as arrays
|
||||
fieldValue = e.target.value.split(",")
|
||||
} else {
|
||||
fieldValue = e.target.value
|
||||
}
|
||||
change[name] = fieldValue;
|
||||
|
||||
this.setState({serverSettings: change});
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
$.ajax({
|
||||
url: "/api/config",
|
||||
dataType: "json",
|
||||
success: (resp) => {
|
||||
if(resp.success === true) {
|
||||
this.setState({config: resp.data})
|
||||
}
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('/api/config/get', status, err.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getServerSettings() {
|
||||
$.ajax({
|
||||
url: "/api/settings",
|
||||
dataType: "json",
|
||||
success: (resp) => {
|
||||
if(resp.success === true) {
|
||||
this.setState({serverSettings: resp.data})
|
||||
console.log(this.state)
|
||||
}
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('/api/settings/get', status, err.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateServerSettings(e) {
|
||||
e.preventDefault();
|
||||
var serverSettingsJSON = JSON.stringify(this.state.serverSettings)
|
||||
$.ajax({
|
||||
url: "/api/settings/update",
|
||||
datatype: "json",
|
||||
type: "POST",
|
||||
data: serverSettingsJSON,
|
||||
success: (data) => {
|
||||
console.log(data);
|
||||
if(data.success === true) {
|
||||
console.log("settings updated")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
formTypeField(key, setting) {
|
||||
if(key.startsWith("_comment_")) {
|
||||
return (
|
||||
<input
|
||||
key={key}
|
||||
ref={key}
|
||||
id={key}
|
||||
defaultValue={setting}
|
||||
type="hidden"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
switch(typeof setting) {
|
||||
case "number":
|
||||
return (
|
||||
<input
|
||||
ref={key}
|
||||
id={key}
|
||||
className="form-control"
|
||||
defaultValue={setting}
|
||||
type="number"
|
||||
onChange={this.handleServerSettingsChange.bind(this, key)}
|
||||
/>
|
||||
)
|
||||
case "string":
|
||||
if(key.includes("password")) {
|
||||
return (
|
||||
<input
|
||||
ref={key}
|
||||
id={key}
|
||||
className="form-control"
|
||||
defaultValue={setting}
|
||||
type="password"
|
||||
onChange={this.handleServerSettingsChange.bind(this, key)}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<input
|
||||
ref={key}
|
||||
id={key}
|
||||
className="form-control"
|
||||
defaultValue={setting}
|
||||
type="text"
|
||||
onChange={this.handleServerSettingsChange.bind(this, key)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case "boolean":
|
||||
return (
|
||||
<select
|
||||
ref={key}
|
||||
id={key}
|
||||
className="form-control"
|
||||
onChange={this.handleServerSettingsChange.bind(this, key)}
|
||||
>
|
||||
<option value={true}>True</option>
|
||||
<option value={false}>False</option>
|
||||
</select>
|
||||
)
|
||||
case "object":
|
||||
if(Array.isArray(setting)) {
|
||||
return (
|
||||
<input
|
||||
ref={key}
|
||||
id={key}
|
||||
className="form-control"
|
||||
defaultValue={setting}
|
||||
type="text"
|
||||
onChange={this.handleServerSettingsChange.bind(this, key)}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
if(key.includes("visibility")) {
|
||||
let vis_fields = []
|
||||
for(const key in setting) {
|
||||
const field =
|
||||
<div key={key}>
|
||||
<p>{key}</p>
|
||||
<select
|
||||
label={key}
|
||||
ref={key}
|
||||
id={key}
|
||||
className="form-control"
|
||||
onChange={this.handleServerSettingsChange.bind(this, key)}
|
||||
value={setting[key]}
|
||||
>
|
||||
<option value={true}>True</option>
|
||||
<option value={false}>False</option>
|
||||
</select>
|
||||
</div>
|
||||
vis_fields.push(field)
|
||||
}
|
||||
|
||||
return vis_fields
|
||||
}
|
||||
}
|
||||
default:
|
||||
return (
|
||||
<input
|
||||
ref={key}
|
||||
id={key}
|
||||
className="form-control"
|
||||
defaultValue={setting}
|
||||
type="text"
|
||||
onChange={this.handleServerSettingsChange.bind(this, key)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="content-wrapper">
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Config
|
||||
<small>Manage game configuration</small>
|
||||
|
||||
<small className="float-sm-right">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/"><FontAwesomeIcon icon="tachometer-alt"/>Server Control</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active">
|
||||
<FontAwesomeIcon icon="cogs"/>Game configurations
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Server Settings</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<div className="row">
|
||||
<div className="col-md-10">
|
||||
<div className="server-settings-section">
|
||||
<div className="table-responsive">
|
||||
<form ref="settingsForm"
|
||||
className="form-horizontal"
|
||||
onSubmit={this.updateServerSettings}
|
||||
>
|
||||
{
|
||||
Object.keys(this.state.serverSettings).map(function(key) {
|
||||
if(key.startsWith("_comment_")) {
|
||||
return (
|
||||
<div key={key}>
|
||||
{this.formTypeField(key, setting)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var setting = this.state.serverSettings[key]
|
||||
var setting_key = this.capitalizeFirstLetter(key.replace(/_/g, " "))
|
||||
var comment = this.state.serverSettings["_comment_" + key]
|
||||
|
||||
return (
|
||||
<div className="form-group" key={key}>
|
||||
<label htmlFor={key}
|
||||
className="control-label col-md-3"
|
||||
>
|
||||
{setting_key}
|
||||
</label>
|
||||
<div className="col-md-6">
|
||||
{
|
||||
this.formTypeField(key, setting)
|
||||
}
|
||||
<p className="help-block">{comment}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, this)
|
||||
}
|
||||
<div className="col-xs-6">
|
||||
<div className="form-group">
|
||||
<input className="form-control btn btn-success" type="submit"
|
||||
ref="button" value="Update Settings"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Game Configuration</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<div className="row">
|
||||
<div className="col-md-10">
|
||||
{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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigContent
|
@ -1,133 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class ConsoleContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.componentDidMount = this.componentDidMount.bind(this);
|
||||
this.handleInput = this.handleInput.bind(this);
|
||||
this.clearInput = this.clearInput.bind(this);
|
||||
this.clearHistory = this.clearHistory.bind(this);
|
||||
this.addHistory = this.addHistory.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.newLogLine = this.newLogLine.bind(this);
|
||||
this.subscribeLogToSocket = this.subscribeLogToSocket.bind(this);
|
||||
|
||||
this.state = {
|
||||
commands: {},
|
||||
history: [],
|
||||
prompt: '$ ',
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribeLogToSocket();
|
||||
}
|
||||
|
||||
subscribeLogToSocket() {
|
||||
let wsReadyState = this.props.socket.emit("log subscribe");
|
||||
if(wsReadyState != WebSocket.OPEN) {
|
||||
setTimeout(() => {
|
||||
this.subscribeLogToSocket();
|
||||
}, 50);
|
||||
|
||||
return;
|
||||
}
|
||||
this.setState({connected: true});
|
||||
|
||||
this.props.socket.on('log update', this.newLogLine.bind(this));
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
var el = this.refs.output;
|
||||
var container = document.getElementById("console-output");
|
||||
container.scrollTop = this.refs.output.scrollHeight;
|
||||
}
|
||||
|
||||
handleInput(e) {
|
||||
if (e.key === "Enter") {
|
||||
var input_text = this.refs.term.value;
|
||||
this.props.socket.emit("command send", input_text);
|
||||
this.addHistory(this.state.prompt + " " + input_text);
|
||||
this.clearInput();
|
||||
}
|
||||
}
|
||||
|
||||
clearInput() {
|
||||
this.refs.term.value = "";
|
||||
}
|
||||
|
||||
clearHistory() {
|
||||
ths.setState({ history: [] });
|
||||
}
|
||||
|
||||
addHistory(output) {
|
||||
var history = this.state.history;
|
||||
history.push(output);
|
||||
this.setState({
|
||||
'history': history
|
||||
});
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
var term = this.refs.term;
|
||||
term.focus();
|
||||
}
|
||||
|
||||
newLogLine(logline) {
|
||||
var history = this.state.history;
|
||||
history.push(logline);
|
||||
this.setState({
|
||||
'history': history
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
var output = this.state.history.map((op, i) => {
|
||||
return <p key={i}>{op}</p>
|
||||
});
|
||||
|
||||
return(
|
||||
<div className="content-wrapper">
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Server Console
|
||||
<small>Send commands and messages to the Factorio server</small>
|
||||
|
||||
<small className="float-sm-right">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/"><FontAwesomeIcon icon="tachometer-alt"/>Server Control</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active">
|
||||
<FontAwesomeIcon icon="terminal"/>Console
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<div className="console-box" >
|
||||
<div id='console-output' className='console-container' onClick={this.handleClick} ref="output">
|
||||
{output}
|
||||
</div>
|
||||
<p>
|
||||
<span className="console-prompt-box">{this.state.prompt}
|
||||
<input type="text" onKeyPress={this.handleInput} ref="term" /></span>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleContent.propTypes = {
|
||||
socket: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default ConsoleContent;
|
@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
class FontAwesomeIcon extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = classNames(this.props.prefix, {
|
||||
"fas": !this.props.prefix,
|
||||
}, 'fa-' + this.props.icon, this.props.className);
|
||||
|
||||
return (
|
||||
<i className={classes}></i>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FontAwesomeIcon.propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
prefix: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
export default FontAwesomeIcon;
|
@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
class Footer extends React.Component {
|
||||
render() {
|
||||
return(
|
||||
<footer className="main-footer">
|
||||
<div className="pull-right hidden-xs">
|
||||
</div>
|
||||
<strong>Copyright © 2019 <a href="https://github.com/MajorMJR/factorio-server-manager">Mitch Roote</a>.</strong> MIT License.
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Footer
|
@ -1,66 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Link, withRouter} from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class Header extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onLogout = this.onLogout.bind(this);
|
||||
}
|
||||
|
||||
onLogout(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: "/api/logout",
|
||||
dataType: "json",
|
||||
success: (resp) => {
|
||||
console.log(resp)
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for 1 second for logout callback to complete
|
||||
setTimeout(() => {
|
||||
this.props.history.push("/login")
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
render() {
|
||||
var loginMenu;
|
||||
if (this.props.loggedIn) {
|
||||
loginMenu =
|
||||
<ul className="navbar-nav ml-auto">
|
||||
<li className="nav-item">
|
||||
<Link className="nav-link" to="/settings">
|
||||
<FontAwesomeIcon icon="cogs" className="fa-fw"/>Settings
|
||||
</Link>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a href="javascript:void(0)" onClick={this.onLogout} className="nav-link">
|
||||
<FontAwesomeIcon icon="lock" className="fa-fw"/>Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
return(
|
||||
<nav className="main-header navbar navbar-expand navbar-light border-bottom">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" data-widget="pushmenu" href="#">
|
||||
<FontAwesomeIcon icon="bars"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{loginMenu}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
loggedIn: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
export default withRouter(Header);
|
@ -1,59 +0,0 @@
|
||||
import React from 'react';
|
||||
import ServerCtl from './ServerCtl/ServerCtl.jsx';
|
||||
import ServerStatus from './ServerCtl/ServerStatus.jsx';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class Index extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.facServStatus();
|
||||
this.props.getSaves();
|
||||
this.props.getStatus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.facServStatus();
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="content-wrapper" style={{height: "100%"}}>
|
||||
<section className="content-header" style={{height: "100%"}}>
|
||||
<h1>
|
||||
Factorio Server
|
||||
<small>Control your Factorio server</small>
|
||||
|
||||
<small className="float-sm-right">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item active">
|
||||
<FontAwesomeIcon icon="tachometer-alt"/>Server Control
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<ServerStatus
|
||||
serverStatus={this.props.serverStatus}
|
||||
facServStatus={this.props.facServStatus}
|
||||
getStatus={this.props.getStatus}
|
||||
/>
|
||||
|
||||
<ServerCtl
|
||||
getStatus={this.props.getStatus}
|
||||
saves={this.props.saves}
|
||||
getSaves={this.props.getSaves}
|
||||
serverStatus={this.props.serverStatus}
|
||||
facServStatus={this.props.facServStatus}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Index
|
@ -1,7 +1,7 @@
|
||||
import React, {useEffect} from "react";
|
||||
import server from "../../api/resources/server";
|
||||
import {NavLink} from "react-router-dom";
|
||||
import Button from "../elements/Button";
|
||||
import Button from "./Button";
|
||||
|
||||
const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => {
|
||||
|
||||
|
@ -1,79 +0,0 @@
|
||||
import React from 'react';
|
||||
import {withRouter} from 'react-router-dom';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class LoginContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.loginUser = this.loginUser.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {}
|
||||
|
||||
loginUser(e) {
|
||||
e.preventDefault();
|
||||
let user = {
|
||||
username: this.refs.username.value,
|
||||
password: this.refs.password.value,
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/login",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(user),
|
||||
success: (resp) => {
|
||||
console.log(resp);
|
||||
this.props.history.push("/");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="container" id="login">
|
||||
<div className="d-flex justify-content-center h-100">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h1>
|
||||
<img src="./images/factorio.jpg" className="img-circle" alt="User Image"/>
|
||||
Factorio Server Manager
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="car-body">
|
||||
<form onSubmit={this.loginUser}>
|
||||
<label className="input-group form-group">
|
||||
<div className="input-group-prepend">
|
||||
<span className="input-group-text">
|
||||
<FontAwesomeIcon icon="user"/>
|
||||
</span>
|
||||
</div>
|
||||
<input className="form-control" type="text" ref="username" placeholder="Username"/>
|
||||
</label>
|
||||
|
||||
<label className="input-group form-group">
|
||||
<div className="input-group-prepend">
|
||||
<span className="input-group-text">
|
||||
<FontAwesomeIcon icon="lock"/>
|
||||
</span>
|
||||
</div>
|
||||
<input className="form-control" type="password" ref="password" placeholder="Password"/>
|
||||
</label>
|
||||
|
||||
<label className="remember-me">
|
||||
<input type="checkbox"/>
|
||||
Remember me
|
||||
</label>
|
||||
|
||||
<input type="submit" value="Sign In" className="btn btn-primary btn-block btn-flat"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(LoginContent);
|
@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class LogLines extends React.Component {
|
||||
updateLog() {
|
||||
this.props.getLastLog();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.props.log.reverse();
|
||||
return(
|
||||
<div id="logLines" className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Factorio Log</h3>
|
||||
</div>
|
||||
<div className="box-body">
|
||||
<input className="btn btn-default" type='button' onClick={this.updateLog.bind(this)} value="Refresh" />
|
||||
<h5>Latest log line at the top</h5>
|
||||
<samp>
|
||||
{this.props.log.map ( (line, i) => {
|
||||
return(
|
||||
<p key={i}>{line}</p>
|
||||
)
|
||||
})}
|
||||
</samp>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LogLines.propTypes = {
|
||||
log: PropTypes.array.isRequired,
|
||||
getLastLog: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default LogLines
|
@ -1,66 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import LogLines from './Logs/LogLines.jsx';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class LogsContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.componentDidMount = this.componentDidMount.bind(this);
|
||||
this.getLastLog = this.getLastLog.bind(this);
|
||||
this.state = {
|
||||
log: []
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getLastLog();
|
||||
}
|
||||
|
||||
getLastLog() {
|
||||
$.ajax({
|
||||
url: "/api/log/tail",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({log: data.data})
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="content-wrapper">
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Logs
|
||||
<small>Analyze Factorio Logs</small>
|
||||
|
||||
<small className="float-sm-right">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/"><FontAwesomeIcon icon="tachometer-alt"/>Server Control</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active">
|
||||
<FontAwesomeIcon icon="file-alt" prefix="far"/>Logs
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<LogLines
|
||||
getLastLog={this.getLastLog}
|
||||
{...this.state}
|
||||
/>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default LogsContent
|
@ -1,195 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SemVer from 'semver';
|
||||
import FontAwesomeIcon from "../FontAwesomeIcon";
|
||||
|
||||
class Mod extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.toggleUpdateStatus = this.toggleUpdateStatus.bind(this);
|
||||
this.removeVersionAvailableStatus = this.removeVersionAvailableStatus.bind(this);
|
||||
|
||||
this.state = {
|
||||
newVersionAvailable: false,
|
||||
updateInProgress: false
|
||||
}
|
||||
|
||||
this.versionAjax = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.checkForNewVersion();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if(prevProps.mod.version != this.props.mod.version) {
|
||||
this.checkForNewVersion();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.versionAjax.abort();
|
||||
}
|
||||
|
||||
checkForNewVersion() {
|
||||
//send AJAX that will check this
|
||||
this.versionAjax = $.ajax({
|
||||
url: "/api/mods/details",
|
||||
method: "POST",
|
||||
data: {
|
||||
modId: this.props.mod.name
|
||||
},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
let newData = JSON.parse(data.data);
|
||||
//get newest COMPATIBLE release
|
||||
let newestRelease;
|
||||
newData.releases.forEach((release) => {
|
||||
//TODO change to info_json dependency (when mod-portal-api is working again)
|
||||
if(SemVer.satisfies(this.props.factorioVersion, release.info_json.factorio_version + ".x")) {
|
||||
if(!newestRelease) {
|
||||
newestRelease = release;
|
||||
} else if(SemVer.gt(release.version, newestRelease.version)) {
|
||||
newestRelease = release;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(newestRelease && newestRelease.version != this.props.mod.version) {
|
||||
if(this.props.updateCountAdd)
|
||||
this.props.updateCountAdd();
|
||||
|
||||
this.setState({
|
||||
newVersionAvailable: true,
|
||||
newVersion: {
|
||||
downloadUrl: newestRelease.download_url,
|
||||
file_name: newestRelease.file_name,
|
||||
version: newestRelease.version
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
newVersionAvailable: false,
|
||||
newVersion: null
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/details', status, err.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleUpdateStatus() {
|
||||
console.log("update Status changed");
|
||||
this.setState({
|
||||
updateInProgress: !this.state.updateInProgress
|
||||
})
|
||||
}
|
||||
|
||||
removeVersionAvailableStatus() {
|
||||
this.setState({
|
||||
newVersionAvailable: false,
|
||||
newVersion: null
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let modStatus;
|
||||
if(this.props.mod.enabled === false) {
|
||||
modStatus = <span className="badge badge-danger">Disabled</span>
|
||||
} else {
|
||||
modStatus = <span className="badge badge-success">Enabled</span>
|
||||
}
|
||||
|
||||
let version;
|
||||
if(this.state.newVersionAvailable) {
|
||||
let faArrow;
|
||||
if(SemVer.gt(this.state.newVersion.version, this.props.mod.version)) {
|
||||
faArrow = "arrow-circle-up";
|
||||
} else {
|
||||
faArrow = "arrow-circle-down";
|
||||
}
|
||||
|
||||
version = <span>{this.props.mod.version}
|
||||
<a className="btn btn-xs btn-default update-button"
|
||||
style={{
|
||||
marginLeft: 10,
|
||||
display: "inline-flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 30,
|
||||
height: 30
|
||||
}}
|
||||
href="#"
|
||||
onClick={(event) => {
|
||||
this.state.updateInProgress && this.props.updateMod != null ? null : this.props.updateMod(event, this.toggleUpdateStatus, this.removeVersionAvailableStatus);
|
||||
}}
|
||||
data-download-url={this.state.newVersion.downloadUrl}
|
||||
data-file-name={this.state.newVersion.file_name}
|
||||
>
|
||||
{
|
||||
this.state.updateInProgress ?
|
||||
<div className='loader' style={{width: 15, height: 15, marginRight: 0, borderWidth: 3,}}></div>
|
||||
:
|
||||
<FontAwesomeIcon icon={faArrow} title="Update Mod" style={{fontSize: "15pt"}}/>
|
||||
}
|
||||
</a>
|
||||
</span>;
|
||||
} else {
|
||||
version = this.props.mod.version;
|
||||
}
|
||||
|
||||
let factorioVersion;
|
||||
if(!this.props.mod.compatibility) {
|
||||
factorioVersion = <span style={{color: "red"}}>
|
||||
{this.props.mod.factorio_version}
|
||||
<sup>not compatible</sup>
|
||||
</span>
|
||||
} else {
|
||||
factorioVersion = this.props.mod.factorio_version;
|
||||
}
|
||||
|
||||
return(
|
||||
<tr data-mod-name={this.props.mod.name}
|
||||
data-file-name={this.props.mod.file_name}
|
||||
data-mod-version={this.props.mod.version}
|
||||
>
|
||||
<td>{this.props.mod.title}</td>
|
||||
<td>{modStatus}</td>
|
||||
<td>{version}</td>
|
||||
<td>{factorioVersion}</td>
|
||||
<td>
|
||||
<input className='btn btn-default btn-sm'
|
||||
ref='modName'
|
||||
type='submit'
|
||||
value='Toggle'
|
||||
onClick={(event) => this.props.toggleMod(event, this.state.updateInProgress)}
|
||||
disabled={this.state.updateInProgress}
|
||||
/>
|
||||
|
||||
<input className="btn btn-danger btn-sm"
|
||||
style={{marginLeft: 25}}
|
||||
ref="modName"
|
||||
type="submit"
|
||||
value="Delete"
|
||||
onClick={(event) => this.props.deleteMod(event, this.state.updateInProgress)}
|
||||
disabled={this.state.updateInProgress}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Mod.propTypes = {
|
||||
mod: PropTypes.object.isRequired,
|
||||
toggleMod: PropTypes.func.isRequired,
|
||||
deleteMod: PropTypes.func.isRequired,
|
||||
updateMod: PropTypes.func.isRequired,
|
||||
updateCountAdd: PropTypes.func,
|
||||
factorioVersion: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Mod
|
@ -1,163 +0,0 @@
|
||||
import React from 'react';
|
||||
import {instanceOfModsContent} from "./ModsPropTypes";
|
||||
import PropTypes from "prop-types";
|
||||
import {ReactSwalNormal} from 'Utilities/customSwal';
|
||||
|
||||
class ModLoadSave extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.loadMods = this.loadMods.bind(this);
|
||||
this.loadModsSwalHandler = this.loadModsSwalHandler.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//Load Saves
|
||||
this.props.getSaves();
|
||||
}
|
||||
|
||||
loadMods(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/save/load",
|
||||
method: "POST",
|
||||
data: $(e.target).serialize(),
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
let checkboxes = [];
|
||||
|
||||
data.data.mods.forEach((mod) => {
|
||||
if(mod.name == "base") return;
|
||||
|
||||
let singleCheckbox = <tr key={mod.name}>
|
||||
<td>
|
||||
{mod.name}
|
||||
<input type="hidden" name="mod_name" value={mod.name}/>
|
||||
</td>
|
||||
<td>
|
||||
{mod.version}
|
||||
<input type="hidden" name="mod_version" value={mod.version}/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
checkboxes.push(singleCheckbox);
|
||||
});
|
||||
|
||||
if(checkboxes.length == 0) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "No mods in this save!",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let table = <div>
|
||||
All Mods will be installed
|
||||
<div style={{display: "flex", width: "100%", justifyContent: "center"}}>
|
||||
<form id="swalForm">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<th>
|
||||
Version
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{checkboxes}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "Mods to install",
|
||||
html: table,
|
||||
type: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Download Mods!",
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: this.loadModsSwalHandler
|
||||
});
|
||||
},
|
||||
error: (jqXHR) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: jqXHR.responseJSON.data,
|
||||
html: true,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadModsSwalHandler() {
|
||||
$.ajax({
|
||||
url: "/api/mods/install/multiple",
|
||||
method: "POST",
|
||||
dataType: "JSON",
|
||||
data: $("#swalForm").serialize(),
|
||||
success: (data) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "All Mods installed successfully!",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
this.props.modContentClass.setState({
|
||||
installedMods: data.data.mods
|
||||
});
|
||||
},
|
||||
error: (jqXHR) => {
|
||||
let json_data = JSON.parse(jqXHR.responseJSON.data);
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: json_data.detail,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let saves = [];
|
||||
this.props.saves.forEach((value, index) => {
|
||||
if(index != this.props.saves.length - 1) {
|
||||
saves.push(
|
||||
<option key={index} value={value.name}>
|
||||
{value.name}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let classes = "box-body" + " " + this.props.className;
|
||||
let ids = this.props.id;
|
||||
|
||||
return (
|
||||
<div id={ids} className={classes}>
|
||||
<form action="" onSubmit={this.loadMods}>
|
||||
<div className="input-group">
|
||||
<select className="custom-select form-control" name="saveFile">
|
||||
{saves}
|
||||
</select>
|
||||
<div className="input-group-append">
|
||||
<button className="btn btn-outline-secondary" type="submit">Load Mods</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ModLoadSave.propTypes = {
|
||||
modContentClass: instanceOfModsContent.isRequired,
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string
|
||||
}
|
||||
|
||||
export default ModLoadSave;
|
@ -1,60 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import Mod from "./Mod.jsx";
|
||||
|
||||
class ModManager extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = "box-body" + " " + this.props.className;
|
||||
let ids = this.props.id;
|
||||
|
||||
return (
|
||||
<div id={ids} className={classes}>
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Version</th>
|
||||
<th>Factorio Version</th>
|
||||
<th>Toggle/Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{
|
||||
(this.props.installedMods != null) ?
|
||||
this.props.installedMods.map((mod, i) => {
|
||||
if(mod.name !== "base")
|
||||
return(
|
||||
<Mod
|
||||
key={mod.name}
|
||||
mod={mod}
|
||||
{...this.props}
|
||||
/>
|
||||
)
|
||||
}):null
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ModManager.propTypes = {
|
||||
installedMods: PropTypes.array,
|
||||
toggleMod: PropTypes.func.isRequired,
|
||||
deleteMod: PropTypes.func.isRequired,
|
||||
updateMod: PropTypes.func.isRequired,
|
||||
updateCountAdd: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string
|
||||
}
|
||||
|
||||
export default ModManager;
|
@ -1,219 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import NativeListener from 'react-native-listener';
|
||||
import ModSearch from './search/ModSearch.jsx';
|
||||
import ModUpload from "./ModUpload.jsx";
|
||||
import ModManager from "./ModManager.jsx";
|
||||
import ModPacks from "./packs/ModPackOverview.jsx";
|
||||
import {instanceOfModsContent} from "./ModsPropTypes.js";
|
||||
import ModLoadSave from "./ModLoadSave.jsx";
|
||||
import {ReactSwalNormal} from 'Utilities/customSwal';
|
||||
import FontAwesomeIcon from "../FontAwesomeIcon";
|
||||
|
||||
class ModOverview extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handlerSearchMod = this.handlerSearchMod.bind(this);
|
||||
|
||||
this.state = {
|
||||
shownModList: []
|
||||
}
|
||||
}
|
||||
|
||||
handlerSearchMod(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/search",
|
||||
method: "GET",
|
||||
data: $(e.target).serialize(),
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
let parsed_data = JSON.parse(data.data);
|
||||
|
||||
this.setState({
|
||||
"shownModList": parsed_data.results
|
||||
});
|
||||
},
|
||||
error: (jqXHR) => {
|
||||
let json_data = JSON.parse(jqXHR.responseJSON.data);
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: json_data.detail,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
downloadAllHandler(e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div>
|
||||
<div className="box collapsed-box" id="add-mod-box">
|
||||
<div className="box-header"
|
||||
data-toggle="collapse"
|
||||
data-target="#modSearch"
|
||||
aria-expanded="false"
|
||||
aria-controls="modSearch"
|
||||
role="button"
|
||||
style={{cursor: "pointer"}}
|
||||
>
|
||||
<FontAwesomeIcon icon="plus"/>
|
||||
<h3 className="box-title">Add Mod</h3>
|
||||
{this.props.loggedIn ?
|
||||
<div className="box-tools pull-right">
|
||||
<NativeListener onClick={this.props.factorioLogoutHandler}>
|
||||
<button className="btn btn-box-tool btn-danger" style={{color: "#fff"}}>
|
||||
Logout
|
||||
</button>
|
||||
</NativeListener>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
<ModSearch
|
||||
{...this.state}
|
||||
{...this.props}
|
||||
className="collapse"
|
||||
id="modSearch"
|
||||
submitSearchMod={this.handlerSearchMod}
|
||||
submitFactorioLogin={this.props.submitFactorioLogin}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="box collapsed-box">
|
||||
<div className="box-header"
|
||||
data-toggle="collapse"
|
||||
data-target="#modUpload"
|
||||
aria-expanded="false"
|
||||
aria-controls="modUpload"
|
||||
role="button"
|
||||
style={{cursor: "pointer"}}
|
||||
>
|
||||
<FontAwesomeIcon icon="plus"/>
|
||||
<h3 className="box-title">Upload Mod</h3>
|
||||
</div>
|
||||
|
||||
<ModUpload
|
||||
{...this.props}
|
||||
className="collapse"
|
||||
id="modUpload"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="box collapsed-box">
|
||||
<div className="box-header"
|
||||
data-toggle="collapse"
|
||||
data-target="#modLoadSave"
|
||||
aria-expanded="false"
|
||||
aria-controls="modLoadSave"
|
||||
role="button"
|
||||
style={{cursor: "pointer"}}
|
||||
>
|
||||
<FontAwesomeIcon icon="plus"/>
|
||||
<h3 className="box-title">Load Mods From Save</h3>
|
||||
</div>
|
||||
|
||||
<ModLoadSave
|
||||
{...this.props}
|
||||
className="collapse"
|
||||
id="modLoadSave"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="box" id="manage-mods">
|
||||
<div className="box-header"
|
||||
data-toggle="collapse"
|
||||
data-target="#modManager"
|
||||
aria-expanded="true"
|
||||
aria-controls="modManager"
|
||||
role="button"
|
||||
style={{cursor: "pointer"}}
|
||||
>
|
||||
<FontAwesomeIcon icon="minus"/>
|
||||
<h3 className="box-title">Manage Mods</h3>
|
||||
<div className="box-tools float-sm-right">
|
||||
{
|
||||
this.props.installedMods != null ?
|
||||
<NativeListener onClick={this.downloadAllHandler}>
|
||||
<a className="btn btn-box-tool btn-default" style={{marginRight: 20}} href={"/api/mods/download"} download>
|
||||
Download all Mods
|
||||
</a>
|
||||
</NativeListener>
|
||||
: null
|
||||
}
|
||||
{
|
||||
this.props.updatesAvailable > 0 ?
|
||||
<NativeListener onClick={this.props.updateAllMods}>
|
||||
<button className="btn btn-box-tool btn-default" style={{marginRight: 20}}>
|
||||
Update all Mods
|
||||
</button>
|
||||
</NativeListener>
|
||||
: null
|
||||
}
|
||||
{
|
||||
this.props.installedMods != null ?
|
||||
<NativeListener onClick={this.props.deleteAll}>
|
||||
<button className="btn btn-box-tool btn-danger" style={{color: "#fff"}}>
|
||||
Delete ALL Mods
|
||||
</button>
|
||||
</NativeListener>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModManager
|
||||
{...this.props}
|
||||
id="modManager"
|
||||
className="show"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="box collapsed-box">
|
||||
<div className="box-header"
|
||||
data-toggle="collapse"
|
||||
data-target="#modPacks"
|
||||
aria-expanded="false"
|
||||
aria-controls="modPacks"
|
||||
role="button"
|
||||
style={{cursor: "pointer"}}
|
||||
>
|
||||
<FontAwesomeIcon icon="plus"/>
|
||||
<h3 className="box-title">Manage Modpacks</h3>
|
||||
</div>
|
||||
|
||||
<ModPacks
|
||||
{...this.props}
|
||||
className="collapse"
|
||||
id="modPacks"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ModOverview.propTypes = {
|
||||
installedMods: PropTypes.array,
|
||||
submitFactorioLogin: PropTypes.func.isRequired,
|
||||
toggleMod: PropTypes.func.isRequired,
|
||||
deleteMod: PropTypes.func.isRequired,
|
||||
deleteAll: PropTypes.func.isRequired,
|
||||
updateMod: PropTypes.func.isRequired,
|
||||
uploadModSuccessHandler: PropTypes.func.isRequired,
|
||||
loggedIn: PropTypes.bool.isRequired,
|
||||
factorioLogoutHandler: PropTypes.func.isRequired,
|
||||
updatesAvailable: PropTypes.number.isRequired,
|
||||
updateAllMods: PropTypes.func.isRequired,
|
||||
updateCountAdd: PropTypes.func.isRequired,
|
||||
|
||||
modContentClass: instanceOfModsContent.isRequired,
|
||||
};
|
||||
|
||||
export default ModOverview;
|
@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ModUpload extends React.Component {
|
||||
componentDidMount() {
|
||||
$("#mod_upload_input").fileinput({
|
||||
uploadUrl: '/api/mods/upload',
|
||||
showCancel: false,
|
||||
showUploadedThumbs: false,
|
||||
browseOnZoneClick: true,
|
||||
uploadAsync: false,
|
||||
allowedFileExtensions: ['zip'],
|
||||
browseLabel: "Select Mods...",
|
||||
browseIcon: '<i class="fa fa-upload text-muted" style="color: white;"></i> ',
|
||||
theme: "fas",
|
||||
slugCallback: function(filename) {
|
||||
return filename;
|
||||
},
|
||||
}).on('filebatchuploadsuccess fileuploaded', this.props.uploadModSuccessHandler);
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = "box-body" + " " + this.props.className;
|
||||
let ids = this.props.id;
|
||||
|
||||
return(
|
||||
<div id={ids} className={classes}>
|
||||
<div className="alert alert-warning alert-dismissible" role="alert">
|
||||
<button type="button" className="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
The mods you upload will override every mod, that is already uploaded!<br/>
|
||||
Uploaded mods will be treated same, as if you install mods with the mod-portal-api.<br/><br/>
|
||||
Only zip-files are allowed to upload !!
|
||||
</div>
|
||||
|
||||
<label className="control-label">Select File</label>
|
||||
<input id="mod_upload_input" name="mod_file" multiple type="file" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ModUpload.propTypes = {
|
||||
uploadModSuccessHandler: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string
|
||||
};
|
||||
|
||||
export default ModUpload;
|
@ -1,22 +0,0 @@
|
||||
import ModsContent from "../ModsContent.jsx";
|
||||
|
||||
function instanceOfModsContentFunction(isRequired) {
|
||||
return function(props, propName, componentName) {
|
||||
if(props[propName]) {
|
||||
if(!props[propName] instanceof ModsContent) {
|
||||
return new Error(propName + ' in ' + componentName + ' is not an instance of ModContent');
|
||||
}
|
||||
} else {
|
||||
if(isRequired) {
|
||||
return new Error(propName + ' in ' + componentName + ' is missing');
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const instanceOfModsContent = instanceOfModsContentFunction(false);
|
||||
instanceOfModsContent.isRequired = instanceOfModsContentFunction(true);
|
||||
|
||||
export {instanceOfModsContent};
|
@ -1,502 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModManager from "../ModManager.jsx";
|
||||
import NativeListener from 'react-native-listener';
|
||||
import {instanceOfModsContent} from "../ModsPropTypes.js";
|
||||
import locks from "locks";
|
||||
import PropTypes from "prop-types";
|
||||
import {ReactSwalNormal, ReactSwalDanger} from 'Utilities/customSwal';
|
||||
import FontAwesomeIcon from "../../FontAwesomeIcon";
|
||||
|
||||
class ModPackOverview extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.createModPack = this.createModPack.bind(this);
|
||||
this.deleteModPack = this.deleteModPack.bind(this);
|
||||
this.loadModPack = this.loadModPack.bind(this);
|
||||
this.modPackToggleModHandler = this.modPackToggleModHandler.bind(this);
|
||||
this.modPackDeleteModHandler = this.modPackDeleteModHandler.bind(this);
|
||||
this.modPackUpdateModHandler = this.modPackUpdateModHandler.bind(this);
|
||||
|
||||
this.state = {
|
||||
listPacks: []
|
||||
}
|
||||
|
||||
this.mutex = locks.createMutex();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getModPacks();
|
||||
}
|
||||
|
||||
getModPacks() {
|
||||
//send ajax to get all modPacks and setState
|
||||
$.ajax({
|
||||
url: "/api/mods/packs/list",
|
||||
method: "GET",
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
this.setState({
|
||||
listPacks: data.data.mod_packs
|
||||
});
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/packs/list', status, err.toString());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
createModPack() {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Create modpack",
|
||||
html: "Please enter an unique modpack name:",
|
||||
input: "text",
|
||||
showCancelButton: true,
|
||||
inputPlaceholder: "Modpack name",
|
||||
inputAttributes: {
|
||||
required: "required"
|
||||
},
|
||||
inputValidator: (value) => {
|
||||
return new Promise(resolve => {
|
||||
if(value) {
|
||||
resolve();
|
||||
} else {
|
||||
resolve("You need to enter a name");
|
||||
}
|
||||
});
|
||||
},
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: (inputValue) => {
|
||||
// console.log(this);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/packs/create",
|
||||
method: "POST",
|
||||
data: {name: inputValue},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
this.mutex.lock(() => {
|
||||
let packList = this.state.listPacks;
|
||||
|
||||
data.data.mod_packs.forEach((v, k) => {
|
||||
if(v.name == inputValue) {
|
||||
packList.push(data.data.mod_packs[k]);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
listPacks: packList
|
||||
});
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "modpack created successfully",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/packs/create', status, err.toString());
|
||||
|
||||
let jsonResponse = jqXHR.responseJSON;
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "Error on creating modpack",
|
||||
text: jsonResponse.data,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteModPack(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
let name = $(e.target).parent().prev().html();
|
||||
|
||||
ReactSwalDanger.fire({
|
||||
title: "Are you sure?",
|
||||
html: <p>You really want to delete this modpack?<br/>There is no turning back, the modpack will be deleted forever (a very long time)!</p>,
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: () => {
|
||||
$.ajax({
|
||||
url: "/api/mods/packs/delete",
|
||||
method: "POST",
|
||||
data: {name: name},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
if(data.success) {
|
||||
this.mutex.lock(() => {
|
||||
let modPacks = this.state.listPacks;
|
||||
|
||||
modPacks.forEach((v, k) => {
|
||||
if(v.name == name) {
|
||||
delete modPacks[k];
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
listPacks: modPacks
|
||||
});
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "Modpack deleted successfully",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/packs/delete', status, err.toString());
|
||||
|
||||
let jsonResponse = jqXHR.responseJSON || err.toString();
|
||||
jsonResponse = jsonResponse.data || err.toString();
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "Error on creating modpack",
|
||||
text: jsonResponse,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadModPack(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
let name = $(e.target).parent().prev().html();
|
||||
|
||||
ReactSwalDanger.fire({
|
||||
title: "Are you sure?",
|
||||
text: "This operation will replace the current installed mods with the mods out of the selected ModPack!",
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: () => {
|
||||
$.ajax({
|
||||
url: "/api/mods/packs/load",
|
||||
method: "POST",
|
||||
data: {name: name},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "ModPack loaded!",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
this.props.modContentClass.setState({
|
||||
installedMods: data.data.mods
|
||||
});
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/packs/load', status, err.toString());
|
||||
|
||||
let jsonResponse = jqXHR.responseJSON || err.toString();
|
||||
jsonResponse = jsonResponse.data || err.toString();
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "Error on loading ModPack",
|
||||
text: jsonResponse,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
downloadModPack(e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
modPackToggleModHandler(e, updatesInProgress) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
if(updatesInProgress) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Toggle mod failed",
|
||||
text: "Can't toggle the mod, when an update is still in progress",
|
||||
type: "error"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let $button = $(e.target);
|
||||
let $row = $button.parents("tr");
|
||||
let modName = $row.data("mod-name");
|
||||
let modPackName = $row.parents(".single-modpack").find("h3").html();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/packs/mod/toggle",
|
||||
method: "POST",
|
||||
data: {
|
||||
modName: modName,
|
||||
modPack: modPackName
|
||||
},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
if(data.success) {
|
||||
this.mutex.lock(() => {
|
||||
let packList = this.state.listPacks;
|
||||
|
||||
packList.forEach((modPack, modPackKey) => {
|
||||
if(modPack.name == modPackName) {
|
||||
packList[modPackKey].mods.mods.forEach((mod, modKey) => {
|
||||
if(mod.name == modName) {
|
||||
packList[modPackKey].mods.mods[modKey].enabled = data.data;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
listPacks: packList
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/packs/mod/toggle', status, err.toString());
|
||||
ReactSwalNormal.fire({
|
||||
title: "Toggle Mod went wrong",
|
||||
text: err.toString(),
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
modPackDeleteModHandler(e, updatesInProgress) {
|
||||
e.preventDefault();
|
||||
|
||||
if(updatesInProgress) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Delete failed",
|
||||
text: "Can't delete the mod, when an update is still in progress",
|
||||
type: "error"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let $button = $(e.target);
|
||||
let $row = $button.parents("tr");
|
||||
let modName = $row.data("mod-name");
|
||||
let modPackName = $row.parents(".single-modpack").find("h3").html();
|
||||
|
||||
ReactSwalDanger.fire({
|
||||
title: "Delete Mod?",
|
||||
text: "This will delete the mod forever",
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Delete it!",
|
||||
cancelButtonText: "Close",
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: () => {
|
||||
$.ajax({
|
||||
url: "/api/mods/packs/mod/delete",
|
||||
method: "POST",
|
||||
data: {
|
||||
modName: modName,
|
||||
modPackName: modPackName
|
||||
},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
if(data.success) {
|
||||
this.mutex.lock(() => {
|
||||
ReactSwalNormal.fire({
|
||||
title: <p>Delete of mod {modName} inside modPack {modPackName} successful</p>,
|
||||
type: "success"
|
||||
})
|
||||
|
||||
let packList = this.state.listPacks;
|
||||
|
||||
packList.forEach((modPack, modPackKey) => {
|
||||
if(modPack.name == modPackName) {
|
||||
packList[modPackKey].mods.mods.forEach((mod, modKey) => {
|
||||
if(mod.name == modName) {
|
||||
delete packList[modPackKey].mods.mods[modKey];
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
listPacks: packList
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/packs/mod/delete', status, err.toString());
|
||||
ReactSwalNormal.fire({
|
||||
title: "Delete Mod went wrong",
|
||||
text: jqXHR.responseJSON.data,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
modPackUpdateModHandler(e, toggleUpdateStatus, removeVersionAvailableStatus) {
|
||||
e.preventDefault();
|
||||
|
||||
if(!this.props.modContentClass.state.loggedIn) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Update failed",
|
||||
text: "please login into Factorio to update mod",
|
||||
type: "error",
|
||||
});
|
||||
|
||||
let $addModBox = $('#add-mod-box');
|
||||
if($addModBox.hasClass("collapsed-box")) {
|
||||
$addModBox.find(".box-header").click();
|
||||
}
|
||||
} else {
|
||||
let $button = $(e.currentTarget);
|
||||
let download_url = $button.data("downloadUrl");
|
||||
let filename = $button.data("fileName");
|
||||
let $row = $button.parents("tr");
|
||||
let modName = $row.data("modName");
|
||||
let modPackName = $row.parents(".single-modpack").find("h3").html();
|
||||
|
||||
//make button spinning
|
||||
toggleUpdateStatus();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/packs/mod/update",
|
||||
method: "POST",
|
||||
data: {
|
||||
downloadUrl: download_url,
|
||||
filename: filename,
|
||||
modName: modName,
|
||||
modPackName: modPackName
|
||||
},
|
||||
success: (data) => {
|
||||
toggleUpdateStatus();
|
||||
removeVersionAvailableStatus();
|
||||
|
||||
if(data.success) {
|
||||
this.mutex.lock(() => {
|
||||
let packList = this.state.listPacks;
|
||||
|
||||
packList.forEach((modPack, modPackKey) => {
|
||||
if(modPack.name == modPackName) {
|
||||
packList[modPackKey].mods.mods.forEach((mod, modKey) => {
|
||||
if(mod.name == modName) {
|
||||
packList[modPackKey].mods.mods[modKey] = data.data;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
listPacks: packList
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/packs/mod/update', status, err.toString());
|
||||
toggleUpdateStatus();
|
||||
ReactSwalNormal.fire({
|
||||
title: "Update Mod went wrong",
|
||||
text: jqXHR.responseJSON.data,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let classes = "box-body" + " " + this.props.className;
|
||||
let ids = this.props.id;
|
||||
|
||||
return(
|
||||
<div id={ids} className={classes}>
|
||||
{
|
||||
this.state.listPacks != null ?
|
||||
this.state.listPacks.map(
|
||||
(modpack, index) => {
|
||||
return(
|
||||
<div key={modpack.name} className="box single-modpack collapsed-box">
|
||||
<div className="box-header"
|
||||
data-toggle="collapse"
|
||||
data-target={"#" + modpack.name}
|
||||
aria-expanded="false"
|
||||
aria-controls={modpack.name}
|
||||
style={{cursor: "pointer"}}
|
||||
>
|
||||
<FontAwesomeIcon icon="plus"/>
|
||||
<h3 className="box-title">{modpack.name}</h3>
|
||||
<div className="box-tools pull-right">
|
||||
<NativeListener onClick={this.downloadModPack}>
|
||||
<a className="btn btn-box-tool btn-default" style={{marginRight: 10}} href={"/api/mods/packs/download/" + modpack.name} download>Download</a>
|
||||
</NativeListener>
|
||||
|
||||
<NativeListener onClick={this.loadModPack}>
|
||||
<button className="btn btn-box-tool btn-default" style={{marginRight: 10}}>Load ModPack</button>
|
||||
</NativeListener>
|
||||
|
||||
<NativeListener onClick={this.deleteModPack}>
|
||||
<button className="btn btn-box-tool btn-danger" style={{color: "#fff"}}>Delete</button>
|
||||
</NativeListener>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModManager
|
||||
{...this.props}
|
||||
className="collapse"
|
||||
id={modpack.name}
|
||||
installedMods={modpack.mods.mods}
|
||||
deleteMod={this.modPackDeleteModHandler}
|
||||
toggleMod={this.modPackToggleModHandler}
|
||||
updateMod={this.modPackUpdateModHandler}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
: null
|
||||
}
|
||||
|
||||
<div className="box">
|
||||
<div className="box-header" style={{cursor: "pointer"}} onClick={this.createModPack}>
|
||||
<FontAwesomeIcon icon="plus"/>
|
||||
<h3 className="box-title">Add ModPack with current installed mods</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ModPackOverview.propTypes = {
|
||||
modContentClass: instanceOfModsContent.isRequired,
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string
|
||||
};
|
||||
|
||||
export default ModPackOverview
|
@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ModFoundOverview extends React.Component {
|
||||
render() {
|
||||
let imgStyle= {
|
||||
width: 144,
|
||||
height: 144,
|
||||
border: "1px outset #333",
|
||||
borderRadius: 2,
|
||||
}
|
||||
let noImgStyle = {
|
||||
container: {
|
||||
width: 144,
|
||||
height: 144,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: "#333",
|
||||
border: "1px inset #333",
|
||||
fontSize: 20,
|
||||
color: "#949391",
|
||||
border: "1px outset #333",
|
||||
borderRadius: 2,
|
||||
},
|
||||
}
|
||||
let informationStyle = {
|
||||
container: {
|
||||
marginLeft: 20,
|
||||
}
|
||||
}
|
||||
|
||||
let mods = [];
|
||||
this.props.shownModList.some((mod, index) => {
|
||||
if(index == 10) return true;
|
||||
let img =
|
||||
(mod.first_media_file != null) ?
|
||||
<img src={mod.first_media_file.urls.thumb} style={imgStyle} />
|
||||
:
|
||||
<div style={noImgStyle.container}>
|
||||
<div>No picture</div>
|
||||
</div>;
|
||||
|
||||
mods.push(
|
||||
<div className="list-group-item" key={mod.title}>
|
||||
<div style={{display: "flex"}}>
|
||||
{img}
|
||||
<div style={informationStyle.container}>
|
||||
<h4 className="list-group-item-heading">{mod.title} <small>by {mod.owner}</small></h4>
|
||||
<div className="list-group-item-text">{mod.summary}</div>
|
||||
<button style={{marginTop: 10, display: "flex"}} onClick={this.props.loadDownloadList} data-mod-id={mod.name}>INSTALL</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="list-group">
|
||||
{mods}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ModFoundOverview.propTypes = {
|
||||
shownModList: PropTypes.array.isRequired,
|
||||
loadDownloadList: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default ModFoundOverview;
|
@ -1,69 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ModSearch extends React.Component {
|
||||
render() {
|
||||
let classes = "box-body" + " " + this.props.className;
|
||||
let ids = this.props.id;
|
||||
|
||||
if(this.props.loggedIn) {
|
||||
//TODO switch back to currently commented out code, when the mod-portal-api is back with all features!!
|
||||
/*return (
|
||||
<div className="box-body">
|
||||
<form onSubmit={this.props.submitSearchMod}>
|
||||
<div className="input-group col-lg-5">
|
||||
<input type="text" className="form-control" placeholder="Search for Mod" name="search" />
|
||||
<span className="input-group-btn">
|
||||
<input className="btn btn-default" type="submit" value="Go!"/>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ModFoundOverview
|
||||
{...this.props}
|
||||
/>
|
||||
</div>
|
||||
)*/
|
||||
return (
|
||||
<div id={ids} className={classes}>
|
||||
<form onSubmit={this.props.loadDownloadList}>
|
||||
<div className="input-group col-lg-5">
|
||||
<input type="text" className="form-control" placeholder="Download mod by ID" name="modId" />
|
||||
<span className="input-group-btn">
|
||||
<input className="btn btn-default" type="submit" value="Go!"/>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div id={ids} className={classes}>
|
||||
<form onSubmit={this.props.submitFactorioLogin}>
|
||||
<h4>Login into Factorio</h4>
|
||||
<div className="form-group">
|
||||
<label htmlFor="factorio-account-name">Factorio Account Name:</label>
|
||||
<input type="text" className="form-control" id="factorio-account-name" name="username" required />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="pwd">Password:</label>
|
||||
<input type="password" className="form-control" id="pwd" name="password" required />
|
||||
</div>
|
||||
<input type="submit" className="btn btn-default" value="Login" />
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModSearch.propTypes = {
|
||||
submitSearchMod: PropTypes.func.isRequired,
|
||||
loggedIn: PropTypes.bool.isRequired,
|
||||
submitFactorioLogin: PropTypes.func.isRequired,
|
||||
loadDownloadList: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
id: PropTypes.string
|
||||
}
|
||||
|
||||
export default ModSearch;
|
@ -1,582 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Link} from 'react-router-dom';
|
||||
import ModOverview from './Mods/ModOverview.jsx';
|
||||
import locks from "locks";
|
||||
import SemVer from 'semver';
|
||||
import {ReactSwalNormal, ReactSwalDanger} from 'Utilities/customSwal';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class ModsContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.componentDidMount = this.componentDidMount.bind(this);
|
||||
this.loadModList = this.loadModList.bind(this);
|
||||
this.handlerFactorioLogin = this.handlerFactorioLogin.bind(this);
|
||||
this.loadDownloadList = this.loadDownloadList.bind(this);
|
||||
this.loadDownloadListSwalHandler = this.loadDownloadListSwalHandler.bind(this);
|
||||
this.toggleModHandler = this.toggleModHandler.bind(this);
|
||||
this.deleteModHandler = this.deleteModHandler.bind(this);
|
||||
this.updateModHandler = this.updateModHandler.bind(this);
|
||||
this.uploadModSuccessHandler = this.uploadModSuccessHandler.bind(this);
|
||||
this.factorioLogoutHandler = this.factorioLogoutHandler.bind(this);
|
||||
this.deleteAllHandler = this.deleteAllHandler.bind(this);
|
||||
this.updateAllModsHandler = this.updateAllModsHandler.bind(this);
|
||||
this.updatesAvailable = this.updatesAvailable.bind(this);
|
||||
this.updateCountSubtract = this.updateCountSubtract.bind(this);
|
||||
this.updateCountAdd = this.updateCountAdd.bind(this);
|
||||
|
||||
|
||||
this.state = {
|
||||
loggedIn: false,
|
||||
installedMods: null,
|
||||
updatesAvailable: 0,
|
||||
};
|
||||
|
||||
this.mutex = locks.createMutex();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadModList();
|
||||
this.checkLoginState();
|
||||
}
|
||||
|
||||
loadModList() {
|
||||
$.ajax({
|
||||
url: "/api/mods/list/installed",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({installedMods: data.data})
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/list/installed', status, err.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handlerFactorioLogin(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let $form = $(e.target);
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/factorio/login",
|
||||
method: "POST",
|
||||
data: $form.serialize(),
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Logged in Successfully",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
this.setState({
|
||||
"loggedIn": data.data
|
||||
});
|
||||
},
|
||||
error: (jqXHR) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: jqXHR.responseJSON.data,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkLoginState() {
|
||||
$.ajax({
|
||||
url: "/api/mods/factorio/status",
|
||||
method: "POST",
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
this.setState({
|
||||
"loggedIn": data.data
|
||||
})
|
||||
},
|
||||
error: (jqXHR) => {
|
||||
let json_data = JSON.parse(jqXHR.responseJSON.data);
|
||||
|
||||
console.log("error checking login status", json_data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
factorioLogoutHandler(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/factorio/logout",
|
||||
method: "POST",
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
this.setState({
|
||||
loggedIn: data.data
|
||||
})
|
||||
},
|
||||
error: (jqXHR) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "error logging out of factorio",
|
||||
text: jqXHR.responseJSON.data,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadDownloadListSwalHandler() {
|
||||
let $checkedInput = $('input[name=version]:checked');
|
||||
let link = $checkedInput.data("link");
|
||||
let filename = $checkedInput.data("filename");
|
||||
let modName = $checkedInput.data("modid");
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: "/api/mods/install",
|
||||
dataType: "JSON",
|
||||
data: {
|
||||
link: link,
|
||||
filename: filename,
|
||||
modName: modName
|
||||
},
|
||||
success: (data) => {
|
||||
this.setState({
|
||||
installedMods: data.data.mods
|
||||
});
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "Mod installed",
|
||||
type: "success"
|
||||
});
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "An error occurred",
|
||||
text: err.toString(),
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//TODO remove modIdInput, when the factorio-mod-portal-api is fixed
|
||||
// all outcommented needs to be reimplemented, when it will work again
|
||||
loadDownloadList(e) {
|
||||
e.preventDefault();
|
||||
// let $button = $(e.target);
|
||||
// let $loader = $("<div class='loader'></div>");
|
||||
// $button.prepend($loader);
|
||||
let modId = $(e.target).find("input[name=modId]").val();
|
||||
// let modId = $button.data("modId");
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: "/api/mods/details",
|
||||
data: {
|
||||
modId: modId
|
||||
},
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
// $loader.remove();
|
||||
|
||||
let correctData = JSON.parse(data.data);
|
||||
|
||||
let checkboxes = [];
|
||||
let compatibleReleaseFound = false;
|
||||
|
||||
correctData.releases.reverse();
|
||||
correctData.releases.forEach((release) => {
|
||||
let incompatibleClass = "";
|
||||
let isChecked = false;
|
||||
|
||||
if(!SemVer.satisfies(this.props.factorioVersion, release.info_json.factorio_version + ".x")) {
|
||||
incompatibleClass = "incompatible";
|
||||
} else if(compatibleReleaseFound == false) {
|
||||
compatibleReleaseFound = true;
|
||||
isChecked = true;
|
||||
}
|
||||
|
||||
let date = new Date(release.released_at);
|
||||
|
||||
let singleBox = <tr className={incompatibleClass} key={release.version}>
|
||||
<td>
|
||||
<input type="radio"
|
||||
name="version"
|
||||
data-link={release.download_url}
|
||||
data-filename={release.file_name}
|
||||
data-modid={modId}
|
||||
defaultChecked={isChecked}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{release.version}
|
||||
</td>
|
||||
<td>
|
||||
{release.info_json.factorio_version}
|
||||
</td>
|
||||
<td>
|
||||
{date.toLocaleDateString()}
|
||||
</td>
|
||||
{/*<td>*/}
|
||||
{/*{release.downloads_count}*/}
|
||||
{/*</td>*/}
|
||||
</tr>;
|
||||
|
||||
checkboxes.push(singleBox);
|
||||
});
|
||||
|
||||
let table = <table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>
|
||||
Version
|
||||
</th>
|
||||
<th>
|
||||
Game Version
|
||||
</th>
|
||||
<th>
|
||||
Release Date
|
||||
</th>
|
||||
{/*<th>*/}
|
||||
{/*Downloads*/}
|
||||
{/*</th>*/}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{checkboxes}
|
||||
</tbody>
|
||||
</table>;
|
||||
|
||||
ReactSwalDanger.fire({
|
||||
title: "Choose version",
|
||||
html: table,
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Download it!",
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: this.loadDownloadListSwalHandler
|
||||
});
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/details', status, err.toString());
|
||||
// $loader.remove();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
toggleModHandler(e, updatesInProgress) {
|
||||
e.preventDefault();
|
||||
|
||||
if(updatesInProgress) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Toggle mods failed",
|
||||
text: "Can't toggle the mod, when an update is still in progress",
|
||||
type: "error"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let $button = $(e.target);
|
||||
let $row = $button.parents("tr");
|
||||
let modName = $row.data("mod-name");
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/toggle",
|
||||
method: "POST",
|
||||
data: {
|
||||
modName: modName
|
||||
},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
if(data.success) {
|
||||
this.mutex.lock(() => {
|
||||
let installedMods = this.state.installedMods;
|
||||
|
||||
$.each(installedMods, (k, v) => {
|
||||
if(v.name == modName) {
|
||||
installedMods[k].enabled = data.data;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
installedMods: installedMods
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/toggle', status, err.toString());
|
||||
ReactSwalNormal.fire({
|
||||
title: "Toggle Mod went wrong",
|
||||
text: err.toString(),
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteModHandler(e, updatesInProgress) {
|
||||
e.preventDefault();
|
||||
|
||||
if(updatesInProgress) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Delete failed!",
|
||||
text: "Can't delete the mod, when an update is still in progress",
|
||||
type: "error"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
let $button = $(e.target);
|
||||
let $row = $button.parents("tr");
|
||||
let modName = $row.data("mod-name");
|
||||
|
||||
ReactSwalDanger.fire({
|
||||
title: "Delete Mod?",
|
||||
text: "This will delete the mod and can break the save file",
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Delete it!",
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: () => {
|
||||
$.ajax({
|
||||
url: "/api/mods/delete",
|
||||
method: "POST",
|
||||
data: {
|
||||
modName: modName
|
||||
},
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
if (data.success) {
|
||||
this.mutex.lock(() => {
|
||||
ReactSwalNormal.fire({
|
||||
title: <p>Delete of mod {modName} successful</p>,
|
||||
type: "success"
|
||||
});
|
||||
let installedMods = this.state.installedMods;
|
||||
|
||||
installedMods.forEach((v, k) => {
|
||||
if (v.name == modName) {
|
||||
delete installedMods[k];
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
installedMods: installedMods
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/delete', status, err.toString());
|
||||
ReactSwalNormal.fire({
|
||||
title: "Delete Mod went wrong",
|
||||
text: err.toString(),
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteAllHandler(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
ReactSwalDanger.fire({
|
||||
title: "Delete Mod?",
|
||||
html: <p>This will delete ALL mods and can't be redone!<br/> Are you sure?</p>,
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Yes, Delete ALL!",
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: () => {
|
||||
$.ajax({
|
||||
url: "/api/mods/delete/all",
|
||||
method: "POST",
|
||||
dataType: "JSON",
|
||||
success: (data) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "ALL mods deleted successful",
|
||||
type: "success"
|
||||
})
|
||||
this.setState({
|
||||
installedMods: data.data
|
||||
});
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/delete/all', status, err.toString());
|
||||
ReactSwalNormal.fire({
|
||||
title: "Delete all mods went wrong",
|
||||
html: <p>{err.toString()}<br/>{jqXHR.responseJSON.data}</p>,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateModHandler(e, toggleUpdateStatus, removeVersionAvailableStatus) {
|
||||
e.preventDefault();
|
||||
|
||||
if(!this.state.loggedIn) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Update failed",
|
||||
text: "Please login into Factorio to update mod",
|
||||
type: "error",
|
||||
});
|
||||
|
||||
let $addModBox = $('#add-mod-box');
|
||||
if($addModBox.hasClass("collapsed-box")) {
|
||||
$addModBox.find(".box-header").click();
|
||||
}
|
||||
} else {
|
||||
let $button = $(e.currentTarget);
|
||||
let downloadUrl = $button.data("downloadUrl");
|
||||
let filename = $button.data("fileName");
|
||||
let modName = $button.parents("tr").data("modName");
|
||||
|
||||
//make button spinning
|
||||
toggleUpdateStatus();
|
||||
|
||||
$.ajax({
|
||||
url: "/api/mods/update",
|
||||
method: "POST",
|
||||
data: {
|
||||
downloadUrl: downloadUrl,
|
||||
filename: filename,
|
||||
modName: modName,
|
||||
},
|
||||
success: (data) => {
|
||||
toggleUpdateStatus();
|
||||
removeVersionAvailableStatus();
|
||||
|
||||
this.updateCountSubtract();
|
||||
|
||||
if(data.success) {
|
||||
this.mutex.lock(() => {
|
||||
ReactSwalNormal.fire({
|
||||
title: <p>Update of mod {modName} successful</p>,
|
||||
type: "success"
|
||||
})
|
||||
let installedMods = this.state.installedMods;
|
||||
|
||||
installedMods.forEach((v, k) => {
|
||||
if(v.name == modName) {
|
||||
installedMods[k] = data.data;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
installedMods: installedMods
|
||||
});
|
||||
|
||||
this.mutex.unlock();
|
||||
});
|
||||
}
|
||||
},
|
||||
error: (jqXHR, status, err) => {
|
||||
console.log('api/mods/delete', status, err.toString());
|
||||
toggleUpdateStatus();
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: "Update Mod went wrong",
|
||||
text: err.toString(),
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updatesAvailable() {
|
||||
this.setState({
|
||||
updatesAvailable: true
|
||||
});
|
||||
}
|
||||
|
||||
updateAllModsHandler(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let updateButtons = $('#manage-mods').find(".update-button");
|
||||
$.each(updateButtons, (k, v) => {
|
||||
v.click();
|
||||
});
|
||||
}
|
||||
|
||||
updateCountSubtract() {
|
||||
this.setState({
|
||||
updatesAvailable: this.state.updatesAvailable > 0 ? this.state.updatesAvailable - 1 : 0
|
||||
});
|
||||
}
|
||||
|
||||
updateCountAdd() {
|
||||
this.setState({
|
||||
updatesAvailable: this.state.updatesAvailable + 1
|
||||
});
|
||||
}
|
||||
|
||||
uploadModSuccessHandler(event, data) {
|
||||
this.setState({
|
||||
installedMods: data.response.data.mods
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="content-wrapper">
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Mods
|
||||
<small>Manage your mods</small>
|
||||
|
||||
<small className="float-sm-right">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/"><FontAwesomeIcon icon="tachometer-alt"/>Server Control</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active">
|
||||
<FontAwesomeIcon icon="pencil-alt"/>Mods
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<ModOverview
|
||||
{...this.state}
|
||||
{...this.props}
|
||||
loadDownloadList={this.loadDownloadList}
|
||||
submitFactorioLogin={this.handlerFactorioLogin}
|
||||
toggleMod={this.toggleModHandler}
|
||||
deleteMod={this.deleteModHandler}
|
||||
deleteAll={this.deleteAllHandler}
|
||||
updateMod={this.updateModHandler}
|
||||
updateCountAdd={this.updateCountAdd}
|
||||
uploadModSuccessHandler={this.uploadModSuccessHandler}
|
||||
updateAllMods={this.updateAllModsHandler}
|
||||
modContentClass={this}
|
||||
factorioLogoutHandler={this.factorioLogoutHandler}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ModsContent.propTypes = {
|
||||
factorioVersion: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ModsContent;
|
@ -1,76 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import FontAwesomeIcon from "../FontAwesomeIcon";
|
||||
|
||||
class CreateSave extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.createSaveFile = this.createSaveFile.bind(this);
|
||||
this.updateSavesList = this.updateSavesList.bind(this)
|
||||
this.state = {
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
updateSavesList() {
|
||||
this.props.getSaves();
|
||||
}
|
||||
|
||||
createSaveFile(e) {
|
||||
this.setState({loading: true});
|
||||
$.ajax({
|
||||
url: "/api/saves/create/" + this.refs.savename.value,
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
console.log(data);
|
||||
if (data.success === true) {
|
||||
alert(data.data)
|
||||
this.updateSavesList();
|
||||
this.setState({loading: false});
|
||||
} else {
|
||||
alert(data.data)
|
||||
this.setState({loading: false});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
var loadingOverlay
|
||||
if (this.state.loading) {
|
||||
loadingOverlay =
|
||||
<div className="overlay">
|
||||
<FontAwesomeIcon icon="refresh" className="fa-spin"/>
|
||||
</div>
|
||||
} else {
|
||||
loadingOverlay = ""
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="box" id="uploadsave">
|
||||
<div className="box-header">
|
||||
<h4 className="box-title">Create Save File</h4>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label htmlFor="savefile">Enter Savefile Name... </label>
|
||||
<input className="form-control" ref="savename" type="text" name="savefile" id="savefilename" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input className="form-control btn btn-default" type="button" ref="button" value="Create" onClick={this.createSaveFile} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{loadingOverlay}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CreateSave.propTypes = {
|
||||
getSaves: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default CreateSave
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import FontAwesomeIcon from "../FontAwesomeIcon";
|
||||
|
||||
class Save extends React.Component {
|
||||
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.toISOString().replace('T', ' ').split('.')[0]; //Outputs date as "YYYY-MM-DD HH:MM:SS" with zero-padding and 24h
|
||||
|
||||
return(
|
||||
<tr>
|
||||
<td>{this.props.save.name}</td>
|
||||
<td>{dateFmt}</td>
|
||||
<td>{saveSize} MB</td>
|
||||
<td>
|
||||
<a className="btn btn-default" href={saveLocation}>Download</a>
|
||||
</td>
|
||||
<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)}
|
||||
>
|
||||
<FontAwesomeIcon icon="trash"/>
|
||||
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Save.propTypes = {
|
||||
save: PropTypes.object.isRequired,
|
||||
saves: PropTypes.array.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
removeSave: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default Save
|
@ -1,116 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Save from './Save.jsx';
|
||||
import {ReactSwalDanger, ReactSwalNormal} from 'Utilities/customSwal';
|
||||
|
||||
class SavesList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.updateSavesList = this.updateSavesList.bind(this);
|
||||
this.removeSave = this.removeSave.bind(this);
|
||||
}
|
||||
|
||||
updateSavesList () {
|
||||
this.props.getSaves();
|
||||
}
|
||||
|
||||
removeSave(saveName, e) {
|
||||
let self = this;
|
||||
ReactSwalDanger.fire({
|
||||
title: "Are you sure?",
|
||||
html: <p>Save: {saveName} will be deleted</p>,
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
url: "/api/saves/rm/" + saveName,
|
||||
dataType: "json",
|
||||
success: (resp) => {
|
||||
if (resp.success === true) {
|
||||
resolve(resp.data);
|
||||
} else {
|
||||
reject("Unknown occurred!");
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
reject("Unknown occurred!");
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
}).then((result) => {
|
||||
if(result.value) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Deleted!",
|
||||
text: result.value,
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
self.updateSavesList();
|
||||
}).catch((result) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "An error occurred!",
|
||||
text: result,
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let savesList;
|
||||
if (this.props.saves.length === 0) {
|
||||
savesList = <tr></tr>
|
||||
} else {
|
||||
savesList = this.props.saves.map ( (save, i) => {
|
||||
return(
|
||||
<Save
|
||||
key={i}
|
||||
saves={this.props.saves}
|
||||
index={i}
|
||||
save={save}
|
||||
removeSave={this.removeSave}
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Save Files</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Filname</th>
|
||||
<th>Last Modified Time</th>
|
||||
<th>Filesize</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{savesList}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SavesList.propTypes = {
|
||||
saves: PropTypes.array.isRequired,
|
||||
dlSave: PropTypes.func.isRequired,
|
||||
getSaves: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SavesList
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class UploadSave extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.updateSavesList = this.updateSavesList.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
$("#savefile").fileinput({
|
||||
showPreview: false,
|
||||
uploadUrl: '/api/saves/upload',
|
||||
showCancel: false,
|
||||
allowedFileExtensions: ['zip'],
|
||||
theme: "fas",
|
||||
removeClass: "btn btn-default",
|
||||
uploadClass: "btn btn-default",
|
||||
}).on('filebatchuploadsuccess fileuploaded', this.updateSavesList);
|
||||
}
|
||||
|
||||
updateSavesList() {
|
||||
$('#savefile').fileinput('reset');
|
||||
this.props.getSaves();
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h4 className="box-title">Upload Save File</h4>
|
||||
</div>
|
||||
<div className="box-body">
|
||||
<input id="savefile" name="savefile" type="file" className="file"/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
UploadSave.propTypes = {
|
||||
getSaves: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default UploadSave
|
@ -1,79 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import SavesList from './Saves/SavesList.jsx';
|
||||
import CreateSave from './Saves/CreateSave.jsx';
|
||||
import UploadSave from './Saves/UploadSave.jsx';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class SavesContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.dlSave = this.dlSave.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getSaves();
|
||||
}
|
||||
|
||||
|
||||
dlSave(saveName) {
|
||||
$.ajax({
|
||||
url: "/api/saves/dl/" + saveName,
|
||||
dataType: "json",
|
||||
success: (data) => {
|
||||
console.log("Downloading save: " + saveName)
|
||||
},
|
||||
error: (xhr, status, err) => {
|
||||
console.log('api/mods/list', status, err.toString());
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="saves" className="content-wrapper">
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Saves
|
||||
<small>Factorio Save Files</small>
|
||||
|
||||
<small className="float-sm-right">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/"><FontAwesomeIcon icon="tachometer-alt" className="fa-fw"/>Server Control</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active">
|
||||
<FontAwesomeIcon icon="save" prefix="far"/>Saves
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</h1>
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<CreateSave
|
||||
getSaves={this.props.getSaves}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<UploadSave
|
||||
getSaves={this.props.getSaves}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SavesList
|
||||
{...this.state}
|
||||
saves={this.props.saves}
|
||||
dlSave={this.dlSave}
|
||||
getSaves={this.props.getSaves}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SavesContent
|
@ -1,205 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {ReactSwalNormal} from 'Utilities/customSwal';
|
||||
import FontAwesomeIcon from "../FontAwesomeIcon";
|
||||
|
||||
class ServerCtl extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.startServer = this.startServer.bind(this);
|
||||
this.stopServer = this.stopServer.bind(this);
|
||||
this.killServer = this.killServer.bind(this);
|
||||
|
||||
this.incrementPort = this.incrementPort.bind(this);
|
||||
this.decrementPort = this.decrementPort.bind(this);
|
||||
|
||||
this.state = {
|
||||
gameBindIP: "0.0.0.0",
|
||||
savefile: "",
|
||||
port: 34197,
|
||||
}
|
||||
}
|
||||
|
||||
startServer(e) {
|
||||
e.preventDefault();
|
||||
let serverSettings = {
|
||||
bindip: this.refs.gameBindIP.value,
|
||||
savefile: this.refs.savefile.value,
|
||||
port: Number(this.refs.port.value),
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/server/start",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(serverSettings),
|
||||
success: (resp) => {
|
||||
this.props.facServStatus();
|
||||
this.props.getStatus();
|
||||
if (resp.success === true) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Factorio server started",
|
||||
text: resp.data,
|
||||
type: "success"
|
||||
});
|
||||
} else {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Error starting Factorio server",
|
||||
text: resp.data,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
savefile: this.refs.savefile.value,
|
||||
port: Number(this.refs.port.value),
|
||||
});
|
||||
}
|
||||
|
||||
stopServer(e) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/api/server/stop",
|
||||
dataType: "json",
|
||||
success: (resp) => {
|
||||
this.props.facServStatus();
|
||||
this.props.getStatus();
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: resp.data
|
||||
});
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
killServer(e) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/api/server/kill",
|
||||
dataType: "json",
|
||||
success: (resp) => {
|
||||
this.props.facServStatus();
|
||||
this.props.getStatus();
|
||||
|
||||
ReactSwalNormal.fire({
|
||||
title: resp.data
|
||||
});
|
||||
}
|
||||
});
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
incrementPort() {
|
||||
let port = this.state.port + 1;
|
||||
this.setState({port: port})
|
||||
}
|
||||
|
||||
decrementPort() {
|
||||
let port = this.state.port - 1;
|
||||
this.setState({port: port})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="serverCtl" className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Server Control</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<form action="" onSubmit={this.startServer}>
|
||||
<div className="form-group">
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<button className="btn btn-block btn-success" type="submit">
|
||||
<FontAwesomeIcon icon="play" className="fa-fw"/>Start Factorio Server
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="col-md-4">
|
||||
<button className="btn btn-block btn-warning" type="button" onClick={this.stopServer}>
|
||||
<FontAwesomeIcon icon="stop" className="fa-fw"/>Stop & Save Factorio Server
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="col-md-4">
|
||||
<button className="btn btn-block btn-danger" type="button" onClick={this.killServer}>
|
||||
<FontAwesomeIcon icon="close" className="fa-fw"/>Stop Factorio Server without Saving
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<label>Select Save File</label>
|
||||
<select ref="savefile" className="form-control">
|
||||
{this.props.saves.map((save, i) => {
|
||||
return (
|
||||
<option key={save.name} value={save.name}>{save.name}</option>
|
||||
)
|
||||
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="box box-success advanced">
|
||||
<button type="button"
|
||||
className="btn btn-box-tool"
|
||||
data-toggle="collapse"
|
||||
data-target="#serverCtlAdvanced"
|
||||
aria-expanded="false"
|
||||
aria-controls="serverCtlAdvanced"
|
||||
>
|
||||
<div className="box-header with-border">
|
||||
<FontAwesomeIcon icon="plus" className="fa-fw"/>
|
||||
<div className="box-title">Advanced</div>
|
||||
</div>
|
||||
</button>
|
||||
<div id="serverCtlAdvanced" className="box-body collapse">
|
||||
<label htmlFor="port">Factorio Server IP</label>
|
||||
<div id="port" className="input-group">
|
||||
<input ref="gameBindIP"
|
||||
name="gameBindIP"
|
||||
id="gameBindIP"
|
||||
type="text"
|
||||
className="form-control"
|
||||
defaultValue={this.state.gameBindIP}
|
||||
placeholder={this.state.gameBindIP}/>
|
||||
</div>
|
||||
<label htmlFor="port">Factorio Server Port</label>
|
||||
<div id="port" className="input-group">
|
||||
<input ref="port"
|
||||
name="port"
|
||||
id="port"
|
||||
type="text"
|
||||
className="form-control"
|
||||
defaultValue={this.state.port}
|
||||
placeholder={this.state.port}
|
||||
/>
|
||||
<div className="input-group-btn">
|
||||
<button type="button" className="btn btn-primary" onClick={this.incrementPort}>
|
||||
<FontAwesomeIcon icon="arrow-up"/>
|
||||
</button>
|
||||
<button type="button" className="btn btn-primary" onClick={this.decrementPort}>
|
||||
<FontAwesomeIcon icon="arrow-down"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ServerCtl.propTypes = {
|
||||
facServStatus: PropTypes.func.isRequired,
|
||||
getStatus: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default ServerCtl
|
||||
|
@ -1,67 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ServerStatus extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.formatServerStatus = this.formatServerStatus.bind(this)
|
||||
}
|
||||
|
||||
capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
formatServerStatus(serverStatus) {
|
||||
var result = {};
|
||||
|
||||
if (serverStatus === "running") {
|
||||
result = <span className="badge badge-success">Running</span>;
|
||||
return result;
|
||||
} else if (serverStatus == "stopped") {
|
||||
result = <span className="badge badge-danger">Not Running</span>;
|
||||
return result;
|
||||
}
|
||||
|
||||
return serverStatus
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Server Status</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(this.props.serverStatus).map(function(key) {
|
||||
return(
|
||||
<tr key={key}>
|
||||
<td>{this.capitalizeFirstLetter(key)}</td>
|
||||
<td>{this.formatServerStatus(this.props.serverStatus[key])}</td>
|
||||
</tr>
|
||||
)
|
||||
}, this)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ServerStatus.propTypes = {
|
||||
serverStatus: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default ServerStatus
|
@ -1,97 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Link, NavLink} from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import FontAwesomeIcon from './FontAwesomeIcon.jsx';
|
||||
|
||||
class Sidebar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
if(this.props.serverRunning === "running") {
|
||||
var serverStatus = <Link to="/" className="d-block text-success"><FontAwesomeIcon icon="circle"/>Server Online</Link>
|
||||
} else {
|
||||
var serverStatus = <Link to="/" className="d-block text-danger"><FontAwesomeIcon icon="circle"/>Server Offline</Link>
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="main-sidebar sidebar-dark-primary elevation-4">
|
||||
<Link className="brand-link" to="/">
|
||||
<span className="logo-lg"><b>Factorio</b>SM</span>
|
||||
</Link>
|
||||
|
||||
<div className="sidebar">
|
||||
<div className="user-panel">
|
||||
<div className="image">
|
||||
<img src="./images/factorio.jpg" className="img-circle" alt="User Image"/>
|
||||
</div>
|
||||
<div className="info">
|
||||
<div className="text-white">Factorio Server Manager</div>
|
||||
{serverStatus}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*<form action="#" method="get" className="sidebar-form">
|
||||
<div className="input-group">
|
||||
<input type="text" name="q" className="form-control" placeholder="Search..."/>
|
||||
<span className="input-group-btn">
|
||||
<button type="submit" name="search" id="search-btn" className="btn btn-flat"><i
|
||||
className="fa fa-search"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>*/}
|
||||
|
||||
<nav className="mt-2">
|
||||
<ul className="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
||||
<li className="nav-header">MENU</li>
|
||||
<li className="nav-item">
|
||||
<NavLink exact to="/" activeClassName="active" className="nav-link">
|
||||
<FontAwesomeIcon icon="tachometer-alt" className="nav-icon"/><p>Server Control</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/mods" activeClassName="active" className="nav-link">
|
||||
<FontAwesomeIcon icon="pencil-alt" className="nav-icon"/><p>Mods</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/logs" activeClassName="active" className="nav-link">
|
||||
<FontAwesomeIcon icon="file-alt" className="nav-icon" prefix="far"/><p>Logs</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/saves" activeClassName="active" className="nav-link">
|
||||
<FontAwesomeIcon icon="save" className="nav-icon" prefix="far"/><p>Saves</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/config" activeClassName="active" className="nav-link">
|
||||
<FontAwesomeIcon icon="cogs" className="nav-icon"/><p>Game Configuration</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/settings" activeClassName="active" className="nav-link">
|
||||
<FontAwesomeIcon icon="cog" className="nav-icon"/><p>Settings</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink to="/console" activeClassName="active" className="nav-link">
|
||||
<FontAwesomeIcon icon="terminal" className="nav-icon"/><p>Console</p>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Sidebar.propTypes = {
|
||||
serverStatus: PropTypes.func.isRequired,
|
||||
serverRunning: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default Sidebar
|
@ -1,130 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {ReactSwalNormal} from 'Utilities/customSwal';
|
||||
import FontAwesomeIcon from "../FontAwesomeIcon";
|
||||
|
||||
class AddUser extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.createUser = this.createUser.bind(this);
|
||||
this.validateEmail = this.validateEmail.bind(this);
|
||||
}
|
||||
|
||||
validateEmail(email) {
|
||||
var re = /\S+@\S+\.\S+/;
|
||||
return re.test(email)
|
||||
}
|
||||
|
||||
createUser(e) {
|
||||
e.preventDefault();
|
||||
console.log(this.refs);
|
||||
let user = {
|
||||
username: this.refs.username.value,
|
||||
// Add handler for listing roles
|
||||
role: "admin",
|
||||
password: this.refs.password.value,
|
||||
email: this.refs.email.value,
|
||||
}
|
||||
if (user.password !== this.refs.passwordConfirm.value) {
|
||||
console.log("passwords do not match");
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.validateEmail(user.email)) {
|
||||
console.log("invalid email address");
|
||||
return
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/user/add",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(user),
|
||||
success: (resp) => {
|
||||
if (resp.success === true) {
|
||||
ReactSwalNormal.fire({
|
||||
title: <p>User: {user.username} added successfully</p>,
|
||||
type: "success"
|
||||
});
|
||||
this.props.listUsers();
|
||||
} else {
|
||||
ReactSwalNormal.fire({
|
||||
title: <p>Error adding user: {resp.data}</p>,
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Manage Users</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<h4>Add user</h4>
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<form action="" onSubmit={this.createUser}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
ref="username"
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="username"
|
||||
placeholder="Enter username"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="email">Email address</label>
|
||||
<input
|
||||
ref="email"
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="email"
|
||||
placeholder="Enter email"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Password</label>
|
||||
<input
|
||||
ref="password"
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="password"
|
||||
placeholder="Enter password"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Password confirmation</label>
|
||||
<input
|
||||
ref="passwordConfirm"
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="password"
|
||||
placeholder="Enter password again"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-block btn-success" type="submit">
|
||||
<FontAwesomeIcon icon="plus" className="fa-fw"/>
|
||||
Add User
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AddUser.propTypes = {
|
||||
listUsers: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default AddUser
|
@ -1,103 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {ReactSwalDanger, ReactSwalNormal} from 'Utilities/customSwal';
|
||||
|
||||
class UserTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.removeUser = this.removeUser.bind(this);
|
||||
}
|
||||
|
||||
removeUser(user) {
|
||||
ReactSwalDanger.fire({
|
||||
title: "Are you sure?",
|
||||
html: <p>User {user} will be deleted!</p>,
|
||||
type: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Yes, delete it!",
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
user = {username: user};
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/user/remove",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(user),
|
||||
success: (resp) => {
|
||||
if (resp.success === true) {
|
||||
resolve(resp.data);
|
||||
} else {
|
||||
reject("Unknown error");
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
reject("Unknown error");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
ReactSwalNormal.fire({
|
||||
title: "Deleted!",
|
||||
text: result.value,
|
||||
type: "success"
|
||||
});
|
||||
this.props.listUsers();
|
||||
}
|
||||
}).catch((result) => {
|
||||
ReactSwalNormal.fire({
|
||||
title: "An error occurred!",
|
||||
text: result,
|
||||
type: "error"
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="box">
|
||||
<div className="box-header">
|
||||
<h3 className="box-title">Users</h3>
|
||||
</div>
|
||||
|
||||
<div className="box-body">
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Role</th>
|
||||
<th>Email</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.users.map( (user, i) => {
|
||||
return(
|
||||
<tr key={user.username}>
|
||||
<td>{user.username}</td>
|
||||
<td>{user.role}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>
|
||||
<button className="btn btn-danger" onClick={()=>{this.removeUser(user.username)}}>Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
UserTable.propTypes = {
|
||||
users: PropTypes.array.isRequired,
|
||||
listUsers: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default UserTable
|
@ -1,72 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import UserTable from './Users/UserTable.jsx';
|
||||
import AddUser from './Users/AddUser.jsx';
|
||||
import FontAwesomeIcon from "./FontAwesomeIcon";
|
||||
|
||||
class UsersContent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.listUsers = this.listUsers.bind(this);
|
||||
this.state = {
|
||||
users: [],
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.listUsers();
|
||||
}
|
||||
|
||||
listUsers() {
|
||||
// $.ajax({
|
||||
// type: "GET",
|
||||
// url: "/api/user/list",
|
||||
// dataType: "json",
|
||||
// success: (resp) => {
|
||||
// if (resp.success === true) {
|
||||
// this.setState({users: resp.data})
|
||||
// } else {
|
||||
// console.log("error listing users.")
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
this.setState({users: []});
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div className="content-wrapper">
|
||||
<section className="content-header">
|
||||
<h1>
|
||||
Settings
|
||||
<small>Manage Factorio Server Manager settings</small>
|
||||
|
||||
<small className="float-sm-right">
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link to="/"><FontAwesomeIcon icon="tachometer-alt"/>Server Control</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active">
|
||||
<FontAwesomeIcon icon="cog"/>Settings
|
||||
</li>
|
||||
</ol>
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
</section>
|
||||
|
||||
<section className="content">
|
||||
<UserTable
|
||||
users={this.state.users}
|
||||
listUsers={this.listUsers}
|
||||
/>
|
||||
<AddUser
|
||||
listUsers={this.listUsers}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersContent;
|
@ -1,35 +0,0 @@
|
||||
import Button from "../elements/Button";
|
||||
import React from "react";
|
||||
import {useForm} from "react-hook-form";
|
||||
import saves from "../../api/resources/saves";
|
||||
|
||||
|
||||
const UploadSaveForm = ({onSuccess}) => {
|
||||
|
||||
const {register, handleSubmit, errors} = useForm();
|
||||
const onSubmit = async data => {
|
||||
const res = await saves.upload(data.savefile);
|
||||
if (res.success) {
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-6">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="password">
|
||||
Savefile Name
|
||||
</label>
|
||||
<input
|
||||
className="shadow appearance-none w-full py-2 px-3 text-black"
|
||||
ref={register({required: true})}
|
||||
name="savefile"
|
||||
id="savefile" type="file"/>
|
||||
{errors.savefile && <span className="block text-red">Savefile Name is required</span>}
|
||||
</div>
|
||||
<Button type="success">Upload Save</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default UploadSaveForm;
|
45
ui/App/views/Console.jsx
Normal file
45
ui/App/views/Console.jsx
Normal file
@ -0,0 +1,45 @@
|
||||
import Panel from "../components/Panel";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import Socket from "../../api/socket"
|
||||
|
||||
const Console = () => {
|
||||
|
||||
const socket = new Socket();
|
||||
|
||||
const [logs, setLogs] = useState([]);
|
||||
const consoleInput = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
// todo: maybe check for ready state
|
||||
socket.emit("log subscribe")
|
||||
socket.on('log update', log => {
|
||||
console.log(log)
|
||||
setLogs((logs) => {
|
||||
[...logs].push(log)
|
||||
});
|
||||
})
|
||||
consoleInput.current?.focus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
title="Console"
|
||||
content={
|
||||
<>
|
||||
<ul>
|
||||
{logs?.map(log => (<li key={log}>{log}</li>))}
|
||||
</ul>
|
||||
<input type="text" ref={consoleInput} className="shadow appearance-none border w-full py-2 px-3 text-black" onKeyPress={e => {
|
||||
if (e.key === "Enter") {
|
||||
socket.emit("command send", consoleInput.current.value);
|
||||
consoleInput.current.value = ""
|
||||
}
|
||||
}
|
||||
}/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Console;
|
@ -1,6 +1,6 @@
|
||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||
import Panel from "../elements/Panel";
|
||||
import Button from "../elements/Button";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import Panel from "../components/Panel";
|
||||
import Button from "../components/Button";
|
||||
import server from "../../api/resources/server";
|
||||
|
||||
const Controls = ({serverStatus, updateServerStatus, s}) => {
|
||||
|
13
ui/App/views/GameSettings.jsx
Normal file
13
ui/App/views/GameSettings.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import Panel from "../components/Panel";
|
||||
import React from "react";
|
||||
|
||||
const GameSettings = () => {
|
||||
return (
|
||||
<Panel
|
||||
title="Game Settings"
|
||||
content={null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default GameSettings;
|
13
ui/App/views/Help.jsx
Normal file
13
ui/App/views/Help.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import Panel from "../components/Panel";
|
||||
import React from "react";
|
||||
|
||||
const Help = () => {
|
||||
return (
|
||||
<Panel
|
||||
title="Help"
|
||||
content={null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Help;
|
@ -1,9 +1,9 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import {useForm} from "react-hook-form";
|
||||
import user from "../../api/resources/user";
|
||||
import Button from "../elements/Button";
|
||||
import Button from "../components/Button";
|
||||
import {useHistory, useLocation} from "react-router";
|
||||
import Panel from "../elements/Panel";
|
||||
import Panel from "../components/Panel";
|
||||
|
||||
const Login = ({handleLogin}) => {
|
||||
const {register, handleSubmit, errors} = useForm();
|
||||
@ -44,7 +44,7 @@ const Login = ({handleLogin}) => {
|
||||
id="username"
|
||||
name="username"
|
||||
type="text" placeholder="Username"/>
|
||||
{errors.password && <span className="block text-red">Username is required</span>}
|
||||
{errors.username && <span className="block text-red">Username is required</span>}
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="password">
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import Layout from "../components/Layout";
|
||||
import log from "../../api/resources/log";
|
||||
import Panel from "../elements/Panel";
|
||||
import Panel from "../components/Panel";
|
||||
|
||||
const Logs = () => {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Panel from "../elements/Panel";
|
||||
import Panel from "../components/Panel";
|
||||
import React from "react";
|
||||
|
||||
const Mods = () => {
|
||||
|
@ -1,16 +1,14 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import saveClient from "../../api/resources/saves";
|
||||
import Panel from "../elements/Panel";
|
||||
import ButtonLink from "../elements/ButtonLink";
|
||||
import Button from "../elements/Button";
|
||||
import {useForm} from "react-hook-form";
|
||||
import CreateSaveForm from "../forms/CreateSaveForm";
|
||||
import UploadSaveForm from "../forms/UploadSaveForm";
|
||||
import saveClient from "../../../api/resources/saves";
|
||||
import Panel from "../../components/Panel";
|
||||
import ButtonLink from "../../components/ButtonLink";
|
||||
import Button from "../../components/Button";
|
||||
import CreateSaveForm from "./components/CreateSaveForm";
|
||||
import UploadSaveForm from "./components/UploadSaveForm";
|
||||
|
||||
const Saves = ({serverStatus}) => {
|
||||
|
||||
const [saves, setSaves] = useState([]);
|
||||
const {register, handleSubmit, errors} = useForm();
|
||||
|
||||
const updateList = async () => {
|
||||
const res = await saveClient.list();
|
||||
@ -48,7 +46,7 @@ const Saves = ({serverStatus}) => {
|
||||
<Panel
|
||||
title="Upload Save"
|
||||
className="w-1/2 ml-3"
|
||||
content={<UploadSaveForm/>}
|
||||
content={<UploadSaveForm onSuccess={updateList}/>}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,15 +1,16 @@
|
||||
import {useForm} from "react-hook-form";
|
||||
import Button from "../elements/Button";
|
||||
import Button from "../../../components/Button";
|
||||
import React from "react";
|
||||
import saves from "../../api/resources/saves";
|
||||
import saves from "../../../../api/resources/saves";
|
||||
|
||||
const CreateSaveForm = ({onSuccess}) => {
|
||||
const {register, handleSubmit, errors} = useForm();
|
||||
|
||||
|
||||
const onSubmit = async data => {
|
||||
const onSubmit = async (data, e) => {
|
||||
const res = await saves.create(data.savefile);
|
||||
if (res.success) {
|
||||
e.target.reset();
|
||||
onSuccess();
|
||||
}
|
||||
};
|
42
ui/App/views/Saves/components/UploadSaveForm.jsx
Normal file
42
ui/App/views/Saves/components/UploadSaveForm.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import Button from "../../../components/Button";
|
||||
import React, {useState} from "react";
|
||||
import {useForm} from "react-hook-form";
|
||||
import saves from "../../../../api/resources/saves";
|
||||
|
||||
|
||||
const UploadSaveForm = ({onSuccess}) => {
|
||||
const {register, handleSubmit, errors} = useForm();
|
||||
const [fileName, setFileName] = useState('Select File ...');
|
||||
|
||||
const onSubmit = async (data, e) => {
|
||||
const res = await saves.upload(data.savefile);
|
||||
if (res.success) {
|
||||
e.target.reset();
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-6">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="password">
|
||||
Save File
|
||||
</label>
|
||||
<div className="relative bg-white shadow text-black w-full">
|
||||
<input
|
||||
className="absolute left-0 top-0 opacity-0 cursor-pointer w-full h-full"
|
||||
ref={register({required: true})}
|
||||
onChange={e => setFileName(e.currentTarget.files[0].name)}
|
||||
name="savefile"
|
||||
id="savefile" type="file"/>
|
||||
<div className="px-2 py-3">{fileName}</div>
|
||||
</div>
|
||||
|
||||
{errors.savefile && <span className="block text-red">Savefile is required</span>}
|
||||
</div>
|
||||
<Button type="success" isSubmit={true}>Upload</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default UploadSaveForm;
|
13
ui/App/views/ServerSettings.jsx
Normal file
13
ui/App/views/ServerSettings.jsx
Normal file
@ -0,0 +1,13 @@
|
||||
import Panel from "../components/Panel";
|
||||
import React from "react";
|
||||
|
||||
const ServerSettings = () => {
|
||||
return (
|
||||
<Panel
|
||||
title="Server Settings"
|
||||
content={null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServerSettings;
|
65
ui/App/views/UserManagement/UserManagment.jsx
Normal file
65
ui/App/views/UserManagement/UserManagment.jsx
Normal file
@ -0,0 +1,65 @@
|
||||
import Panel from "../../components/Panel";
|
||||
import React, {useCallback, useEffect, useState} from "react";
|
||||
import Button from "../../components/Button";
|
||||
import user from "../../../api/resources/user";
|
||||
import CreateUserForm from "./components/CreateUserForm";
|
||||
|
||||
const UserManagement = () => {
|
||||
|
||||
const [users, setUsers] = useState([]);
|
||||
|
||||
const updateList = useCallback(async () => {
|
||||
const res = await user.list();
|
||||
if (res.success) {
|
||||
setUsers(res.data);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const deleteUser = useCallback(async (username) => {
|
||||
user.delete(username)
|
||||
.then(updateList);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateList()
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Panel
|
||||
title="List of Users"
|
||||
content={
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="text-left py-1">
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th>Email</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map(user =>
|
||||
<tr className="py-1" key={user.username}>
|
||||
<td className="pr-4">{user.username}</td>
|
||||
<td className="pr-4">{user.role}</td>
|
||||
<td className="pr-4">{user.email}</td>
|
||||
<td>
|
||||
<Button size="sm" onClick={() => deleteUser(user.username)} type="danger">Delete</Button>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
className="mb-4"
|
||||
/>
|
||||
<Panel
|
||||
title="Create User"
|
||||
content={<CreateUserForm updateUserList={updateList}/>}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserManagement;
|
82
ui/App/views/UserManagement/components/CreateUserForm.jsx
Normal file
82
ui/App/views/UserManagement/components/CreateUserForm.jsx
Normal file
@ -0,0 +1,82 @@
|
||||
import {useForm} from "react-hook-form";
|
||||
import React from "react";
|
||||
import user from "../../../../api/resources/user";
|
||||
import Button from "../../../components/Button";
|
||||
|
||||
const CreateUserForm = ({updateUserList}) => {
|
||||
|
||||
const {register, handleSubmit, errors, watch} = useForm();
|
||||
const password = watch('password');
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const res = user.add(data);
|
||||
if (res.success) {
|
||||
updateUserList()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-4">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="username">
|
||||
Username
|
||||
</label>
|
||||
<input className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
ref={register({required: true})}
|
||||
id="username"
|
||||
name="username"
|
||||
type="text" placeholder="Username"/>
|
||||
{errors.username && <span className="block text-red">Username is required</span>}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="username">
|
||||
Role
|
||||
</label>
|
||||
<input className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
ref={register({required: true})}
|
||||
id="role"
|
||||
name="role"
|
||||
value="admin"
|
||||
disabled={true}
|
||||
type="text" placeholder="Role"/>
|
||||
{errors.role && <span className="block text-red">Role is required</span>}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="username">
|
||||
Email
|
||||
</label>
|
||||
<input className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
ref={register({required: true})}
|
||||
id="email"
|
||||
name="email"
|
||||
type="text" placeholder="Email"/>
|
||||
{errors.email && <span className="block text-red">Email is required</span>}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="username">
|
||||
Password
|
||||
</label>
|
||||
<input className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
ref={register({required: true})}
|
||||
id="password"
|
||||
name="password"
|
||||
type="password" placeholder="Password"/>
|
||||
{errors.password && <span className="block text-red">Password is required</span>}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-white text-sm font-bold mb-2" htmlFor="username">
|
||||
Password Confirmation
|
||||
</label>
|
||||
<input className="shadow appearance-none border w-full py-2 px-3 text-black"
|
||||
ref={register({required: true, validate: confirmation => confirmation === password})}
|
||||
id="password_confirmation"
|
||||
name="password_confirmation"
|
||||
type="password" placeholder="Password Confirmation"/>
|
||||
{errors.password_confirmation && <span className="block text-red">Password Confirmation is required and must match the Password</span>}
|
||||
</div>
|
||||
<Button isSubmit={true} type="success">Save</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateUserForm;
|
@ -1,20 +0,0 @@
|
||||
import Panel from "../elements/Panel";
|
||||
import React from "react";
|
||||
|
||||
const UserManagement = () => {
|
||||
return (
|
||||
<>
|
||||
<Panel
|
||||
title="List of Users"
|
||||
content="test"
|
||||
className="mb-4"
|
||||
/>
|
||||
<Panel
|
||||
title="Create User"
|
||||
content="test"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserManagement;
|
@ -12,5 +12,16 @@ export default {
|
||||
create: async (name) => {
|
||||
const response = await client.get(`/api/saves/create/${name}`);
|
||||
return response.data;
|
||||
},
|
||||
upload: async (data) => {
|
||||
let formData = new FormData();
|
||||
formData.append("savefile", data[0]);
|
||||
|
||||
const response = await client.post(`/api/saves/upload`, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
}
|
@ -12,5 +12,17 @@ export default {
|
||||
logout: async () => {
|
||||
const response = await client.get('/api/logout');
|
||||
return response.data;
|
||||
},
|
||||
list: async () => {
|
||||
const response = await client.get('/api/user/list');
|
||||
return response.data;
|
||||
},
|
||||
add: async (user) => {
|
||||
const response = await client.post('/api/user/add', user);
|
||||
return response.data;
|
||||
},
|
||||
delete: async (username) => {
|
||||
const response = await client.post('/api/user/remove', JSON.stringify({username}));
|
||||
return response.data;
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
import EventEmitter from 'events';
|
||||
|
||||
class Socket {
|
||||
constructor(ws, ee = new EventEmitter()){
|
||||
this.ws = ws;
|
||||
this.ee = ee;
|
||||
ws.onmessage = this.message.bind(this);
|
||||
ws.onopen = this.open.bind(this);
|
||||
ws.onclose = this.close.bind(this);
|
||||
constructor(){
|
||||
let ws_scheme = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
this.ws = new WebSocket(ws_scheme + "://" + window.location.host + "/ws");
|
||||
|
||||
this.ee = new EventEmitter();
|
||||
this.ws.onmessage = this.message.bind(this);
|
||||
this.ws.onopen = this.open.bind(this);
|
||||
this.ws.onclose = this.close.bind(this);
|
||||
|
||||
this.opened = false;
|
||||
}
|
||||
@ -17,7 +19,7 @@ class Socket {
|
||||
this.ee.removeListener(name, fn);
|
||||
}
|
||||
emit(name, data){
|
||||
if(this.ws.readyState == WebSocket.OPEN) {
|
||||
if(this.ws.readyState === WebSocket.OPEN) {
|
||||
const message = JSON.stringify({name, data});
|
||||
this.ws.send(message);
|
||||
}
|
||||
@ -27,7 +29,6 @@ class Socket {
|
||||
message(e){
|
||||
try{
|
||||
let message = JSON.parse(e.data);
|
||||
// console.log(message.name, message.data);
|
||||
this.ee.emit(message.name, message.data);
|
||||
}
|
||||
catch(err){
|
@ -1,20 +0,0 @@
|
||||
import Swal from 'sweetalert2/dist/sweetalert2';
|
||||
import withReactContent from 'sweetalert2-react-content';
|
||||
|
||||
const ReactSwal = withReactContent(Swal);
|
||||
const ReactSwalTemp = ReactSwal.mixin({
|
||||
confirmButtonClass: "swal-btn btn-primary",
|
||||
cancelButtonClass: "swal-btn btn-secondary",
|
||||
customClass: "swal-design",
|
||||
buttonsStyling: false,
|
||||
|
||||
allowOutsideClick: () => !ReactSwalDanger.isLoading()
|
||||
});
|
||||
|
||||
export const ReactSwalNormal = ReactSwalTemp.mixin({
|
||||
|
||||
});
|
||||
|
||||
export const ReactSwalDanger = ReactSwalTemp.mixin({
|
||||
confirmButtonClass: "swal-btn btn-danger"
|
||||
});
|
Loading…
Reference in New Issue
Block a user