mirror of
https://github.com/Dahlgren/arma-server-web-admin.git
synced 2024-08-30 17:22:10 +00:00
React App
This commit is contained in:
parent
7b6d0c6425
commit
2a3814c138
5
app.js
5
app.js
@ -75,4 +75,9 @@ if (require.main === module) {
|
||||
server.listen(config.port, config.host)
|
||||
}
|
||||
|
||||
// Serve main HTML file for all other requests
|
||||
app.get('*', function (req, res) {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'))
|
||||
})
|
||||
|
||||
module.exports = app
|
||||
|
16
package.json
16
package.json
@ -13,17 +13,23 @@
|
||||
},
|
||||
"standard": {
|
||||
"env": [
|
||||
"browser",
|
||||
"mocha"
|
||||
]
|
||||
],
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"arma-server": "0.0.10",
|
||||
"async": "^0.9.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^6.4.1",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"backbone": "1.3.3",
|
||||
"backbone.bootstrap-modal": "https://github.com/powmedia/backbone.bootstrap-modal/archive/632210077c2424be2ee6ea2aafe0d3fe016ae524.tar.gz",
|
||||
"backbone.marionette": "2.4.7",
|
||||
"body-parser": "^1.17.1",
|
||||
"bootstrap": "^3.4.0",
|
||||
"bootstrap": "^4.1.3",
|
||||
"css-loader": "0.17.0",
|
||||
"express": "^4.15.2",
|
||||
"express-basic-auth": "^1.0.1",
|
||||
@ -39,6 +45,11 @@
|
||||
"morgan": "^1.8.1",
|
||||
"multer": "^1.3.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"reactstrap": "^8.9.0",
|
||||
"serve-static": "^1.12.1",
|
||||
"slugify": "^1.1.0",
|
||||
"socket.io": "2.1.1",
|
||||
@ -53,6 +64,7 @@
|
||||
"winser": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.3",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.3",
|
||||
"standard": "^14.3.3",
|
||||
|
@ -5,14 +5,11 @@
|
||||
<title>Arma Server Admin</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="bundle.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
<script src="/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
41
react/App.jsx
Normal file
41
react/App.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React, { Component } from 'react'
|
||||
import Navigation from './Navigation.jsx'
|
||||
|
||||
import MissionsContext from './contexts/Missions'
|
||||
import ModsContext from './contexts/Mods'
|
||||
import ServersContext from './contexts/Servers'
|
||||
|
||||
export default class App extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
missions: [],
|
||||
mods: [],
|
||||
servers: []
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
/* global io */
|
||||
const socket = io.connect()
|
||||
socket.on('missions', missions => this.setState({ missions }))
|
||||
socket.on('mods', mods => this.setState({ mods }))
|
||||
socket.on('servers', servers => this.setState({ servers }))
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div id='app'>
|
||||
<Navigation />
|
||||
<MissionsContext.Provider value={this.state.missions}>
|
||||
<ModsContext.Provider value={this.state.mods}>
|
||||
<ServersContext.Provider value={this.state.servers}>
|
||||
{this.props.children}
|
||||
</ServersContext.Provider>
|
||||
</ModsContext.Provider>
|
||||
</MissionsContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
53
react/Navigation.jsx
Normal file
53
react/Navigation.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap'
|
||||
import { NavLink as RRNavLink } from 'react-router-dom'
|
||||
|
||||
export default class Navigation extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.handleNavbarToggle = this.handleNavbarToggle.bind(this)
|
||||
this.state = {
|
||||
isOpen: false
|
||||
}
|
||||
}
|
||||
|
||||
handleNavbarToggle () {
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Navbar color='light' light expand='md'>
|
||||
<NavbarBrand>Arma Admin</NavbarBrand>
|
||||
<NavbarToggler onClick={this.handleNavbarToggle} />
|
||||
<Collapse isOpen={this.state.isOpen} navbar>
|
||||
<Nav className='ml-auto' navbar>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} to='/logs'>
|
||||
Logs
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} to='/missions'>
|
||||
Missions
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} to='/mods'>
|
||||
Mods
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} to='/servers'>
|
||||
Servers
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
</Collapse>
|
||||
</Navbar>
|
||||
)
|
||||
}
|
||||
}
|
3
react/contexts/Missions.js
Normal file
3
react/contexts/Missions.js
Normal file
@ -0,0 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
export default React.createContext([])
|
3
react/contexts/Mods.js
Normal file
3
react/contexts/Mods.js
Normal file
@ -0,0 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
export default React.createContext([])
|
3
react/contexts/Servers.js
Normal file
3
react/contexts/Servers.js
Normal file
@ -0,0 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
export default React.createContext([])
|
25
react/index.jsx
Normal file
25
react/index.jsx
Normal file
@ -0,0 +1,25 @@
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
|
||||
import App from './App.jsx'
|
||||
import Logs from './pages/Logs.jsx'
|
||||
import Missions from './pages/Missions.jsx'
|
||||
import Mods from './pages/Mods.jsx'
|
||||
import Server from './pages/Server.jsx'
|
||||
import Servers from './pages/Servers.jsx'
|
||||
|
||||
render((
|
||||
<BrowserRouter>
|
||||
<App>
|
||||
<Switch>
|
||||
<Route exact path='/logs' component={Logs} />
|
||||
<Route exact path='/missions' component={Missions} />
|
||||
<Route exact path='/mods' component={Mods} />
|
||||
<Route exact path='/servers' component={Servers} />
|
||||
<Route path='/servers/:id' component={Server} />
|
||||
<Redirect path='*' to='/servers' />
|
||||
</Switch>
|
||||
</App>
|
||||
</BrowserRouter>
|
||||
), document.getElementById('content'))
|
49
react/pages/Logs.jsx
Normal file
49
react/pages/Logs.jsx
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Table } from 'reactstrap'
|
||||
|
||||
export default class Logs extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { logs: [] }
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.requestLogs()
|
||||
}
|
||||
|
||||
requestLogs () {
|
||||
fetch('/api/logs')
|
||||
.then(response => response.json())
|
||||
.then(data => this.setState({ logs: data }))
|
||||
}
|
||||
|
||||
renderRow (log) {
|
||||
return (
|
||||
<tr key={log.name}>
|
||||
<td>{log.name}</td>
|
||||
<td>{log.formattedSize}</td>
|
||||
<td>{new Date(log.created).toLocaleString()}</td>
|
||||
<td>{new Date(log.modified).toLocaleString()}</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mission</th>
|
||||
<th>Size</th>
|
||||
<th>Created</th>
|
||||
<th>Updated</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.state.logs.map(this.renderRow)}
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
}
|
38
react/pages/Missions.jsx
Normal file
38
react/pages/Missions.jsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Table } from 'reactstrap'
|
||||
|
||||
import MissionsContext from '../contexts/Missions'
|
||||
|
||||
export default class Missions extends Component {
|
||||
renderRow (mission) {
|
||||
return (
|
||||
<tr key={mission.name}>
|
||||
<td>{mission.name}</td>
|
||||
<td>{mission.sizeFormatted}</td>
|
||||
<td>{mission.dateModified}</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mission</th>
|
||||
<th>Size</th>
|
||||
<th>Updated</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<MissionsContext.Consumer>
|
||||
{missions => (
|
||||
missions.map(this.renderRow)
|
||||
)}
|
||||
</MissionsContext.Consumer>
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
}
|
34
react/pages/Mods.jsx
Normal file
34
react/pages/Mods.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Table } from 'reactstrap'
|
||||
|
||||
import ModsContext from '../contexts/Mods'
|
||||
|
||||
export default class Mods extends Component {
|
||||
renderRow (mod) {
|
||||
return (
|
||||
<tr key={mod.name}>
|
||||
<td>{mod.name}</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mod</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ModsContext.Consumer>
|
||||
{mods => (
|
||||
mods.map(this.renderRow)
|
||||
)}
|
||||
</ModsContext.Consumer>
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
}
|
112
react/pages/Server.jsx
Normal file
112
react/pages/Server.jsx
Normal file
@ -0,0 +1,112 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Nav, NavItem, NavLink } from 'reactstrap'
|
||||
import { Route, Switch } from 'react-router'
|
||||
import { NavLink as RRNavLink } from 'react-router-dom'
|
||||
|
||||
import ServersContext from '../contexts/Servers'
|
||||
|
||||
class ServerInfo extends Component {
|
||||
render () {
|
||||
return 'ServerInfo'
|
||||
}
|
||||
}
|
||||
|
||||
class ServerMissions extends Component {
|
||||
render () {
|
||||
return 'ServerMissions'
|
||||
}
|
||||
}
|
||||
|
||||
class ServerMods extends Component {
|
||||
render () {
|
||||
return 'ServerMods'
|
||||
}
|
||||
}
|
||||
|
||||
class ServerPlayers extends Component {
|
||||
render () {
|
||||
return 'ServerPlayers'
|
||||
}
|
||||
}
|
||||
|
||||
class ServerSettings extends Component {
|
||||
render () {
|
||||
return 'ServerSettings'
|
||||
}
|
||||
}
|
||||
|
||||
export default class Server extends Component {
|
||||
renderServer (server) {
|
||||
const { path, url } = this.props.match
|
||||
return (
|
||||
<>
|
||||
<Nav tabs>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} exact to={`${url}`}>
|
||||
Info
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} exact to={`${url}/mods`}>
|
||||
Mods
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} exact to={`${url}/missions`}>
|
||||
Missions
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} exact to={`${url}/players`}>
|
||||
Players
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink activeClassName='active' tag={RRNavLink} exact to={`${url}/settings`}>
|
||||
Settings
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
<Switch>
|
||||
<Route exact path={`${path}`}>
|
||||
<ServerInfo />
|
||||
</Route>
|
||||
<Route exact path={`${path}/missions`}>
|
||||
<ServerMissions />
|
||||
</Route>
|
||||
<Route exact path={`${path}/mods`}>
|
||||
<ServerMods />
|
||||
</Route>
|
||||
<Route exact path={`${path}/players`}>
|
||||
<ServerPlayers />
|
||||
</Route>
|
||||
<Route exact path={`${path}/settings`}>
|
||||
<ServerSettings />
|
||||
</Route>
|
||||
</Switch>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
renderNotFound () {
|
||||
return 'Not Found'
|
||||
}
|
||||
|
||||
render () {
|
||||
const serverId = this.props.match.params.id
|
||||
|
||||
return (
|
||||
<ServersContext.Consumer>
|
||||
{servers => {
|
||||
const server = servers.find(server => server.id === serverId)
|
||||
|
||||
if (server) {
|
||||
return this.renderServer(server)
|
||||
}
|
||||
|
||||
return this.renderNotFound()
|
||||
}}
|
||||
</ServersContext.Consumer>
|
||||
)
|
||||
}
|
||||
}
|
81
react/pages/Servers.jsx
Normal file
81
react/pages/Servers.jsx
Normal file
@ -0,0 +1,81 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Badge, Button, Table } from 'reactstrap'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import ServersContext from '../contexts/Servers'
|
||||
|
||||
export default class Servers extends Component {
|
||||
deleteServer (server) {
|
||||
fetch('/api/servers/' + server.id + '', { method: 'DELETE' })
|
||||
}
|
||||
|
||||
startServer (server) {
|
||||
fetch('/api/servers/' + server.id + '/start', { method: 'POST' })
|
||||
}
|
||||
|
||||
stopServer (server) {
|
||||
fetch('/api/servers/' + server.id + '/stop', { method: 'POST' })
|
||||
}
|
||||
|
||||
renderRow (server) {
|
||||
return (
|
||||
<tr key={server.id}>
|
||||
<td>{this.renderStatus(server)}</td>
|
||||
<td>{server.port}</td>
|
||||
<td><Link to={`/servers/${server.id}`}>{server.title}</Link></td>
|
||||
<td><Button color='danger' size='sm' onClick={this.deleteServer.bind(this, server)}>Delete</Button></td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
renderStatus (server) {
|
||||
if (server.pid) {
|
||||
const button = <Button color='primary' size='xs' onClick={this.stopServer.bind(this, server)}>Stop</Button>
|
||||
|
||||
if (server.state) {
|
||||
return (
|
||||
<>
|
||||
<Badge color='success'>Online {(server.state.players || []).length} / {server.state.maxplayers}</Badge>
|
||||
{button}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Badge color='info'>Launching</Badge>
|
||||
{button}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Badge color='secondary'>Offline</Badge>
|
||||
<Button color='primary' size='sm' onClick={this.startServer.bind(this, server)}>Start</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Port</th>
|
||||
<th>Title</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ServersContext.Consumer>
|
||||
{servers => (
|
||||
servers.map(server => this.renderRow(server))
|
||||
)}
|
||||
</ServersContext.Consumer>
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ var webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
// Entry point for static analyzer
|
||||
entry: path.join(__dirname, 'public', 'js', 'app.js'),
|
||||
entry: path.join(__dirname, 'react', 'index.jsx'),
|
||||
|
||||
output: {
|
||||
// Where to build results
|
||||
@ -36,6 +36,7 @@ module.exports = {
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.js/, loader: 'babel-loader', exclude: /node_modules/ },
|
||||
{ test: /\.css$/, loaders: ['style-loader', 'css-loader'] },
|
||||
{ test: /\.html$/, loaders: ['raw-loader'] },
|
||||
{ test: /\.json$/, loaders: ['json-loader'] },
|
||||
|
Loading…
Reference in New Issue
Block a user