Merge pull request #21 from alexfriesen/feature-disconnect

Feature: disconnect
This commit is contained in:
Garry Newman
2017-04-22 10:20:17 +01:00
committed by GitHub
7 changed files with 376 additions and 241 deletions
+59
View File
@@ -1,2 +1,61 @@
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
+86 -47
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" ng-app="RconApp"> <html lang="en" ng-app="RconApp">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@@ -6,74 +6,113 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Websocket Rcon</title> <title>Websocket Rcon</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap-theme.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap-theme.css" rel="stylesheet">
<link href="rcon.css" rel="stylesheet"> <link href="rcon.css" rel="stylesheet">
</head> </head>
<body ng-controller="RconController"> <body ng-controller="RconController">
<nav class="navbar navbar-default" ng-controller="ServerInfoController" ng-show="info.Hostname != null"> <nav class="navbar navbar-default" ng-controller="ServerInfoController" ng-show="Connected">
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-brand" href="#">{{info.Hostname}}</a> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse-header" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<ul class="nav navbar-nav"> <a class="navbar-brand" href="#">{{info.Hostname}}</a>
<li><div class="navbar-info" title="Uptime"><span class="glyphicon glyphicon-time"></span> {{info.Uptime|SecondsToDuration}}</div></li> </div>
<li><div class="navbar-info" title="Map Name"><span class="glyphicon glyphicon-globe"></span> {{info.Map}}</div></li>
<li><div class="navbar-info" title="Current Players/Maximum Players"><span class="glyphicon glyphicon-user"></span> {{info.Players|number}}/{{info.MaxPlayers|number}}</div></li>
<li><div class="navbar-info" title="Framerate"><span class="glyphicon glyphicon-hourglass"></span> {{info.Framerate|number}}fps</div></li>
<li><div class="navbar-info" title="Entity Count"><span class="glyphicon glyphicon-oil"></span> {{info.EntityCount|number}}</div></li>
</ul>
</div> <div class="collapse navbar-collapse" id="navbar-collapse-header">
<ul class="nav navbar-nav">
<li>
<p class="navbar-text" title="Uptime">
<span class="glyphicon glyphicon-time"></span>
{{info.Uptime|SecondsToDuration}}
</p>
</li>
<li>
<p class="navbar-text" title="Map Name">
<span class="glyphicon glyphicon-globe"></span>
{{info.Map}}
</p>
</li>
<li>
<p class="navbar-text" title="Current Players/Maximum Players">
<span class="glyphicon glyphicon-user"></span>
{{info.Players|number}}/{{info.MaxPlayers|number}}
</p>
</li>
<li>
<p class="navbar-text" title="Framerate">
<span class="glyphicon glyphicon-hourglass"></span>
{{info.Framerate|number}}fps
</p>
</li>
<li>
<p class="navbar-text" title="Entity Count">
<span class="glyphicon glyphicon-oil"></span>
{{info.EntityCount|number}}
</p>
</li>
</ul>
</div> <ul class="nav navbar-nav navbar-right">
</nav> <li>
<button class="btn btn-danger navbar-btn" type="button" ng-click="Disconnect()">Disconnect</button>
</li>
</ul>
<div class="container"> </div>
</div>
</nav>
<div ng-include="'html/connect.html'" ng-show="!Connected"> <div class="container">
</div>
<div ng-show="Connected" class="col-sm-2 sidebar"> <div ng-include="'html/connect.html'" ng-show="!Connected"></div>
<ul class="nav nav-sidebar"> <div ng-show="Connected" class="col-sm-2 sidebar">
<li ng-repeat="p in pages | filter: { Nav: '' } " ng-class="{'active' : $route.current.Title == p.Title }">
<a href="#{{Nav( p.originalPath )}}">{{p.Title}}</a>
</li>
</ul>
</div> <ul class="nav nav-sidebar">
<li ng-repeat="page in pages" ng-class="{'active' : $route.current.Title == page.Title }">
<a href="#{{Nav( page.originalPath )}}">{{page.Title}}</a>
</li>
</ul>
<div class="col-sm-10" ng-show="Connected"> </div>
<h1 class="page-header">{{$route.current.Title}}</h1> <div class="col-sm-10" ng-show="Connected">
<div class="row" ng-view></div>
</div>
</div> <h1 class="page-header">{{$route.current.Title}}</h1>
<div class="row" ng-view></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> </div>
<!-- AngularJS --> </div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js"></script>
<!-- Bootstrap --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.js"></script>
<!-- For charts --> <!-- AngularJS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-nvd3/1.0.5/angular-nvd3.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js"></script>
<!-- Data manipulation --> <!-- Bootstrap -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/js/bootstrap.js"></script>
<!-- For charts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-nvd3/1.0.5/angular-nvd3.min.js"></script>
<!-- Data manipulation -->
<script src="js/linq.min.js"></script> <script src="js/linq.min.js"></script>
<!-- Our stuff --> <!-- Our stuff -->
<script src="js/rconService.js"></script> <script src="js/rconService.js"></script>
<script src="js/app.js"></script> <script src="js/app.js"></script>
<script src="js/connection.js"></script> <script src="js/connection.js"></script>
+91 -78
View File
@@ -1,101 +1,114 @@
var app = angular.module('RconApp', ['ngRoute', 'nvd3']);
app.service('rconService', [RconService]);
var app = angular.module( 'RconApp', ['ngRoute', 'nvd3'] ); app.config(function($routeProvider) {
$routeProvider.when("/home", {Title: "Home"});
$routeProvider.when("/:address/info", {
Title: "Server",
templateUrl: "html/serverInfo.html",
Nav: true
});
$routeProvider.when("/:address/console", {
Title: "Console",
templateUrl: "html/console.html",
Nav: true
});
$routeProvider.when("/:address/chat", {
Title: "Chat",
templateUrl: "html/chat.html",
Nav: true
});
$routeProvider.when("/:address/playerlist", {
Title: "Player List",
templateUrl: "html/playerlist.html",
Nav: true
});
$routeProvider.when("/:address/player/:userid", {
Title: "Player Info",
templateUrl: "html/playerInfo.html"
});
$routeProvider.otherwise({redirectTo: '/home'});
});
app.service( 'rconService', [RconService] ); app.controller('RconController', RconController);
app.config( function ( $routeProvider ) function RconController($scope, $rootScope, rconService, $timeout, $route) {
{ $scope.$route = $route;
$routeProvider.when( "/home", { Title: "Home" } )
$routeProvider.when( "/:address/info", { Title: "Server", templateUrl: "html/serverInfo.html", Nav: true } )
$routeProvider.when( "/:address/console", { Title: "Console", templateUrl: "html/console.html", Nav: true } )
$routeProvider.when( "/:address/chat", { Title: "Chat", templateUrl: "html/chat.html", Nav: true } )
$routeProvider.when( "/:address/playerlist", { Title: "Player List", templateUrl: "html/playerlist.html", Nav: true } )
$routeProvider.when( "/:address/player/:userid", { Title: "Player Info", templateUrl: "html/playerInfo.html" } )
$routeProvider.when( "/:address/player/:userid/xp", { Title: "Player Xp Stats", templateUrl: "html/playerXp.html" } )
$routeProvider.otherwise( { redirectTo: '/home' } );
} ); $scope.pages = $.map($route.routes, function(value, index) {
if (value.Nav) {
return [value];
}
});
app.controller( 'RconController', RconController ); $scope.OpenLeftMenu = function() {
$mdSidenav('left').toggle();
};
function RconController( $scope, $rootScope, rconService, $timeout, $route ) $scope.IsConnected = function() {
{ return rconService.IsConnected();
$scope.$route = $route; }
$rootScope.Nav = function(url) {
return url.replace(":address", rconService.Address);
}
$scope.pages = $.map( $route.routes, function ( value, index ) $rootScope.$on('$stateChangeStart', function(next, current) {
{ console.log(next);
return [value]; });
} );
$scope.OpenLeftMenu = function () rconService.OnOpen = function() {
{ $scope.Connected = true;
$mdSidenav( 'left' ).toggle(); $scope.$broadcast("OnConnected");
}; $scope.$digest();
$scope.address = rconService.Address;
}
$scope.IsConnected = function () rconService.OnClose = function(ev) {
{ $scope.$broadcast("OnDisconnected", ev);
return rconService.IsConnected(); $scope.$digest();
} }
$rootScope.Nav = function ( url ) rconService.OnError = function(ev) {
{ $scope.$broadcast("OnConnectionError", ev);
return url.replace( ":address", rconService.Address ); $scope.$digest();
} }
$rootScope.$on( '$stateChangeStart', function ( next, current ) rconService.OnMessage = function(msg) {
{ $scope.$apply(function() {
console.log( next ); $scope.$broadcast("OnMessage", msg);
} ); });
}
rconService.OnOpen = function () $scope.Disconnect = function() {
{ if (confirm('Do you really want to disconnect?')) {
$scope.Connected = true; rconService.Disconnect();
$scope.$broadcast( "OnConnected" ); $scope.Connected = false;
$scope.$digest();
$scope.address = rconService.Address;
}
rconService.OnClose = function ( ev ) $scope.address = '#/home';
{ }
$scope.$broadcast( "OnDisconnected", ev ); }
$scope.$digest();
}
rconService.OnError = function ( ev )
{
$scope.$broadcast( "OnConnectionError", ev );
$scope.$digest();
}
rconService.OnMessage = function ( msg )
{
$scope.$apply( function ()
{
$scope.$broadcast( "OnMessage", msg );
} );
}
} }
app.filter( 'SecondsToDuration', [SecondsToDuration] ); app.filter('SecondsToDuration', [SecondsToDuration]);
function SecondsToDuration() function SecondsToDuration() {
{ return function(input) {
return function ( input ) input = parseInt(input);
{
input = parseInt( input );
var out = ""; var out = "";
var hours = Math.floor( input / 3600 ); var hours = Math.floor(input / 3600);
if ( input > 3600 ) out += hours + "h"; if (input > 3600)
out += hours + "h";
var minutes = Math.floor( input / 60 ) % 60; var minutes = Math.floor(input / 60) % 60;
if ( input > 60 ) out += minutes + "m"; if (input > 60)
out += minutes + "m";
var seconds = input % 60; var seconds = input % 60;
out += seconds + "s"; out += seconds + "s";
return out; return out;
} }
} }
+110 -102
View File
@@ -1,123 +1,131 @@
function RconService() {
function RconService() var ConnectionStatus = {
{ 'CONNECTING': 0,
var s = { 'OPEN': 1,
Callbacks: {} 'CLOSING': 2,
}; 'CLOSED': 3
};
s.Connect = function ( addr, pass ) var Service = {
{ Socket: null,
s.Socket = new WebSocket( "ws://" + addr + "/" + pass ); Address: null,
s.Address = addr; Callbacks: {}
};
s.Socket.onmessage = function ( e ) var LastIndex = 1001;
{
var data = angular.fromJson( e.data );
// Service.Connect = function(addr, pass) {
// This is a targetted message, it has an identifier this.Socket = new WebSocket("ws://" + addr + "/" + pass);
// So feed it back to the right callback. this.Address = addr;
//
if ( data.Identifier > 1000 )
{
var cb = s.Callbacks[data.Identifier];
if ( cb != null )
{
cb.scope.$apply( function ()
{
cb.callback( data );
} );
}
s.Callbacks[data.Identifier] = null;
return; this.Socket.onmessage = function(e) {
} var data = angular.fromJson(e.data);
// //
// Generic console message, let OnMessage catch it // This is a targetted message, it has an identifier
// // So feed it back to the right callback.
if ( s.OnMessage != null ) //
{ if (data.Identifier > 1000) {
s.OnMessage( data ); var cb = Service.Callbacks[data.Identifier];
} if (cb != null) {
}; cb.scope.$apply(function() {
cb.callback(data);
});
}
Service.Callbacks[data.Identifier] = null;
s.Socket.onopen = s.OnOpen; return;
s.Socket.onclose = s.OnClose; }
s.Socket.onerror = s.OnError;
}
s.Command = function ( msg, identifier ) //
{ // Generic console message, let OnMessage catch it
if ( identifier == null ) //
identifier = -1; if (Service.OnMessage != null) {
Service.OnMessage(data);
}
};
if ( s.Socket == null ) this.Socket.onopen = this.OnOpen;
return; this.Socket.onclose = this.OnClose;
this.Socket.onerror = this.OnError;
}
if ( !s.IsConnected ) Service.Disconnect = function() {
return; if (this.Socket) {
this.Socket.close();
this.Socket = null;
}
var packet = this.Callbacks = {};
{ }
Identifier: identifier,
Message: msg,
Name: "WebRcon"
}
s.Socket.send( JSON.stringify( packet ) ); Service.Command = function(msg, identifier) {
}; if (this.Socket === null)
return;
var LastIndex = 1001; if (!this.IsConnected())
return;
// if (identifier === null)
// Make a request, call this function when it returns identifier = -1;
//
s.Request = function ( msg, scope, callback )
{
LastIndex++;
s.Callbacks[LastIndex] = { scope: scope, callback: callback };
s.Command( msg, LastIndex );
}
// var packet = {
// Returns true if websocket is connected Identifier: identifier,
// Message: msg,
s.IsConnected = function () Name: "WebRcon"
{ };
if ( s.Socket == null ) return false;
return s.Socket.readyState == 1;
}
// this.Socket.send(JSON.stringify(packet));
// Helper for installing connectivity logic };
//
// Basically if not connected, call this function when we are
// And if we are - then call it right now.
//
s.InstallService = function( scope, func )
{
scope.$on( "OnConnected", function ()
{
func();
});
if ( s.IsConnected() ) //
{ // Make a request, call this function when it returns
func(); //
} Service.Request = function(msg, scope, callback) {
} LastIndex++;
this.Callbacks[LastIndex] = {
scope: scope,
callback: callback
};
Service.Command(msg, LastIndex);
}
s.getPlayers = function(scope, success) { //
this.Request( "playerlist", scope, function ( response ) // Returns true if websocket is connected
{ //
var players = JSON.parse( response.Message ); Service.IsConnected = function() {
if (this.Socket == null)
return false;
if(typeof success === 'function') { return this.Socket.readyState === ConnectionStatus.OPEN;
success.call(scope, players); }
}
});
}
return s; //
// Helper for installing connectivity logic
//
// Basically if not connected, call this function when we are
// And if we are - then call it right now.
//
Service.InstallService = function(scope, func) {
scope.$on("OnConnected", function() {
func();
});
if (this.IsConnected()) {
func();
}
}
Service.getPlayers = function(scope, success) {
this.Request("playerlist", scope, function(response) {
var players = JSON.parse(response.Message);
if (typeof success === 'function') {
success.call(scope, players);
}
});
}
return Service;
} }
+15
View File
@@ -0,0 +1,15 @@
{
"name": "webrcon",
"description": "Game server Rcon, using websockets",
"repository": {
"type": "git",
"url": "https://github.com/Facepunch/webrcon"
},
"scripts": {
"server": "http-server -p 8888",
"start": "npm run server"
},
"devDependencies": {
"http-server": "^0.9.0"
}
}
+1
View File
@@ -118,6 +118,7 @@ a:hover
color: #fff; color: #fff;
} }
.navbar-default .navbar-nav > li > .navbar-text,
.navbar-default .navbar-nav > li > a, .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus .navbar-default .navbar-nav > li > a, .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus
{ {
color: #fff; color: #fff;