implemented log, saves and mod ui
| @@ -2,10 +2,8 @@ Factorio Server Manager | ||||
|  | ||||
| A tool for managing both local and remote Factorio servers. | ||||
|  | ||||
|  | ||||
| Backend is built as a REST api via the Go application.  It also acts as the webserver to serve the front end react application | ||||
|  | ||||
|  | ||||
| All api actions are accessible with the /api route. | ||||
|  | ||||
| To build | ||||
|   | ||||
							
								
								
									
										2148
									
								
								app/bundle.js
									
									
									
									
									
								
							
							
						
						| @@ -28,12 +28,13 @@ | ||||
|  | ||||
|     <div id="app"></div> | ||||
|  | ||||
|     <script src="bundle.js"></script> | ||||
|     <!-- jQuery 2.2.0 --> | ||||
|     <script src="./dist/plugins/jQuery/jQuery-2.2.0.min.js"></script> | ||||
|     <!-- Bootstrap 3.3.6 --> | ||||
|     <script src="./dist/bootstrap/js/bootstrap.min.js"></script> | ||||
|     <!-- AdminLTE App --> | ||||
|     <script src="./dist/dist/js/app.min.js"></script> | ||||
|     <!-- Main application --> | ||||
|     <script src="bundle.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										13
									
								
								handlers.go
									
									
									
									
									
								
							
							
						
						| @@ -82,3 +82,16 @@ func LogTail(w http.ResponseWriter, r *http.Request) { | ||||
| 		log.Printf("Error tailing logfile", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func DLSave(w http.ResponseWriter, r *http.Request) { | ||||
| 	w.Header().Set("Content-Type", "application/octet-stream") | ||||
|  | ||||
| 	vars := mux.Vars(r) | ||||
| 	save := vars["save"] | ||||
| 	saveName := config.FactorioSavesDir + "/" + save | ||||
|  | ||||
| 	w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", save)) | ||||
| 	log.Printf("%s", saveName) | ||||
|  | ||||
| 	http.ServeFile(w, r, saveName) | ||||
| } | ||||
|   | ||||
							
								
								
									
										22
									
								
								main.go
									
									
									
									
									
								
							
							
						
						| @@ -7,23 +7,33 @@ import ( | ||||
| ) | ||||
|  | ||||
| type Config struct { | ||||
| 	FactorioDir string | ||||
| 	ServerIP    string | ||||
| 	FactorioLog string | ||||
| 	FactorioDir      string | ||||
| 	FactorioSavesDir string | ||||
| 	ServerIP         string | ||||
| 	ServerPort       string | ||||
| 	FactorioLog      string | ||||
| } | ||||
|  | ||||
| var config Config | ||||
|  | ||||
| func main() { | ||||
| func loadFlags() { | ||||
| 	factorioDir := flag.String("dir", "./", "Specify location of Factorio config directory.") | ||||
| 	factorioIP := flag.String("host", "0.0.0.0:8080", "Specify IP and port for webserver to listen on.") | ||||
| 	factorioIP := flag.String("host", "0.0.0.0", "Specify IP for webserver to listen on.") | ||||
| 	factorioPort := flag.String("port", "8080", "Specify a port for the server") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	config.FactorioDir = *factorioDir | ||||
| 	config.FactorioSavesDir = config.FactorioDir + "/saves" | ||||
| 	config.ServerIP = *factorioIP | ||||
| 	config.ServerPort = *factorioPort | ||||
| 	config.FactorioLog = config.FactorioDir + "/factorio-current.log" | ||||
| 	log.Printf(config.FactorioSavesDir) | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	loadFlags() | ||||
|  | ||||
| 	router := NewRouter() | ||||
|  | ||||
| 	log.Fatal(http.ListenAndServe(config.ServerIP, router)) | ||||
| 	log.Fatal(http.ListenAndServe(config.ServerIP+":"+config.ServerPort, router)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										8
									
								
								mods.go
									
									
									
									
									
								
							
							
						
						| @@ -56,9 +56,10 @@ func parseModList() (ModList, error) { | ||||
| 	return mods, nil | ||||
| } | ||||
|  | ||||
| // Toggles Enabled boolean on each Mod in mod-list.json file | ||||
| // Toggles Enabled boolean for mod specified in name parameter in mod-list.json file | ||||
| func (m *ModList) toggleMod(name string) error { | ||||
| 	found := false | ||||
| 	status := false | ||||
|  | ||||
| 	for i := range m.Mods { | ||||
| 		if m.Mods[i].Name == name { | ||||
| @@ -67,13 +68,14 @@ func (m *ModList) toggleMod(name string) error { | ||||
| 				m.Mods[i].Enabled = false | ||||
| 			} else { | ||||
| 				m.Mods[i].Enabled = true | ||||
| 				status = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if found { | ||||
| 		m.save() | ||||
| 		log.Printf("Mod: %s was toggled", name) | ||||
| 		log.Printf("Mod: %s was toggled to %v", name, status) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -92,3 +94,5 @@ func (m ModList) save() error { | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| //TODO Add method to allow downloading all installed mods in zip file | ||||
|   | ||||
							
								
								
									
										15
									
								
								routes.go
									
									
									
									
									
								
							
							
						
						| @@ -22,17 +22,15 @@ func NewRouter() *mux.Router { | ||||
| 	// Serves all REST handlers prefixed with /api | ||||
| 	s := r.PathPrefix("/api").Subrouter() | ||||
| 	for _, route := range apiRoutes { | ||||
| 		s. | ||||
| 			Methods(route.Method). | ||||
| 		s.Methods(route.Method). | ||||
| 			Path(route.Pattern). | ||||
| 			Name(route.Name). | ||||
| 			Handler(route.HandlerFunc) | ||||
| 	} | ||||
|  | ||||
| 	// Serves the frontend application from the app directory | ||||
| 	// Uses basic file server to server index.html and Javascript application | ||||
| 	r. | ||||
| 		PathPrefix("/"). | ||||
| 	// Uses basic file server to serve index.html and Javascript application | ||||
| 	r.PathPrefix("/"). | ||||
| 		Methods("GET"). | ||||
| 		Name("Index"). | ||||
| 		Handler(http.FileServer(http.Dir("./app/"))) | ||||
| @@ -40,6 +38,8 @@ func NewRouter() *mux.Router { | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // Defines all API REST endpoints | ||||
| // All routes are prefixed with /api | ||||
| var apiRoutes = Routes{ | ||||
| 	Route{ | ||||
| 		"ListInstalledMods", | ||||
| @@ -61,6 +61,11 @@ var apiRoutes = Routes{ | ||||
| 		"GET", | ||||
| 		"/saves/list", | ||||
| 		ListSaves, | ||||
| 	}, { | ||||
| 		"DlSave", | ||||
| 		"GET", | ||||
| 		"/saves/dl/{save}", | ||||
| 		DLSave, | ||||
| 	}, { | ||||
| 		"LogTail", | ||||
| 		"GET", | ||||
|   | ||||
							
								
								
									
										14
									
								
								saves.go
									
									
									
									
									
								
							
							
						
						| @@ -3,12 +3,19 @@ package main | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Save struct { | ||||
| 	Name    string    `json:"name"` | ||||
| 	LastMod time.Time `json:"last_mod"` | ||||
| 	Size    int64     `json:"size"` | ||||
| } | ||||
|  | ||||
| // Lists save files in factorio/saves | ||||
| func listSaves() []string { | ||||
| func listSaves() []Save { | ||||
| 	saveDir := config.FactorioDir + "/saves" | ||||
| 	result := []string{} | ||||
| 	result := []Save{} | ||||
|  | ||||
| 	files, err := ioutil.ReadDir(saveDir) | ||||
| 	if err != nil { | ||||
| @@ -17,7 +24,8 @@ func listSaves() []string { | ||||
| 	} | ||||
|  | ||||
| 	for _, f := range files { | ||||
| 		result = append(result, f.Name()) | ||||
| 		save := Save{f.Name(), f.ModTime(), f.Size()} | ||||
| 		result = append(result, save) | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
|   | ||||
| @@ -1,85 +0,0 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class Header extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <header className="main-header"> | ||||
|  | ||||
|                 <a href="/" className="logo"> | ||||
|                 <span className="logo-mini"><b>F</b>SM</span> | ||||
|                 <span className="logo-lg"><b>Factorio</b>SM</span> | ||||
|                 </a> | ||||
|  | ||||
|                 <nav className="navbar navbar-static-top" role="navigation"> | ||||
|                 <a href="#" className="sidebar-toggle" data-toggle="offcanvas" role="button"> | ||||
|                 <span className="sr-only">Toggle navigation</span> | ||||
|                 </a> | ||||
|                 <div className="navbar-custom-menu"> | ||||
|                     <ul className="nav navbar-nav"> | ||||
|  | ||||
|                     <li className="dropdown notifications-menu"> | ||||
|                         <a href="#" className="dropdown-toggle" data-toggle="dropdown"> | ||||
|                         <i className="fa fa-bell-o"></i> | ||||
|                         <span className="label label-warning">10</span> | ||||
|                         </a> | ||||
|                         <ul className="dropdown-menu"> | ||||
|                         <li className="header">You have 10 notifications</li> | ||||
|                         <li> | ||||
|                             <ul className="menu"> | ||||
|                                 <a href="#"> | ||||
|                                 <i className="fa fa-users text-aqua"></i> 5 new members joined today | ||||
|                                 </a> | ||||
|                             </ul> | ||||
|                         </li> | ||||
|                         <li className="footer"><a href="#">View all</a></li> | ||||
|                         </ul> | ||||
|                     </li> | ||||
|                     <li className="dropdown user user-menu"> | ||||
|                         <a href="#" className="dropdown-toggle" data-toggle="dropdown"> | ||||
|                         <img src="./dist/dist/img/user2-160x160.jpg" className="user-image" alt="User Image" /> | ||||
|                         <span className="hidden-xs">Alexander Pierce</span> | ||||
|                         </a> | ||||
|                         <ul className="dropdown-menu"> | ||||
|                         <li className="user-header"> | ||||
|                             <img src="./dist/dist/img/user2-160x160.jpg" className="img-circle" alt="User Image" /> | ||||
|  | ||||
|                             <p> | ||||
|                             Alexander Pierce - Web Developer | ||||
|                             <small>Member since Nov. 2012</small> | ||||
|                             </p> | ||||
|                         </li> | ||||
|                         <li className="user-body"> | ||||
|                             <div className="row"> | ||||
|                             <div className="col-xs-4 text-center"> | ||||
|                                 <a href="#">Followers</a> | ||||
|                             </div> | ||||
|                             <div className="col-xs-4 text-center"> | ||||
|                                 <a href="#">Sales</a> | ||||
|                             </div> | ||||
|                             <div className="col-xs-4 text-center"> | ||||
|                                 <a href="#">Friends</a> | ||||
|                             </div> | ||||
|                             </div> | ||||
|                         </li> | ||||
|                         <li className="user-footer"> | ||||
|                             <div className="pull-left"> | ||||
|                             <a href="#" className="btn btn-default btn-flat">Profile</a> | ||||
|                             </div> | ||||
|                             <div className="pull-right"> | ||||
|                             <a href="#" className="btn btn-default btn-flat">Sign out</a> | ||||
|                             </div> | ||||
|                         </li> | ||||
|                         </ul> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a href="#" data-toggle="control-sidebar"><i className="fa fa-gears"></i></a> | ||||
|                     </li> | ||||
|                     </ul> | ||||
|                 </div> | ||||
|                 </nav> | ||||
|             </header> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Header | ||||
| @@ -1,27 +0,0 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class LogsContent extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="content-wrapper"> | ||||
|                 <section className="content-header"> | ||||
|                 <h1> | ||||
|                     Logs | ||||
|                     <small>Optional description</small> | ||||
|                 </h1> | ||||
|                 <ol className="breadcrumb"> | ||||
|                     <li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li> | ||||
|                     <li className="active">Here</li> | ||||
|                 </ol> | ||||
|                 </section> | ||||
|  | ||||
|                 <section className="content"> | ||||
|  | ||||
|  | ||||
|                 </section> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default LogsContent | ||||
| @@ -1,27 +0,0 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class ModsContent extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="content-wrapper"> | ||||
|                 <section className="content-header"> | ||||
|                 <h1> | ||||
|                     Mods | ||||
|                     <small>Optional description</small> | ||||
|                 </h1> | ||||
|                 <ol className="breadcrumb"> | ||||
|                     <li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li> | ||||
|                     <li className="active">Here</li> | ||||
|                 </ol> | ||||
|                 </section> | ||||
|  | ||||
|                 <section className="content"> | ||||
|  | ||||
|  | ||||
|                 </section> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default ModsContent | ||||
| @@ -1,27 +0,0 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class SavesContent extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="content-wrapper"> | ||||
|                 <section className="content-header"> | ||||
|                 <h1> | ||||
|                     Saves | ||||
|                     <small>Optional description</small> | ||||
|                 </h1> | ||||
|                 <ol className="breadcrumb"> | ||||
|                     <li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li> | ||||
|                     <li className="active">Here</li> | ||||
|                 </ol> | ||||
|                 </section> | ||||
|  | ||||
|                 <section className="content"> | ||||
|  | ||||
|  | ||||
|                 </section> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default SavesContent | ||||
							
								
								
									
										26955
									
								
								static/js/bundle.js
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										32
									
								
								ui/App/App.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,32 @@ | ||||
| import React from 'react'; | ||||
| import Header from './components/Header.jsx'; | ||||
| import Sidebar from './components/Sidebar.jsx'; | ||||
| import Footer from './components/Footer.jsx'; | ||||
| import HiddenSidebar from './components/HiddenSidebar.jsx'; | ||||
|  | ||||
|  | ||||
| class App extends React.Component { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="wrapper"> | ||||
|  | ||||
|                 <Header /> | ||||
|  | ||||
|                 <Sidebar /> | ||||
|  | ||||
|                 {this.props.children} | ||||
|  | ||||
|                 <Footer /> | ||||
|  | ||||
|                 <HiddenSidebar /> | ||||
|  | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default App | ||||
							
								
								
									
										16
									
								
								ui/App/components/Footer.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class Footer extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <footer className="main-footer"> | ||||
|                 <div className="pull-right hidden-xs"> | ||||
|                 Anything you want!!!!!! | ||||
|                 </div> | ||||
|                 <strong>Copyright © 2015 <a href="#">Company</a>.</strong> All rights reserved. | ||||
|             </footer> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Footer | ||||
							
								
								
									
										46
									
								
								ui/App/components/Header.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,46 @@ | ||||
| import React from 'react'; | ||||
| import {IndexLink} from 'react-router'; | ||||
|  | ||||
| class Header extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <header className="main-header"> | ||||
|                  | ||||
|                 <IndexLink className="logo" to="/"><span className="logo-lg"><b>Factorio</b>SM</span></IndexLink> | ||||
|                  | ||||
|                 <nav className="navbar navbar-static-top" role="navigation"> | ||||
|                 <a href="#" className="sidebar-toggle" data-toggle="offcanvas" role="button"> | ||||
|                 <span className="sr-only">Toggle navigation</span> | ||||
|                 </a> | ||||
|                 <div className="navbar-custom-menu"> | ||||
|                     <ul className="nav navbar-nav"> | ||||
|  | ||||
|                     <li className="dropdown notifications-menu"> | ||||
|                         <a href="#" className="dropdown-toggle" data-toggle="dropdown"> | ||||
|                         <i className="fa fa-bell-o"></i> | ||||
|                         <span className="label label-warning">10</span> | ||||
|                         </a> | ||||
|                         <ul className="dropdown-menu"> | ||||
|                         <li className="header">You have 10 notifications</li> | ||||
|                         <li> | ||||
|                             <ul className="menu"> | ||||
|                                 <a href="#"> | ||||
|                                 <i className="fa fa-users text-aqua"></i> 5 new members joined today | ||||
|                                 </a> | ||||
|                             </ul> | ||||
|                         </li> | ||||
|                         <li className="footer"><a href="#">View all</a></li> | ||||
|                         </ul> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a href="#" data-toggle="control-sidebar"><i className="fa fa-gears"></i></a> | ||||
|                     </li> | ||||
|                     </ul> | ||||
|                 </div> | ||||
|                 </nav> | ||||
|             </header> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Header | ||||
| @@ -1,27 +1,8 @@ | ||||
| import React from 'react'; | ||||
| import Header from './components/Header.jsx'; | ||||
| import Sidebar from './components/Sidebar.jsx'; | ||||
| //import ModsContent from './components/ModsContent.jsx'; | ||||
| 
 | ||||
| 
 | ||||
| class App extends React.Component { | ||||
| class HiddenSidebar extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="wrapper"> | ||||
| 
 | ||||
|             <Header /> | ||||
| 
 | ||||
|             <Sidebar /> | ||||
| 
 | ||||
|             {this.props.children} | ||||
| 
 | ||||
|             <footer className="main-footer"> | ||||
|                 <div className="pull-right hidden-xs"> | ||||
|                 Anything you want | ||||
|                 </div> | ||||
|                 <strong>Copyright © 2015 <a href="#">Company</a>.</strong> All rights reserved. | ||||
|             </footer> | ||||
| 
 | ||||
|             <aside className="control-sidebar control-sidebar-dark"> | ||||
|                 <ul className="nav nav-tabs nav-justified control-sidebar-tabs"> | ||||
|                 <li className="active"><a href="#control-sidebar-home-tab" data-toggle="tab"><i className="fa fa-home"></i></a></li> | ||||
| @@ -79,11 +60,10 @@ class App extends React.Component { | ||||
|                     </form> | ||||
|                 </div> | ||||
|                 </div> | ||||
|             </aside> | ||||
|             <div className="control-sidebar-bg"></div> | ||||
|         </div> | ||||
|             </aside> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default App | ||||
| export default HiddenSidebar | ||||
							
								
								
									
										36
									
								
								ui/App/components/Logs/LogLines.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class LogLines extends React.Component { | ||||
|     updateLog() { | ||||
|         this.props.getLastLog(); | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         this.props.log.reverse(); | ||||
|         return( | ||||
|             <div 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: React.PropTypes.array.isRequired, | ||||
|     getLastLog: React.PropTypes.func.isRequired | ||||
| } | ||||
|  | ||||
| export default LogLines | ||||
							
								
								
									
										58
									
								
								ui/App/components/LogsContent.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,58 @@ | ||||
| import React from 'react'; | ||||
| import LogLines from './Logs/LogLines.jsx'; | ||||
|  | ||||
| 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}) | ||||
|             }, | ||||
|             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>Optional description</small> | ||||
|                 </h1> | ||||
|                 <ol className="breadcrumb"> | ||||
|                     <li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li> | ||||
|                     <li className="active">Here</li> | ||||
|                 </ol> | ||||
|                 </section> | ||||
|  | ||||
|                 <section className="content"> | ||||
|  | ||||
|                 <LogLines  | ||||
|                     getLastLog={this.getLastLog} | ||||
|                     {...this.state}  | ||||
|                 /> | ||||
|  | ||||
|                 </section> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default LogsContent | ||||
							
								
								
									
										27
									
								
								ui/App/components/Mods/InstalledMods.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,27 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class InstalledMods extends React.Component { | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="box"> | ||||
|                 <div className="box-header"> | ||||
|                     <h3 className="box-title">Installed Mods</h3> | ||||
|                 </div> | ||||
|                       | ||||
|                 <div className="box-body"> | ||||
|                 {this.props.installedMods.map ( (mod, i) => { | ||||
|                     return( | ||||
|                         <p>{mod}</p> | ||||
|                     )                                             | ||||
|                 })} | ||||
|                 </div> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| InstalledMods.propTypes = { | ||||
|     installedMods: React.PropTypes.array.isRequired | ||||
| } | ||||
|  | ||||
| export default InstalledMods | ||||
							
								
								
									
										50
									
								
								ui/App/components/Mods/ListMods.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,50 @@ | ||||
| import React from 'react'; | ||||
| import Mod from './Mod.jsx' | ||||
|  | ||||
| class ModList extends React.Component { | ||||
|     componentDidMount() { | ||||
|         console.log(this.props.listMods); | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="box"> | ||||
|                 <div className="box-header"> | ||||
|                     <h3 className="box-title">Manage Mods</h3> | ||||
|                 </div> | ||||
|                  | ||||
|                 <div className="box-body"> | ||||
|                     <div className="table-responsive"> | ||||
|                     <table className="table table-striped"> | ||||
|                         <thead> | ||||
|                             <tr> | ||||
|                                 <th>Name</th> | ||||
|                                 <th>Status</th> | ||||
|                                 <th>Toggle Status</th> | ||||
|                             </tr> | ||||
|                         </thead> | ||||
|                         <tbody> | ||||
|                         {this.props.listMods.map ( (mod, i) => { | ||||
|                             return( | ||||
|                                 <Mod  | ||||
|                                     key={i} | ||||
|                                     mod={mod} | ||||
|                                     {...this.props} | ||||
|                                 /> | ||||
|                             )                                        | ||||
|                         })} | ||||
|                         </tbody> | ||||
|                     </table> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| ModList.propTypes = { | ||||
|     listMods: React.PropTypes.array.isRequired, | ||||
|     toggleMod: React.PropTypes.func.isRequired | ||||
| } | ||||
|  | ||||
| export default ModList | ||||
							
								
								
									
										42
									
								
								ui/App/components/Mods/Mod.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class Mod extends React.Component { | ||||
|     togglePress(e) { | ||||
|         e.preventDefault(); | ||||
|         console.log(this.refs.modName); | ||||
|         const node = this.refs.modName; | ||||
|         const modName = node.name; | ||||
|         this.props.toggleMod(modName); | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         if (this.props.mod.enabled === "false") { | ||||
|             this.modStatus = <span className="label label-danger">Disabled</span> | ||||
|         } else { | ||||
|             this.modStatus = <span className="label label-success">Enabled</span> | ||||
|         } | ||||
|         return( | ||||
|             <tr> | ||||
|                 <td>{this.props.mod.name}</td> | ||||
|                 <td>{this.modStatus}</td> | ||||
|                 <td> | ||||
|                     <form onSubmit={this.togglePress.bind(this)}> | ||||
|                         <input className='btn btn-default btn-sm' | ||||
|                             ref='modName' | ||||
|                             type='submit' | ||||
|                             value='Toggle' | ||||
|                             name={this.props.mod.name} | ||||
|                         /> | ||||
|                     </form> | ||||
|                 </td> | ||||
|             </tr> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| Mod.propTypes = { | ||||
|     mod: React.PropTypes.object.isRequired, | ||||
|     toggleMod: React.PropTypes.func.isRequired | ||||
| } | ||||
|  | ||||
| export default Mod | ||||
							
								
								
									
										90
									
								
								ui/App/components/ModsContent.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,90 @@ | ||||
| import React from 'react'; | ||||
| import ModList from './Mods/ListMods.jsx'; | ||||
| import InstalledMods from './Mods/InstalledMods.jsx'; | ||||
|  | ||||
| class ModsContent extends React.Component { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
|         this.componentDidMount = this.componentDidMount.bind(this); | ||||
|         this.toggleMod = this.toggleMod.bind(this); | ||||
|         this.state = { | ||||
|             installedMods: [], | ||||
|             listMods: [] | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     componentDidMount() { | ||||
|         this.loadModList(); | ||||
|         this.loadInstalledModList(); | ||||
|     } | ||||
|  | ||||
|     loadModList() { | ||||
|         $.ajax({ | ||||
|             url: "/api/mods/list", | ||||
|             dataType: "json", | ||||
|             success: (data) => { | ||||
|                 this.setState({listMods: data.mods}) | ||||
|             }, | ||||
|             error: (xhr, status, err) => { | ||||
|                 console.log('api/mods/list', status, err.toString()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     loadInstalledModList() { | ||||
|         $.ajax({ | ||||
|             url: "/api/mods/list/installed", | ||||
|             dataType: "json", | ||||
|             success: (data) => { | ||||
|                 this.setState({installedMods: data}) | ||||
|             }, | ||||
|             error: (xhr, status, err) => { | ||||
|                 console.log('api/mods/list', status, err.toString()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     toggleMod(modName) { | ||||
|         $.ajax({ | ||||
|             url: "/api/mods/toggle/" + modName, | ||||
|             dataType: "json", | ||||
|             success: (data) => { | ||||
|                 this.setState({listMods: data.mods}) | ||||
|             }, | ||||
|             error: (xhr, status, err) => { | ||||
|                 console.log('api/mods/toggle', status, err.toString()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         return( | ||||
|             <div className="content-wrapper"> | ||||
|                 <section className="content-header"> | ||||
|                 <h1> | ||||
|                     Mods | ||||
|                     <small>Manage your mods</small> | ||||
|                 </h1> | ||||
|                 <ol className="breadcrumb"> | ||||
|                     <li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li> | ||||
|                     <li className="active">Here</li> | ||||
|                 </ol> | ||||
|                 </section> | ||||
|  | ||||
|                 <section className="content" style={{height: "100%"}}> | ||||
|  | ||||
|                     <InstalledMods  | ||||
|                         {...this.state} | ||||
|                     /> | ||||
|                     <ModList | ||||
|                         {...this.state} | ||||
|                         toggleMod={this.toggleMod} | ||||
|                     /> | ||||
|  | ||||
|                 </section> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default ModsContent | ||||
							
								
								
									
										25
									
								
								ui/App/components/Saves/Save.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,25 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| class Save extends React.Component { | ||||
|     render() { | ||||
|         let saveSize = parseFloat(this.props.save.size / 1024 / 1024).toFixed(3) | ||||
|         let saveLastMod = Date.parse(this.props.save.last_mod); | ||||
|         let date = new Date(saveLastMod) | ||||
|         let dateFmt = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDay() + ' ' | ||||
|             + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds(); | ||||
|  | ||||
|         return( | ||||
|             <tr> | ||||
|                 <td>{this.props.save.name}</td> | ||||
|                 <td>{dateFmt}</td> | ||||
|                 <td>{saveSize} MB</td> | ||||
|             </tr> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| Save.propTypes = { | ||||
|     save: React.PropTypes.object.isRequired | ||||
| } | ||||
|  | ||||
| export default Save | ||||
							
								
								
									
										45
									
								
								ui/App/components/Saves/SavesList.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| import React from 'react'; | ||||
| import Save from './Save.jsx'; | ||||
|  | ||||
| class SavesList extends React.Component { | ||||
|     render() { | ||||
|         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> | ||||
|                                 </tr> | ||||
|                             </thead> | ||||
|                             <tbody> | ||||
|                             {this.props.saves.map ( (save, i) => { | ||||
|                                 return( | ||||
|                                     <Save | ||||
|                                         key={i} | ||||
|                                         save={save} | ||||
|                                     /> | ||||
|                                 ) | ||||
|                             })} | ||||
|                             </tbody> | ||||
|                         </table>         | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| SavesList.propTypes = { | ||||
|     saves: React.PropTypes.array.isRequired, | ||||
|     dlSave: React.PropTypes.func.isRequired | ||||
| } | ||||
|  | ||||
| export default SavesList | ||||
							
								
								
									
										71
									
								
								ui/App/components/SavesContent.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,71 @@ | ||||
| import React from 'react'; | ||||
| import SavesList from './Saves/SavesList.jsx'; | ||||
|  | ||||
| class SavesContent extends React.Component { | ||||
|     constructor(props) { | ||||
|         super(props); | ||||
|         this.dlSave = this.dlSave.bind(this); | ||||
|         this.state = { | ||||
|             saves: [] | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     componentDidMount() { | ||||
|         this.getSaves(); | ||||
|     } | ||||
|  | ||||
|     getSaves() { | ||||
|         $.ajax({ | ||||
|             url: "/api/saves/list", | ||||
|             dataType: "json", | ||||
|             success: (data) => { | ||||
|                 this.setState({saves: data}) | ||||
|             }, | ||||
|             error: (xhr, status, err) => { | ||||
|                 console.log('api/mods/list', status, err.toString()); | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     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 className="content-wrapper"> | ||||
|                 <section className="content-header"> | ||||
|                 <h1> | ||||
|                     Saves | ||||
|                     <small>Optional description</small> | ||||
|                 </h1> | ||||
|                 <ol className="breadcrumb"> | ||||
|                     <li><a href="#"><i className="fa fa-dashboard"></i> Level</a></li> | ||||
|                     <li className="active">Here</li> | ||||
|                 </ol> | ||||
|                 </section> | ||||
|  | ||||
|                 <section className="content"> | ||||
|  | ||||
|                 <SavesList  | ||||
|                     {...this.state} | ||||
|                     dlSave={this.dlSave} | ||||
|                 /> | ||||
|  | ||||
|  | ||||
|                 </section> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default SavesContent | ||||
| @@ -33,13 +33,6 @@ class Sidebar extends React.Component { | ||||
|                     <li><Link to="/mods" activeClassName="active"><i className="fa fa-link"></i><span>Mods</span></Link></li> | ||||
|                     <li><Link to="/logs" activeClassName="active"><i className="fa fa-link"></i> <span>Logs</span></Link></li> | ||||
|                     <li><Link to="/saves" activeClassName="active"><i className="fa fa-link"></i> <span>Saves</span></Link></li> | ||||
|                     <li className="treeview"> | ||||
|                     <a href="#"><i className="fa fa-link"></i> <span>Mods</span> <i className="fa fa-angle-left pull-right"></i></a> | ||||
|                     <ul className="treeview-menu"> | ||||
|                         <li>Server Mods</li> | ||||
|                         <li><a href="#">Get Mods</a></li> | ||||
|                     </ul> | ||||
|                     </li> | ||||
|                 </ul> | ||||
|                 </section> | ||||
|             </aside> | ||||
| Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB | 
| Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB | 
| Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB | 
| Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB | 
| Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB | 
| Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB | 
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB | 
| Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB | 
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB | 
| Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 184 B | 
| Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB | 
| Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB | 
| Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 658 KiB | 
| Before Width: | Height: | Size: 414 KiB After Width: | Height: | Size: 414 KiB | 
| Before Width: | Height: | Size: 383 KiB After Width: | Height: | Size: 383 KiB | 
| Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB | 
| Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB | 
| Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB | 
| Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |