kopia lustrzana https://github.com/cyoung/stratux
commit
eae150ee1c
5
Makefile
5
Makefile
|
@ -11,10 +11,13 @@ www:
|
||||||
mkdir -p /var/www/css
|
mkdir -p /var/www/css
|
||||||
cp web/css/*.css /var/www/css
|
cp web/css/*.css /var/www/css
|
||||||
mkdir -p /var/www/js
|
mkdir -p /var/www/js
|
||||||
cp web/js/*.js /var/www/js
|
cp web/js/main.js /var/www/js
|
||||||
|
cp web/js/addtohomescreen.min.js /var/www/js
|
||||||
|
cp web/js/j3di-all.min.js /var/www/js
|
||||||
mkdir -p /var/www/img
|
mkdir -p /var/www/img
|
||||||
cp web/img/logo*.png /var/www/img
|
cp web/img/logo*.png /var/www/img
|
||||||
cp web/img/screen*.png /var/www/img
|
cp web/img/screen*.png /var/www/img
|
||||||
|
cp web/img/world.png /var/www/img
|
||||||
mkdir -p /var/www/maui
|
mkdir -p /var/www/maui
|
||||||
mkdir -p /var/www/maui/js
|
mkdir -p /var/www/maui/js
|
||||||
cp web/maui/js/angular-ui-router.min.js /var/www/maui/js
|
cp web/maui/js/angular-ui-router.min.js /var/www/maui/js
|
||||||
|
|
|
@ -6,16 +6,40 @@
|
||||||
|
|
||||||
.weather-page {}
|
.weather-page {}
|
||||||
|
|
||||||
|
.gps_page {}
|
||||||
|
|
||||||
|
.map-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world-map {
|
||||||
|
background-image: url(../img/world.png);
|
||||||
|
background-position: 1186px 591px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
/* this will be set dynamically to center map at gps location */
|
||||||
|
/*
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.mark-position {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.section_invisible {
|
.section_invisible {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-normal {
|
.text-normal {
|
||||||
font-weight:100;
|
font-weight: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reset-flow {
|
.reset-flow {
|
||||||
clear:both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
|
@ -86,27 +110,32 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.flight_condition_VFR {
|
.flight_condition_VFR {
|
||||||
background-color:forestgreen;
|
background-color: forestgreen;
|
||||||
color:white;
|
color: white;
|
||||||
}
|
|
||||||
.flight_condition_MVFR {
|
|
||||||
background-color:blue;
|
|
||||||
color:white;
|
|
||||||
}
|
|
||||||
.flight_condition_IFR {
|
|
||||||
background-color:crimson;
|
|
||||||
color:white;
|
|
||||||
}
|
|
||||||
.flight_condition_LIFR {
|
|
||||||
background-color:darkorchid;
|
|
||||||
color:white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flight_condition_MVFR {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flight_condition_IFR {
|
||||||
|
background-color: crimson;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flight_condition_LIFR {
|
||||||
|
background-color: darkorchid;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paperairplane_left,
|
||||||
.traffic-style1 {
|
.traffic-style1 {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
background-color: cornflowerblue;
|
background-color: cornflowerblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paperairplane_right,
|
||||||
.traffic-style2 {
|
.traffic-style2 {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
background-color: darkkhaki
|
background-color: darkkhaki
|
||||||
|
@ -124,21 +153,31 @@
|
||||||
color: blue;
|
color: blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-yellow {
|
.icon-white {
|
||||||
color: darkgoldenrod;
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-black {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-red {
|
||||||
|
color: crimson;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar_container {
|
.bar_container {
|
||||||
display:inline-block;
|
display: inline-block;
|
||||||
border:1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar_display {
|
.bar_display {
|
||||||
padding: 1px 2px 1px 3px;
|
padding: 1px 2px 1px 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ***************************************************************************
|
/* ***************************************************************************
|
||||||
everything below this comment represents tweeks to the mobile-angular-uis CSS
|
everything below this comment represents tweeks to the mobile-angular-uis CSS
|
||||||
*************************************************************************** */
|
*************************************************************************** */
|
||||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 58 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 178 KiB |
|
@ -39,9 +39,7 @@
|
||||||
<link rel="stylesheet" type="text/css" href="css/addtohomescreen.css">
|
<link rel="stylesheet" type="text/css" href="css/addtohomescreen.css">
|
||||||
<script src="js/addtohomescreen.min.js"></script>
|
<script src="js/addtohomescreen.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
addToHomescreen({
|
addToHomescreen({ displayPace: 480 });
|
||||||
displayPace: 480
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<link rel="stylesheet" href="maui/css/mobile-angular-ui-base.min.css" />
|
<link rel="stylesheet" href="maui/css/mobile-angular-ui-base.min.css" />
|
||||||
|
@ -57,12 +55,14 @@
|
||||||
|
|
||||||
<!-- TODO: combine and minify the following javascript -->
|
<!-- TODO: combine and minify the following javascript -->
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
<script src="plates/js/gps.js"></script>
|
|
||||||
<script src="plates/js/logs.js"></script>
|
<script src="plates/js/logs.js"></script>
|
||||||
<script src="plates/js/settings.js"></script>
|
<script src="plates/js/settings.js"></script>
|
||||||
<script src="plates/js/status.js"></script>
|
<script src="plates/js/status.js"></script>
|
||||||
<script src="plates/js/traffic.js"></script>
|
<script src="plates/js/traffic.js"></script>
|
||||||
<script src="plates/js/weather.js"></script>
|
<script src="plates/js/weather.js"></script>
|
||||||
|
<script src="js/j3di-all.min.js"></script>
|
||||||
|
<script src="plates/js/ahrs.js"></script>
|
||||||
|
<script src="plates/js/gps.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body ng-app="stratux" ng-controller="MainCtrl" ui-prevent-touchmove-defaults>
|
<body ng-app="stratux" ng-controller="MainCtrl" ui-prevent-touchmove-defaults>
|
||||||
|
|
Plik diff jest za duży
Load Diff
File diff suppressed because one or more lines are too long
|
@ -2,8 +2,10 @@
|
||||||
var URL_HOST_BASE = window.location.hostname;
|
var URL_HOST_BASE = window.location.hostname;
|
||||||
var URL_SETTINGS_GET = "http://" + URL_HOST_BASE + "/getSettings";
|
var URL_SETTINGS_GET = "http://" + URL_HOST_BASE + "/getSettings";
|
||||||
var URL_SETTINGS_SET = "http://" + URL_HOST_BASE + "/setSettings";
|
var URL_SETTINGS_SET = "http://" + URL_HOST_BASE + "/setSettings";
|
||||||
|
var URL_GPS_GET = "http://" + URL_HOST_BASE + "/getSituation";
|
||||||
|
var URL_STATUS_WS = "ws://" + URL_HOST_BASE + "/status"
|
||||||
|
var URL_TRAFFIC_WS = "ws://" + URL_HOST_BASE + "/traffic";
|
||||||
|
var URL_WEATHER_WS = "ws://" + URL_HOST_BASE + "/weather";
|
||||||
|
|
||||||
// define the module with dependency on mobile-angular-ui
|
// define the module with dependency on mobile-angular-ui
|
||||||
//var app = angular.module('stratux', ['ngRoute', 'mobile-angular-ui', 'mobile-angular-ui.gestures', 'appControllers']);
|
//var app = angular.module('stratux', ['ngRoute', 'mobile-angular-ui', 'mobile-angular-ui.gestures', 'appControllers']);
|
||||||
|
@ -13,12 +15,42 @@ var appControllers = angular.module('appControllers', []);
|
||||||
|
|
||||||
app.config(function ($stateProvider, $urlRouterProvider) {
|
app.config(function ($stateProvider, $urlRouterProvider) {
|
||||||
$stateProvider
|
$stateProvider
|
||||||
.state('home', { url: '/', templateUrl: 'plates/status.html', controller: 'StatusCtrl', reloadOnSearch: false })
|
.state('home', {
|
||||||
.state('weather', { url: '/weather', templateUrl: 'plates/weather.html', controller: 'WeatherCtrl', reloadOnSearch: false })
|
url: '/',
|
||||||
.state('traffic', { url: '/traffic', templateUrl: 'plates/traffic.html', controller: 'TrafficCtrl', reloadOnSearch: false })
|
templateUrl: 'plates/status.html',
|
||||||
.state('gps', { url: '/gps', templateUrl: 'plates/gps.html', controller: 'GPSCtrl', reloadOnSearch: false })
|
controller: 'StatusCtrl',
|
||||||
.state('logs', { url: '/logs', templateUrl: 'plates/logs.html', controller: 'LogsCtrl', reloadOnSearch: false })
|
reloadOnSearch: false
|
||||||
.state('settings', { url: '/settings', templateUrl: 'plates/settings.html',controller: 'SettingsCtrl', reloadOnSearch: false });
|
})
|
||||||
|
.state('weather', {
|
||||||
|
url: '/weather',
|
||||||
|
templateUrl: 'plates/weather.html',
|
||||||
|
controller: 'WeatherCtrl',
|
||||||
|
reloadOnSearch: false
|
||||||
|
})
|
||||||
|
.state('traffic', {
|
||||||
|
url: '/traffic',
|
||||||
|
templateUrl: 'plates/traffic.html',
|
||||||
|
controller: 'TrafficCtrl',
|
||||||
|
reloadOnSearch: false
|
||||||
|
})
|
||||||
|
.state('gps', {
|
||||||
|
url: '/gps',
|
||||||
|
templateUrl: 'plates/gps.html',
|
||||||
|
controller: 'GPSCtrl',
|
||||||
|
reloadOnSearch: false
|
||||||
|
})
|
||||||
|
.state('logs', {
|
||||||
|
url: '/logs',
|
||||||
|
templateUrl: 'plates/logs.html',
|
||||||
|
controller: 'LogsCtrl',
|
||||||
|
reloadOnSearch: false
|
||||||
|
})
|
||||||
|
.state('settings', {
|
||||||
|
url: '/settings',
|
||||||
|
templateUrl: 'plates/settings.html',
|
||||||
|
controller: 'SettingsCtrl',
|
||||||
|
reloadOnSearch: false
|
||||||
|
});
|
||||||
$urlRouterProvider.otherwise('/');
|
$urlRouterProvider.otherwise('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="section text-left help-page">
|
||||||
|
<p>The <strong>GPS / AHRS</strong> page provides a view on the current status of GPS data and AHRS orientation. The Satellite count is located on the <strong>Status</strong> page.</p>
|
||||||
|
<p>The <strong>GPS</strong> reports position with estimated accuracy, ground track, ground speed, and GPS derived altitude are displayed along with the location depicted on a world map.</p>
|
||||||
|
<p>The <strong>AHRS</strong> reports magnetic heading, pressure altitude, pitch and roll are displayed along with a graphical representation of movement.</p>
|
||||||
|
<p>The AHR graphical depiction is a 3-dimentional paper airplane which rotates about a heading with 000° being off in the distance and 180° being down in front. The airplane will pitch up/down and left/right based on the data from the AHRS. To aid with recognizing orientation, the <span class="paperairplane_left">left wing is blue</span> and the <span class="paperairplane_right">right wing is tan</span>.</p>
|
||||||
|
<p class="text-warning">NOTE: This page is for reference only and must not be used for flight operations.</p>
|
||||||
|
</div>
|
|
@ -1,13 +1,66 @@
|
||||||
<div class="list-group text-center">
|
<div class="col-sm-12">
|
||||||
<div class="list-group-item list-group-item-home">
|
<div class="col-sm-6">
|
||||||
<h2>GPS Status</h2>
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><span class="panel_label">GPS</span></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<span class="col-xs-12">
|
||||||
|
<div class="map-container">
|
||||||
|
<div id="map_display" class="world-map" ng-attr-style="background-position:{{map_pos_x + 'px ' + map_pos_y + 'px'}}; width:{{map_width}}px; height:{{map_height}}px;">
|
||||||
|
<div class="mark-position" ng-style="{left: map_mark_x+'px', top: map_mark_y+'px'}">
|
||||||
|
<span class="fa fa-crosshairs icon-white"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="separator"></div>
|
||||||
|
<div class="row">
|
||||||
|
<strong class="col-xs-6 text-center">Location:</strong>
|
||||||
|
<strong class="col-xs-6 text-center">Track:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="col-xs-6 text-center">{{gps_lat}}, {{gps_lon}} ± {{gps_accuracy}} m</span>
|
||||||
|
<span class="col-xs-6 text-center">{{gps_track}}° @ {{gps_speed}}KTS @ {{gps_alt}} ft</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading"><span class="panel_label">AHRS</span></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<span class="col-xs-12">
|
||||||
|
<canvas id="ahrs_display">
|
||||||
|
your web browser doesn't support the <canvas>> element.
|
||||||
|
</canvas>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="separator"></div>
|
||||||
|
<div class="row">
|
||||||
|
<strong class="col-xs-3 text-center">Heading:</strong>
|
||||||
|
<strong class="col-xs-3 text-center">Pitch:</strong>
|
||||||
|
<strong class="col-xs-3 text-center">Roll:</strong>
|
||||||
|
<strong class="col-xs-3 text-center">P-Alt:</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="col-xs-3 text-center">{{ahrs_heading}}°</span>
|
||||||
|
<span class="col-xs-3 text-center">{{ahrs_pitch}}°</span>
|
||||||
|
<span class="col-xs-3 text-center">{{ahrs_roll}}°</span>
|
||||||
|
<span class="col-xs-3 text-center">{{ahrs_alt}}°</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group-item list-group-item-home">
|
|
||||||
<div>
|
|
||||||
<i class="fa fa-globe feature-icon text-primary"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
this space reserved to display GPS/AHRS status
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">Raw GPS / AHRS Data</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<pre>{{raw_data}}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
|
@ -0,0 +1,353 @@
|
||||||
|
function ahrsRenderer(location_id) {
|
||||||
|
this.gcontext = {}; // globals
|
||||||
|
this.gl = null;
|
||||||
|
|
||||||
|
this.width = -1;
|
||||||
|
this.height = -1;
|
||||||
|
|
||||||
|
this.locationID = location_id;
|
||||||
|
this.canvas = document.getElementById(location_id);
|
||||||
|
this.canvas_container = document.getElementById(location_id).parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
ahrsRenderer.prototype = {
|
||||||
|
constructor: ahrsRenderer,
|
||||||
|
|
||||||
|
_init_canvas: function () {
|
||||||
|
var gl = initWebGL(this.locationID);
|
||||||
|
if (!gl)
|
||||||
|
return;
|
||||||
|
this.gl = gl;
|
||||||
|
|
||||||
|
vertex_shader = ' \
|
||||||
|
uniform mat4 u_modelViewProjMatrix; \
|
||||||
|
uniform mat4 u_normalMatrix; \
|
||||||
|
uniform vec3 lightDir; \
|
||||||
|
attribute vec3 vNormal; \
|
||||||
|
attribute vec4 vColor; \
|
||||||
|
attribute vec4 vPosition; \
|
||||||
|
varying float v_Dot; \
|
||||||
|
varying vec4 v_Color; \
|
||||||
|
void main() { \
|
||||||
|
gl_Position = u_modelViewProjMatrix * vPosition; \
|
||||||
|
v_Color = vColor; \
|
||||||
|
vec4 transNormal = u_normalMatrix * vec4(vNormal, 1); \
|
||||||
|
v_Dot = max(dot(transNormal.xyz, lightDir), 0.0); \
|
||||||
|
}';
|
||||||
|
|
||||||
|
color_shader = '\
|
||||||
|
precision mediump float; \
|
||||||
|
varying float v_Dot; \
|
||||||
|
varying vec4 v_Color; \
|
||||||
|
void main() { \
|
||||||
|
gl_FragColor = vec4(v_Color.xyz * v_Dot, v_Color.a); \
|
||||||
|
}';
|
||||||
|
|
||||||
|
var vertexShader = loadShaderVertexScript(gl, vertex_shader);
|
||||||
|
var colorShader = loadShaderFragmentScript(gl, color_shader);
|
||||||
|
|
||||||
|
var program = gl.createProgram();
|
||||||
|
|
||||||
|
gl.attachShader(program, vertexShader);
|
||||||
|
gl.attachShader(program, colorShader);
|
||||||
|
|
||||||
|
// Bind attributes
|
||||||
|
gl.bindAttribLocation(program, 0, "vNormal");
|
||||||
|
gl.bindAttribLocation(program, 1, "vColor");
|
||||||
|
gl.bindAttribLocation(program, 2, "vPosition");
|
||||||
|
|
||||||
|
gl.linkProgram(program);
|
||||||
|
|
||||||
|
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||||
|
if (!linked && !gl.isContextLost()) {
|
||||||
|
// something went wrong with the link
|
||||||
|
var error = gl.getProgramInfoLog(program);
|
||||||
|
log("Error in program linking:" + error);
|
||||||
|
gl.deleteProgram(program);
|
||||||
|
gl.deleteProgram(fragmentShader);
|
||||||
|
gl.deleteProgram(vertexShader);
|
||||||
|
} else {
|
||||||
|
gl.useProgram(program);
|
||||||
|
gl.clearColor(0, 0, 0, 0) // rgba for background color
|
||||||
|
gl.clearDepth(10000); //??
|
||||||
|
|
||||||
|
if (false /* funcky blending */ ) {
|
||||||
|
gl.disable(gl.DEPTH_TEST);
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
gl.depthFunc(gl.LESS);
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
|
||||||
|
} else {
|
||||||
|
gl.enable(gl.DEPTH_TEST);
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gcontext.program = program;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
|
this._init_canvas(); // init the canvas
|
||||||
|
g = this.gcontext;
|
||||||
|
gl = this.gl;
|
||||||
|
|
||||||
|
if (gl === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Set up a uniform variable for the shaders
|
||||||
|
gl.uniform3f(gl.getUniformLocation(g.program, "lightDir"), 0, 1, 1); // above and back
|
||||||
|
|
||||||
|
// Create a box. On return 'gl' contains a 'box' property with
|
||||||
|
// the BufferObjects containing the arrays for vertices,
|
||||||
|
// normals, texture coords, and indices.
|
||||||
|
// g.box = makeBox(gl);
|
||||||
|
g.box = this.makePaperAirplane(gl);
|
||||||
|
|
||||||
|
// Create some matrices to use later and save their locations in the shaders
|
||||||
|
g.mvMatrix = new J3DIMatrix4();
|
||||||
|
g.u_normalMatrixLoc = gl.getUniformLocation(g.program, "u_normalMatrix");
|
||||||
|
g.normalMatrix = new J3DIMatrix4();
|
||||||
|
g.u_modelViewProjMatrixLoc =
|
||||||
|
gl.getUniformLocation(g.program, "u_modelViewProjMatrix");
|
||||||
|
g.mvpMatrix = new J3DIMatrix4();
|
||||||
|
|
||||||
|
gl.enableVertexAttribArray(0); // lighting
|
||||||
|
gl.enableVertexAttribArray(1); // color
|
||||||
|
gl.enableVertexAttribArray(2); // vertices
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, g.box.vertexObject);
|
||||||
|
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, g.box.normalObject);
|
||||||
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, g.box.colorObject);
|
||||||
|
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0); // was gl.UNSIGNED_BYTE
|
||||||
|
|
||||||
|
// Bind the index array
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, g.box.indexObject);
|
||||||
|
|
||||||
|
this.pitch = 0;
|
||||||
|
this.roll = 0;
|
||||||
|
this.heading = 0;
|
||||||
|
|
||||||
|
this.resize();
|
||||||
|
},
|
||||||
|
|
||||||
|
resize: function () {
|
||||||
|
gl = this.gl;
|
||||||
|
g = this.gcontext;
|
||||||
|
if (gl === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var canvasWidth = this.canvas_container.offsetWidth - 12; // was (2*(this.canvas_container.offsetLeft)) // account for padding adjustments
|
||||||
|
|
||||||
|
if (canvasWidth !== this.width) {
|
||||||
|
this.width = canvasWidth;
|
||||||
|
this.height = canvasWidth *0.5;
|
||||||
|
|
||||||
|
this.canvas.width = this.width;
|
||||||
|
this.canvas.height = this.height;
|
||||||
|
// Set the viewport and projection matrix for the scene
|
||||||
|
gl.viewport(0, 0, this.width, this.height);
|
||||||
|
g.perspectiveMatrix = new J3DIMatrix4();
|
||||||
|
g.perspectiveMatrix.perspective(30, this.width / this.height, 1, 10000);
|
||||||
|
g.perspectiveMatrix.lookat( 0, 0, 4, // eye location
|
||||||
|
0, 0, 0, // focal point
|
||||||
|
0, 1, 0); // up vector
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
orientation: function (x, y, z) {
|
||||||
|
if (x > 360) x -= 360;
|
||||||
|
if (y > 360) y -= 360;
|
||||||
|
if (z > 360) z -= 360;
|
||||||
|
this.pitch = x; // need to reorient to level
|
||||||
|
this.roll = y;
|
||||||
|
this.heading = z;
|
||||||
|
},
|
||||||
|
|
||||||
|
animate: function (t, x, y, z) {
|
||||||
|
var FPS = 24; // we assume we can maintain a certain frame rate
|
||||||
|
var x_inc = ((x - this.pitch) / (FPS * t));
|
||||||
|
var y_inc = ((y - this.roll) / (FPS * t));
|
||||||
|
if ((z < this.heading) && (this.heading - z) > 180) {
|
||||||
|
// let the animation wrap aroung gracefully clockwise
|
||||||
|
z += 360;
|
||||||
|
} else if ((z > this.heading) && (z - this.heading) > 180) {
|
||||||
|
// let the animation wrap aroung gracefully counter clockwise
|
||||||
|
this.heading += 360;
|
||||||
|
}
|
||||||
|
var z_inc = ((z - this.heading) / (FPS * t));
|
||||||
|
var _this = this;
|
||||||
|
//console.log(z_inc);
|
||||||
|
var frames = 0;
|
||||||
|
var f = function () {
|
||||||
|
_this.pitch += x_inc;
|
||||||
|
_this.roll += y_inc;
|
||||||
|
_this.heading += z_inc;
|
||||||
|
if (frames < (FPS * t)) {
|
||||||
|
_this.draw();
|
||||||
|
frames++
|
||||||
|
window.requestAnimationFrame(f, _this.canvas); // recurse
|
||||||
|
} else {
|
||||||
|
_this.orientation(x, y, z);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
f();
|
||||||
|
},
|
||||||
|
|
||||||
|
draw: function () {
|
||||||
|
this.resize();
|
||||||
|
gl = this.gl;
|
||||||
|
g = this.gcontext;
|
||||||
|
if (gl === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Clear the canvas
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Make a model/view matrix.
|
||||||
|
g.mvMatrix.makeIdentity();
|
||||||
|
|
||||||
|
g.mvMatrix.rotate(15, 1, 0, 0); // adjust viewing angle slightly by pitching the airplane up
|
||||||
|
|
||||||
|
g.mvMatrix.rotate(-this.pitch, 1, 0, 0);
|
||||||
|
g.mvMatrix.rotate(-this.roll, 0, 0, 1);
|
||||||
|
g.mvMatrix.rotate(-this.heading, 0, 1, 0);
|
||||||
|
|
||||||
|
// Construct the normal matrix from the model-view matrix and pass it in
|
||||||
|
g.normalMatrix.load(g.mvMatrix);
|
||||||
|
g.normalMatrix.invert();
|
||||||
|
g.normalMatrix.transpose();
|
||||||
|
g.normalMatrix.setUniform(gl, g.u_normalMatrixLoc, false);
|
||||||
|
|
||||||
|
// Construct the model-view * projection matrix and pass it in
|
||||||
|
g.mvpMatrix.load(g.perspectiveMatrix);
|
||||||
|
g.mvpMatrix.multiply(g.mvMatrix);
|
||||||
|
g.mvpMatrix.setUniform(gl, g.u_modelViewProjMatrixLoc, false);
|
||||||
|
|
||||||
|
// Draw the object
|
||||||
|
gl.drawElements(gl.TRIANGLES, g.box.numIndices, gl.UNSIGNED_BYTE, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
makePaperAirplane: function (ctx) {
|
||||||
|
// Return an object with the following properties:
|
||||||
|
// normalObject WebGLBuffer object for normals (for lighting)
|
||||||
|
// vertexObject WebGLBuffer object for vertices (actual 3d object)
|
||||||
|
// indexObject WebGLBuffer object for indices (index of triangles within object)
|
||||||
|
// numIndices The number of indices in the indexObject (number of triangles)
|
||||||
|
|
||||||
|
// constants to make it easy to adjust the proportions and axis of the object
|
||||||
|
var LENGTH = 1; var WIDTH = 1; var DEPTH = 0.33; var SPREAD = 0.1;
|
||||||
|
var CENTERX = 0; var CENTERY = 0; var CENTERZ = 0.5;
|
||||||
|
|
||||||
|
// vertex coords array
|
||||||
|
var vertices = new Float32Array([
|
||||||
|
CENTERX, CENTERY, -LENGTH-CENTERZ,
|
||||||
|
-WIDTH, CENTERY, LENGTH-CENTERZ,
|
||||||
|
-SPREAD, CENTERY, LENGTH-CENTERZ, // left wing
|
||||||
|
|
||||||
|
CENTERX, CENTERY, -LENGTH-CENTERZ,
|
||||||
|
CENTERX, -DEPTH, LENGTH-CENTERZ,
|
||||||
|
-SPREAD, CENTERY, LENGTH-CENTERZ, // left center section
|
||||||
|
|
||||||
|
CENTERX, CENTERY, -LENGTH-CENTERZ,
|
||||||
|
CENTERX, -DEPTH, LENGTH-CENTERZ,
|
||||||
|
SPREAD, CENTERY, LENGTH-CENTERZ, // right center section
|
||||||
|
|
||||||
|
CENTERX, CENTERY, -LENGTH-CENTERZ,
|
||||||
|
WIDTH, CENTERY, LENGTH-CENTERZ,
|
||||||
|
SPREAD, CENTERY, LENGTH-CENTERZ // right wing
|
||||||
|
]);
|
||||||
|
|
||||||
|
// normal array for light reflection and shading
|
||||||
|
var normals = new Float32Array([
|
||||||
|
0, 1, 0,
|
||||||
|
0, 1, 0,
|
||||||
|
0, 1, 0, // left wing actual perpendicular is up
|
||||||
|
1, 1, 0,
|
||||||
|
1, 1, 0,
|
||||||
|
1, 1, 0, // left center section estmated perpendicular is right
|
||||||
|
-1, 1, 0,
|
||||||
|
-1, 1, 0,
|
||||||
|
-1, 1, 0, // right center section estmated perpendicular is left
|
||||||
|
0, 1, 0,
|
||||||
|
0, 1, 0,
|
||||||
|
0, 1, 0 // right wing actual perpendicular is up
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
// index array
|
||||||
|
var indices = new Uint8Array([
|
||||||
|
0, 1, 2, // left wing
|
||||||
|
3, 4, 5, // left center section
|
||||||
|
6, 7, 8, // right center section
|
||||||
|
9, 10, 11 // right wing
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set up the array of colors for the cube's faces
|
||||||
|
// the tip of the paper aiplane is lighter and then a gradiant back to red for teh left and a green for the right
|
||||||
|
var colors_rg = new Uint8Array([
|
||||||
|
1, 0, 0, 1,
|
||||||
|
1, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, // left wing
|
||||||
|
|
||||||
|
1, 0, 0, 1,
|
||||||
|
1, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, // left center section
|
||||||
|
|
||||||
|
0, 1, 0, 1,
|
||||||
|
0, 1, 0, 1,
|
||||||
|
0, 1, 0, 1, // right center section
|
||||||
|
|
||||||
|
0, 1, 0, 1,
|
||||||
|
0, 1, 0, 1,
|
||||||
|
0, 1, 0, 1 // right wing
|
||||||
|
]);
|
||||||
|
|
||||||
|
var colors_bt = new Float32Array([
|
||||||
|
.21, .31, .49, 1,
|
||||||
|
.21, .31, .49, 1,
|
||||||
|
.21, .31, .49, 1,
|
||||||
|
|
||||||
|
.21, .31, .49, 1,
|
||||||
|
.21, .31, .49, 1,
|
||||||
|
.21, .31, .49, 1,
|
||||||
|
|
||||||
|
.39, .38, .22, 1,
|
||||||
|
.39, .38, .22, 1,
|
||||||
|
.39, .38, .22, 1,
|
||||||
|
|
||||||
|
.39, .38, .22, 1,
|
||||||
|
.39, .38, .22, 1,
|
||||||
|
.39, .38, .22, 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
var retval = {};
|
||||||
|
|
||||||
|
retval.vertexObject = ctx.createBuffer();
|
||||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
|
||||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
|
||||||
|
|
||||||
|
retval.normalObject = ctx.createBuffer();
|
||||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
|
||||||
|
ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW);
|
||||||
|
|
||||||
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
|
||||||
|
|
||||||
|
retval.indexObject = ctx.createBuffer();
|
||||||
|
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
|
||||||
|
ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, indices, ctx.STATIC_DRAW);
|
||||||
|
ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
|
||||||
|
|
||||||
|
// Set up the vertex buffer for the colors
|
||||||
|
retval.colorObject = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, retval.colorObject);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, colors_bt, gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
retval.numIndices = indices.length;
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,125 @@
|
||||||
angular.module('appControllers').controller('GPSCtrl', GPSCtrl); // get the main module contollers set
|
angular.module('appControllers').controller('GPSCtrl', GPSCtrl); // get the main module contollers set
|
||||||
GPSCtrl.$inject = ['$rootScope', '$scope', '$state', '$http']; // Inject my dependencies
|
GPSCtrl.$inject = ['$rootScope', '$scope', '$state', '$http', '$interval']; // Inject my dependencies
|
||||||
|
|
||||||
// create our controller function with all necessary logic
|
// create our controller function with all necessary logic
|
||||||
function GPSCtrl($rootScope, $scope, $state, $http) {
|
function GPSCtrl($rootScope, $scope, $state, $http, $interval) {
|
||||||
|
$scope.$parent.helppage = 'plates/gps-help.html';
|
||||||
|
|
||||||
/*
|
var status = {};
|
||||||
$state.get('weather').onEnter = function () {
|
var display_area_size = -1;
|
||||||
};
|
|
||||||
$state.get('weather').onExit = function () {
|
function sizeMap() {
|
||||||
|
var width = 0;
|
||||||
|
var el = document.getElementById("map_display").parentElement;
|
||||||
|
width = el.offsetWidth; // was (- (2 * el.offsetLeft))
|
||||||
|
if (width !== display_area_size) {
|
||||||
|
display_area_size = width;
|
||||||
|
$scope.map_width = width;
|
||||||
|
$scope.map_height = width *0.5;
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGeoReferenceMap(la, lo) {
|
||||||
|
// Mercator projection
|
||||||
|
// var map = "img/world.png";
|
||||||
|
var map_width = 3929;
|
||||||
|
var map_height = 2184;
|
||||||
|
var map_zero_x = 1868;
|
||||||
|
var map_zero_y = 1412;
|
||||||
|
var font_size = 18; // size of font used for marker
|
||||||
|
|
||||||
|
sizeMap();
|
||||||
|
var div_width = $scope.map_width;
|
||||||
|
var div_height = $scope.map_height;
|
||||||
|
|
||||||
|
|
||||||
|
// longitude: just scale and shift
|
||||||
|
var x = (map_width * (180 + lo) / 360) - (map_width/2 - map_zero_x); // longitude_shift;
|
||||||
|
|
||||||
|
// latitude: using the Mercator projection
|
||||||
|
la_rad = la * Math.PI / 180; // convert from degrees to radians
|
||||||
|
merc_n = Math.log(Math.tan((la_rad / 2) + (Math.PI / 4))); // do the Mercator projection (w/ equator of 2pi units)
|
||||||
|
var y = (map_height / 2) - (map_width * merc_n / (2 * Math.PI)) - (map_height/2 - map_zero_y); // fit it to our map
|
||||||
|
|
||||||
|
// dot = '<div style="position:absolute; width:' + dot_size + 'px; height:' + dot_size + 'px; top:' + y + 'px; left:' + x + 'px; background:#ff7f00;"></div>';
|
||||||
|
// <img src="map-world-medium.png" style="position:absolute;top:0px;left:0px">
|
||||||
|
$scope.map_pos_x = map_width - Math.round(x - (div_width / 2));
|
||||||
|
$scope.map_pos_y = map_height - Math.round(y - (div_height / 2));
|
||||||
|
|
||||||
|
$scope.map_mark_x = Math.round((div_width - (font_size * 0.85)) / 2);
|
||||||
|
$scope.map_mark_y = Math.round((div_height - font_size) / 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function loadStatus(data) {
|
||||||
|
status = angular.fromJson(data);
|
||||||
|
// consider using angular.extend()
|
||||||
|
$scope.raw_data = angular.toJson(data, true); // makes it pretty
|
||||||
|
|
||||||
|
/* not currently used
|
||||||
|
$scope.gps_satellites = status.Satellites;
|
||||||
*/
|
*/
|
||||||
|
$scope.gps_accuracy = Math.round(status.Accuracy);
|
||||||
|
|
||||||
|
// NACp should be an integer value in the range of 0 .. 11
|
||||||
|
// var accuracies = ["≥ 10 NM", "< 10 NM", "< 4 NM", "< 2 NM", "< 1 NM", "< 0.5 NM", "< 0.3 NM", "< 0.1 NM", "< 100 m", "< 30 m", "< 10 m", "< 3 m"];
|
||||||
|
// $scope.gps_accuracy = accuracies[status.NACp];
|
||||||
|
// "LastFixLocalTime":"2015-10-11T16:47:03.523085162Z"
|
||||||
|
|
||||||
|
$scope.gps_lat = status.Lat.toPrecision(6); // result is string
|
||||||
|
$scope.gps_lon = status.Lng.toPrecision(6); // result is string
|
||||||
|
$scope.gps_alt = Math.round(status.Alt);
|
||||||
|
$scope.gps_track = status.TrueCourse;
|
||||||
|
$scope.gps_speed = status.GroundSpeed;
|
||||||
|
// "LastGroundTrackTime":"0001-01-01T00:00:00Z"
|
||||||
|
|
||||||
|
/* not currently used
|
||||||
|
$scope.ahrs_temp = status.Temp;
|
||||||
|
*/
|
||||||
|
$scope.ahrs_alt = Math.round(status.Pressure_alt);
|
||||||
|
|
||||||
|
$scope.ahrs_heading = Math.round(status.Gyro_heading);
|
||||||
|
// pitch and roll are in degrees
|
||||||
|
$scope.ahrs_pitch = Math.round(status.Pitch);
|
||||||
|
$scope.ahrs_roll = Math.round(status.Roll);
|
||||||
|
// "LastAttitudeTime":"2015-10-11T16:47:03.534615187Z"
|
||||||
|
|
||||||
|
setGeoReferenceMap(status.Lat, status.Lng);
|
||||||
|
|
||||||
|
// $scope.$apply();
|
||||||
|
};
|
||||||
|
|
||||||
|
function getStatus() {
|
||||||
|
// Simple GET request example (note: responce is asynchronous)
|
||||||
|
$http.get(URL_GPS_GET).
|
||||||
|
then(function (response) {
|
||||||
|
loadStatus(response.data);
|
||||||
|
ahrs.animate(1, $scope.ahrs_pitch, $scope.ahrs_roll, $scope.ahrs_heading);
|
||||||
|
// $scope.$apply();
|
||||||
|
}, function (response) {
|
||||||
|
$scope.raw_data = "error getting gps / ahrs status";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateStatus = $interval(function () {
|
||||||
|
// refresh GPS/AHRS status once each second (aka polling)
|
||||||
|
getStatus();
|
||||||
|
}, (1 * 1000), 0, false);
|
||||||
|
|
||||||
|
$state.get('gps').onEnter = function () {
|
||||||
|
// everything gets handled correctly by the controller
|
||||||
|
};
|
||||||
|
|
||||||
|
$state.get('gps').onExit = function () {
|
||||||
|
// stop polling for gps/ahrs status
|
||||||
|
$interval.cancel(updateStatus);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// GPS/AHRS Controller tasks go here
|
||||||
|
var ahrs = new ahrsRenderer("ahrs_display");
|
||||||
|
ahrs.init();
|
||||||
|
ahrs.orientation(0, 0, 0);
|
||||||
|
|
||||||
// Weather Controller tasks go here
|
|
||||||
};
|
};
|
|
@ -11,7 +11,7 @@ function StatusCtrl($rootScope, $scope, $state, $http) {
|
||||||
return; // we are getting called once after clicking away from the status page
|
return; // we are getting called once after clicking away from the status page
|
||||||
|
|
||||||
if (($scope.socket === undefined) || ($scope.socket === null)) {
|
if (($scope.socket === undefined) || ($scope.socket === null)) {
|
||||||
socket = new WebSocket('ws://' + URL_HOST_BASE + '/status');
|
socket = new WebSocket(URL_STATUS_WS);
|
||||||
$scope.socket = socket; // store socket in scope for enter/exit usage
|
$scope.socket = socket; // store socket in scope for enter/exit usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ function TrafficCtrl($rootScope, $scope, $state, $http, $interval) {
|
||||||
return; // we are getting called once after clicking away from the status page
|
return; // we are getting called once after clicking away from the status page
|
||||||
|
|
||||||
if (($scope.socket === undefined) || ($scope.socket === null)) {
|
if (($scope.socket === undefined) || ($scope.socket === null)) {
|
||||||
socket = new WebSocket('ws://' + URL_HOST_BASE + '/traffic');
|
socket = new WebSocket(URL_TRAFFIC_WS);
|
||||||
$scope.socket = socket; // store socket in scope for enter/exit usage
|
$scope.socket = socket; // store socket in scope for enter/exit usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@ function WeatherCtrl($rootScope, $scope, $state, $http, $interval) {
|
||||||
return; // we are getting called once after clicking away from the status page
|
return; // we are getting called once after clicking away from the status page
|
||||||
|
|
||||||
if (($scope.socket === undefined) || ($scope.socket === null)) {
|
if (($scope.socket === undefined) || ($scope.socket === null)) {
|
||||||
socket = new WebSocket('ws://' + URL_HOST_BASE + '/weather');
|
socket = new WebSocket(URL_WEATHER_WS);
|
||||||
$scope.socket = socket; // store socket in scope for enter/exit usage
|
$scope.socket = socket; // store socket in scope for enter/exit usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
ssh<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">Raw Configuration</div>
|
<div class="panel-heading">Raw Configuration</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
|
|
@ -29,13 +29,13 @@
|
||||||
<div class="row" ng-class="{'section_invisible': !visible_uat}">
|
<div class="row" ng-class="{'section_invisible': !visible_uat}">
|
||||||
<span class="col-xs-1"></span>
|
<span class="col-xs-1"></span>
|
||||||
<label class="col-xs-3">UAT:</label>
|
<label class="col-xs-3">UAT:</label>
|
||||||
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style2" ng-attr-style="width:{{100*UAT_messages_last_minute / UAT_messages_max}}%">{{UAT_messages_last_minute}}</div></div></span>
|
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style2" ng-attr-style="width:{{UAT_messages_max ? 100*UAT_messages_last_minute / UAT_messages_max : 0}}%">{{UAT_messages_last_minute}}</div></div></span>
|
||||||
<span class="col-xs-2 text-right">{{UAT_messages_max}}</span>
|
<span class="col-xs-2 text-right">{{UAT_messages_max}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" ng-class="{'section_invisible': !visible_es}">
|
<div class="row" ng-class="{'section_invisible': !visible_es}">
|
||||||
<span class="col-xs-1"></span>
|
<span class="col-xs-1"></span>
|
||||||
<label class="col-xs-3">1090ES:</label>
|
<label class="col-xs-3">1090ES:</label>
|
||||||
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style1" ng-attr-style="width:{{100*ES_messages_last_minute / ES_messages_max}}%;">{{ES_messages_last_minute}}</div></div></span>
|
<span class="col-xs-6"><div class="bar_container"><div class="bar_display traffic-style1" ng-attr-style="width:{{ES_messages_max ? 100*ES_messages_last_minute / ES_messages_max : 0}}%;">{{ES_messages_last_minute}}</div></div></span>
|
||||||
<span class="col-xs-2 text-right">{{ES_messages_max}}</span>
|
<span class="col-xs-2 text-right">{{ES_messages_max}}</span>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
|
|
Ładowanie…
Reference in New Issue