aboutsummaryrefslogtreecommitdiff
path: root/web/app
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2016-09-18 11:40:46 +1200
committerAnton Tananaev <anton.tananaev@gmail.com>2016-09-18 11:40:46 +1200
commit2608adeff46f1286deff11a5320cee5647f6d41b (patch)
treea16014c04c755ec701b3237b0cccc7d8cc406960 /web/app
parentd5ca160059220a07c71c95cc85696e0d66bf9c6e (diff)
downloadtrackermap-web-2608adeff46f1286deff11a5320cee5647f6d41b.tar.gz
trackermap-web-2608adeff46f1286deff11a5320cee5647f6d41b.tar.bz2
trackermap-web-2608adeff46f1286deff11a5320cee5647f6d41b.zip
Move web interface from main repo
Diffstat (limited to 'web/app')
-rw-r--r--web/app/Application.js117
-rw-r--r--web/app/AttributeFormatter.js81
-rw-r--r--web/app/GeofenceConverter.js79
-rw-r--r--web/app/Style.js79
-rw-r--r--web/app/controller/Root.js207
-rw-r--r--web/app/model/Attribute.js30
-rw-r--r--web/app/model/Command.js30
-rw-r--r--web/app/model/Device.js45
-rw-r--r--web/app/model/Event.js43
-rw-r--r--web/app/model/Geofence.js36
-rw-r--r--web/app/model/Group.js33
-rw-r--r--web/app/model/Notification.js33
-rw-r--r--web/app/model/Position.js66
-rw-r--r--web/app/model/ReportSummary.js41
-rw-r--r--web/app/model/ReportTrip.js55
-rw-r--r--web/app/model/Server.js72
-rw-r--r--web/app/model/User.js69
-rw-r--r--web/app/store/AllDevices.js28
-rw-r--r--web/app/store/AllGeofences.js28
-rw-r--r--web/app/store/AllGroups.js28
-rw-r--r--web/app/store/AllNotifications.js30
-rw-r--r--web/app/store/Attributes.js24
-rw-r--r--web/app/store/CommandTypes.js50
-rw-r--r--web/app/store/Devices.js28
-rw-r--r--web/app/store/DistanceUnits.js39
-rw-r--r--web/app/store/Events.js25
-rw-r--r--web/app/store/GeofenceTypes.js28
-rw-r--r--web/app/store/Geofences.js28
-rw-r--r--web/app/store/Groups.js28
-rw-r--r--web/app/store/Languages.js33
-rw-r--r--web/app/store/LatestPositions.js20
-rw-r--r--web/app/store/MapTypes.js34
-rw-r--r--web/app/store/Notifications.js25
-rw-r--r--web/app/store/Positions.js25
-rw-r--r--web/app/store/ReportEventTypes.js25
-rw-r--r--web/app/store/ReportEvents.js28
-rw-r--r--web/app/store/ReportRoute.js28
-rw-r--r--web/app/store/ReportSummary.js29
-rw-r--r--web/app/store/ReportTrips.js29
-rw-r--r--web/app/store/ReportTypes.js34
-rw-r--r--web/app/store/SpeedUnits.js43
-rw-r--r--web/app/store/TimeUnits.js31
-rw-r--r--web/app/store/Users.js28
-rw-r--r--web/app/view/AttributeController.js42
-rw-r--r--web/app/view/AttributeDialog.js47
-rw-r--r--web/app/view/Attributes.js47
-rw-r--r--web/app/view/AttributesController.js114
-rw-r--r--web/app/view/BaseDialog.js23
-rw-r--r--web/app/view/BaseEditDialog.js32
-rw-r--r--web/app/view/BaseEditDialogController.js60
-rw-r--r--web/app/view/BaseMap.js117
-rw-r--r--web/app/view/BasePermissionsController.js81
-rw-r--r--web/app/view/BaseWindow.js24
-rw-r--r--web/app/view/CommandDialog.js107
-rw-r--r--web/app/view/CommandDialogController.js103
-rw-r--r--web/app/view/CustomTimeField.js29
-rw-r--r--web/app/view/DeviceDialog.js49
-rw-r--r--web/app/view/DeviceGeofences.js43
-rw-r--r--web/app/view/Devices.js176
-rw-r--r--web/app/view/DevicesController.js156
-rw-r--r--web/app/view/EditToolbar.js48
-rw-r--r--web/app/view/GeofenceDialog.js58
-rw-r--r--web/app/view/GeofenceDialogController.js51
-rw-r--r--web/app/view/GeofenceMap.js116
-rw-r--r--web/app/view/GeofenceMapController.js43
-rw-r--r--web/app/view/Geofences.js48
-rw-r--r--web/app/view/GeofencesController.js72
-rw-r--r--web/app/view/GroupDialog.js44
-rw-r--r--web/app/view/GroupGeofences.js43
-rw-r--r--web/app/view/Groups.js53
-rw-r--r--web/app/view/GroupsController.js88
-rw-r--r--web/app/view/Login.js99
-rw-r--r--web/app/view/LoginController.js109
-rw-r--r--web/app/view/Main.js67
-rw-r--r--web/app/view/MainMobile.js52
-rw-r--r--web/app/view/Map.js59
-rw-r--r--web/app/view/MapController.js317
-rw-r--r--web/app/view/MapPickerDialogController.js41
-rw-r--r--web/app/view/Notifications.js71
-rw-r--r--web/app/view/NotificationsController.js84
-rw-r--r--web/app/view/Register.js60
-rw-r--r--web/app/view/RegisterController.js43
-rw-r--r--web/app/view/Report.js63
-rw-r--r--web/app/view/ReportConfigController.js69
-rw-r--r--web/app/view/ReportConfigDialog.js98
-rw-r--r--web/app/view/ReportController.js343
-rw-r--r--web/app/view/ServerDialog.js111
-rw-r--r--web/app/view/SettingsMenu.js64
-rw-r--r--web/app/view/SettingsMenuController.js104
-rw-r--r--web/app/view/State.js45
-rw-r--r--web/app/view/StateController.js131
-rw-r--r--web/app/view/UserDevices.js47
-rw-r--r--web/app/view/UserDialog.js115
-rw-r--r--web/app/view/UserDialogController.js48
-rw-r--r--web/app/view/UserGeofences.js43
-rw-r--r--web/app/view/UserGroups.js43
-rw-r--r--web/app/view/Users.js73
-rw-r--r--web/app/view/UsersController.js140
98 files changed, 6345 insertions, 0 deletions
diff --git a/web/app/Application.js b/web/app/Application.js
new file mode 100644
index 00000000..2d806534
--- /dev/null
+++ b/web/app/Application.js
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.Application', {
+ extend: 'Ext.app.Application',
+ name: 'Traccar',
+
+ requires: [
+ 'Traccar.Style',
+ 'Traccar.AttributeFormatter'
+ ],
+
+ models: [
+ 'Server',
+ 'User',
+ 'Group',
+ 'Device',
+ 'Position',
+ 'Attribute',
+ 'Command',
+ 'Event',
+ 'Geofence',
+ 'Notification',
+ 'ReportSummary',
+ 'ReportTrip'
+ ],
+
+ stores: [
+ 'Groups',
+ 'Devices',
+ 'AllGroups',
+ 'AllDevices',
+ 'Positions',
+ 'LatestPositions',
+ 'Users',
+ 'Attributes',
+ 'MapTypes',
+ 'DistanceUnits',
+ 'SpeedUnits',
+ 'CommandTypes',
+ 'TimeUnits',
+ 'Languages',
+ 'Events',
+ 'Geofences',
+ 'AllGeofences',
+ 'Notifications',
+ 'AllNotifications',
+ 'GeofenceTypes',
+ 'ReportRoute',
+ 'ReportEvents',
+ 'ReportTrips',
+ 'ReportSummary',
+ 'ReportTypes',
+ 'ReportEventTypes'
+ ],
+
+ controllers: [
+ 'Root'
+ ],
+
+ setUser: function (data) {
+ var reader = Ext.create('Ext.data.reader.Json', {
+ model: 'Traccar.model.User'
+ });
+ this.user = reader.readRecords(data).getRecords()[0];
+ },
+
+ getUser: function () {
+ return this.user;
+ },
+
+ setServer: function (data) {
+ var reader = Ext.create('Ext.data.reader.Json', {
+ model: 'Traccar.model.Server'
+ });
+ this.server = reader.readRecords(data).getRecords()[0];
+ },
+
+ getServer: function () {
+ return this.server;
+ },
+
+ getPreference: function (key, defaultValue) {
+ return this.getUser().get(key) || this.getServer().get(key) || defaultValue;
+ },
+
+ showError: function (response) {
+ var data;
+ if (Ext.isString(response)) {
+ Ext.Msg.alert(Strings.errorTitle, response);
+ } else if (response.responseText) {
+ data = Ext.decode(response.responseText);
+ if (data.details) {
+ Ext.Msg.alert(Strings.errorTitle, data.details);
+ } else {
+ Ext.Msg.alert(Strings.errorTitle, data.message);
+ }
+ } else if (response.statusText) {
+ Ext.Msg.alert(Strings.errorTitle, response.statusText);
+ } else {
+ Ext.Msg.alert(Strings.errorTitle, Strings.errorConnection);
+ }
+ }
+});
diff --git a/web/app/AttributeFormatter.js b/web/app/AttributeFormatter.js
new file mode 100644
index 00000000..1fff07bc
--- /dev/null
+++ b/web/app/AttributeFormatter.js
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.AttributeFormatter', {
+ singleton: true,
+
+ coordinateFormatter: function (value) {
+ return value.toFixed(Traccar.Style.coordinatePrecision);
+ },
+
+ speedFormatter: function (value) {
+ return Ext.getStore('SpeedUnits').formatValue(value, Traccar.app.getPreference('speedUnit'));
+ },
+
+ courseFormatter: function (value) {
+ var courseValues = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
+ return courseValues[Math.floor(value / 45)];
+ },
+
+ distanceFormatter: function (value) {
+ return Ext.getStore('DistanceUnits').formatValue(value, Traccar.app.getPreference('distanceUnit'));
+ },
+
+ hoursFormatter: function (value) {
+ var hours = Math.round(value / 3600000);
+ return (hours + ' ' + Strings.sharedHourAbbreviation);
+ },
+
+ durationFormatter: function (value) {
+ var hours, minutes;
+ hours = Math.floor(value / 3600000);
+ minutes = Math.round((value % 3600000) / 60000);
+ return (hours + ' ' + Strings.sharedHourAbbreviation + ' ' + minutes + ' ' + Strings.sharedMinuteAbbreviation);
+ },
+
+ defaultFormatter: function (value) {
+ if (typeof value === 'number') {
+ return Number(value.toFixed(Traccar.Style.numberPrecision));
+ } else if (typeof value === 'boolean') {
+ return value ? Ext.Msg.buttonText.yes : Ext.Msg.buttonText.no;
+ } else if (value instanceof Date) {
+ if (Traccar.app.getPreference('twelveHourFormat', false)) {
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat12);
+ } else {
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat24);
+ }
+ }
+ return value;
+ },
+
+ getFormatter: function (key) {
+ if (key === 'latitude' || key === 'longitude') {
+ return this.coordinateFormatter;
+ } else if (key === 'speed') {
+ return this.speedFormatter;
+ } else if (key === 'course') {
+ return this.courseFormatter;
+ } else if (key === 'distance' || key === 'odometer' || key === 'totalDistance') {
+ return this.distanceFormatter;
+ } else if (key === 'hours') {
+ return this.hoursFormatter;
+ } else if (key === 'duration') {
+ return this.durationFormatter;
+ } else {
+ return this.defaultFormatter;
+ }
+ }
+});
diff --git a/web/app/GeofenceConverter.js b/web/app/GeofenceConverter.js
new file mode 100644
index 00000000..339f0961
--- /dev/null
+++ b/web/app/GeofenceConverter.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.GeofenceConverter', {
+ singleton: true,
+
+ wktToGeometry: function (mapView, wkt) {
+ var geometry, projection, resolutionAtEquator, pointResolution, resolutionFactor, points = [], center, radius,
+ content, i, lat, lon, coordinates;
+ if (wkt.lastIndexOf('POLYGON', 0) === 0) {
+ content = wkt.match(/\([^\(\)]+\)/);
+ if (content !== null) {
+ coordinates = content[0].match(/-?\d+\.?\d*/g);
+ if (coordinates !== null) {
+ projection = mapView.getProjection();
+ for (i = 0; i < coordinates.length; i += 2) {
+ lat = Number(coordinates[i]);
+ lon = Number(coordinates[i + 1]);
+ points.push(ol.proj.transform([lon, lat], 'EPSG:4326', projection));
+ }
+ geometry = new ol.geom.Polygon([points]);
+ }
+ }
+ } else if (wkt.lastIndexOf('CIRCLE', 0) === 0) {
+ content = wkt.match(/\([^\(\)]+\)/);
+ if (content !== null) {
+ coordinates = content[0].match(/-?\d+\.?\d*/g);
+ if (coordinates !== null) {
+ projection = mapView.getProjection();
+ center = ol.proj.transform([Number(coordinates[1]), Number(coordinates[0])], 'EPSG:4326', projection);
+ resolutionAtEquator = mapView.getResolution();
+ pointResolution = projection.getPointResolution(resolutionAtEquator, center);
+ resolutionFactor = resolutionAtEquator / pointResolution;
+ radius = (Number(coordinates[2]) / ol.proj.METERS_PER_UNIT.m) * resolutionFactor;
+ geometry = new ol.geom.Circle(center, radius);
+ }
+ }
+ }
+ return geometry;
+ },
+
+ geometryToWkt: function (projection, geometry) {
+ var result, i, center, radius, edgeCoordinate, earthSphere, groundRadius, points;
+ if (geometry instanceof ol.geom.Circle) {
+ center = geometry.getCenter();
+ radius = geometry.getRadius();
+ edgeCoordinate = [center[0] + radius, center[1]];
+ center = ol.proj.transform(center, projection, 'EPSG:4326');
+ earthSphere = new ol.Sphere(6378137);
+ groundRadius = earthSphere.haversineDistance(center,
+ ol.proj.transform(edgeCoordinate, projection, 'EPSG:4326'));
+ result = 'CIRCLE (';
+ result += center[1] + ' ' + center[0] + ', ';
+ result += Number((groundRadius).toFixed(1)) + ')';
+ } else if (geometry instanceof ol.geom.Polygon) {
+ geometry.transform(projection, 'EPSG:4326');
+ points = geometry.getCoordinates();
+ result = 'POLYGON((';
+ for (i = 0; i < points[0].length; i += 1) {
+ result += points[0][i][1] + ' ' + points[0][i][0] + ', ';
+ }
+ result = result.substring(0, result.length - 2) + '))';
+ }
+ return result;
+ }
+});
diff --git a/web/app/Style.js b/web/app/Style.js
new file mode 100644
index 00000000..b3b296b7
--- /dev/null
+++ b/web/app/Style.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.Style', {
+ singleton: true,
+
+ panelPadding: 10,
+
+ windowWidth: 640,
+ windowHeight: 480,
+
+ dateTimeFormat24: 'Y-m-d H:i:s',
+ dateTimeFormat12: 'Y-m-d g:i:s a',
+ timeFormat24: 'H:i',
+ timeFormat12: 'g:i a',
+ dateFormat: 'Y-m-d',
+ weekStartDay: 1,
+
+ deviceWidth: 350,
+
+ reportHeight: 250,
+ reportTime: 100,
+
+ mapDefaultLat: 51.507222,
+ mapDefaultLon: -0.1275,
+ mapDefaultZoom: 6,
+
+ mapRouteColor: [
+ 'rgba(21, 127, 204, 1.0)',
+ 'rgba(109, 46, 204, 1.0)',
+ 'rgba(204, 46, 162, 1.0)',
+ 'rgba(204, 46, 38, 1.0)',
+ 'rgba(128, 204, 46, 1.0)',
+ 'rgba(46, 204, 155, 1.0)'
+ ],
+ mapRouteWidth: 5,
+
+ mapArrowStrokeColor: 'rgba(50, 50, 50, 1.0)',
+ mapArrowStrokeWidth: 2,
+
+ mapTextColor: 'rgba(50, 50, 50, 1.0)',
+ mapTextStrokeColor: 'rgba(255, 255, 255, 1.0)',
+ mapTextStrokeWidth: 2,
+ mapTextOffset: 10,
+ mapTextFont: 'bold 12px sans-serif',
+
+ mapColorOnline: 'rgba(77, 250, 144, 1.0)',
+ mapColorUnknown: 'rgba(250, 190, 77, 1.0)',
+ mapColorOffline: 'rgba(255, 84, 104, 1.0)',
+
+ mapRadiusNormal: 9,
+ mapRadiusSelected: 14,
+
+ mapMaxZoom: 19,
+ mapDelay: 500,
+
+ mapGeofenceColor: 'rgba(21, 127, 204, 1.0)',
+ mapGeofenceOverlay: 'rgba(21, 127, 204, 0.2)',
+ mapGeofenceWidth: 5,
+ mapGeofenceRadius: 9,
+
+ coordinatePrecision: 6,
+ numberPrecision: 2,
+
+ reportTagfieldWidth: 375
+});
diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js
new file mode 100644
index 00000000..26f2f6f2
--- /dev/null
+++ b/web/app/controller/Root.js
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.controller.Root', {
+ extend: 'Ext.app.Controller',
+
+ requires: [
+ 'Traccar.view.Login',
+ 'Traccar.view.Main',
+ 'Traccar.view.MainMobile',
+ 'Traccar.model.Position'
+ ],
+
+ init: function () {
+ var indicator = document.createElement('div');
+ indicator.className = 'state-indicator';
+ document.body.appendChild(indicator);
+ this.isPhone = parseInt(window.getComputedStyle(indicator).getPropertyValue('z-index'), 10) !== 0;
+ },
+
+ onLaunch: function () {
+ Ext.Ajax.request({
+ scope: this,
+ url: 'api/server',
+ callback: this.onServerReturn
+ });
+ },
+
+ onServerReturn: function (options, success, response) {
+ Ext.get('spinner').remove();
+ if (success) {
+ Traccar.app.setServer(Ext.decode(response.responseText));
+ Ext.Ajax.request({
+ scope: this,
+ url: 'api/session',
+ callback: this.onSessionReturn
+ });
+ } else {
+ Traccar.app.showError(response);
+ }
+ },
+
+ onSessionReturn: function (options, success, response) {
+ if (success) {
+ Traccar.app.setUser(Ext.decode(response.responseText));
+ this.loadApp();
+ } else {
+ this.login = Ext.create('widget.login', {
+ listeners: {
+ scope: this,
+ login: this.onLogin
+ }
+ });
+ this.login.show();
+ }
+ },
+
+ onLogin: function () {
+ this.login.close();
+ this.loadApp();
+ },
+
+ loadApp: function () {
+ var attribution;
+ Ext.getStore('Groups').load();
+ Ext.getStore('Geofences').load();
+ Ext.getStore('Devices').load({
+ scope: this,
+ callback: function () {
+ this.asyncUpdate(true);
+ }
+ });
+ attribution = Ext.get('attribution');
+ if (attribution) {
+ attribution.remove();
+ }
+ if (this.isPhone) {
+ Ext.create('widget.mainMobile');
+ } else {
+ Ext.create('widget.main');
+ }
+ },
+
+ beep: function () {
+ if (!this.beepSound) {
+ this.beepSound = new Audio('beep.wav');
+ }
+ this.beepSound.play();
+ },
+
+ mutePressed: function () {
+ var muteButton = Ext.getCmp('muteButton');
+ return muteButton && !muteButton.pressed;
+ },
+
+ asyncUpdate: function (first) {
+ var protocol, socket, self = this;
+ protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ socket = new WebSocket(protocol + '//' + window.location.host + window.location.pathname + 'api/socket');
+
+ socket.onclose = function (event) {
+ self.asyncUpdate(false);
+ };
+
+ socket.onmessage = function (event) {
+ var i, j, store, data, array, entity, device, typeKey, alarmKey, text, geofence;
+
+ data = Ext.decode(event.data);
+
+ if (data.devices) {
+ array = data.devices;
+ store = Ext.getStore('Devices');
+ for (i = 0; i < array.length; i++) {
+ entity = store.getById(array[i].id);
+ if (entity) {
+ entity.set({
+ status: array[i].status,
+ lastUpdate: array[i].lastUpdate
+ }, {
+ dirty: false
+ });
+ }
+ }
+ }
+
+ if (data.positions && !data.events) {
+ array = data.positions;
+ store = Ext.getStore('LatestPositions');
+ for (i = 0; i < array.length; i++) {
+ entity = store.findRecord('deviceId', array[i].deviceId, 0, false, false, true);
+ if (entity) {
+ entity.set(array[i]);
+ } else {
+ store.add(Ext.create('Traccar.model.Position', array[i]));
+ }
+ }
+ }
+
+ if (data.events) {
+ array = data.events;
+ store = Ext.getStore('Events');
+ for (i = 0; i < array.length; i++) {
+ store.add(array[i]);
+ if (array[i].type === 'commandResult' && data.positions) {
+ for (j = 0; j < data.positions.length; j++) {
+ if (data.positions[j].id === array[i].positionId) {
+ text = data.positions[j].attributes.result;
+ break;
+ }
+ }
+ text = Strings.eventCommandResult + ': ' + text;
+ } else if (array[i].type === 'alarm' && data.positions) {
+ alarmKey = 'alarm';
+ text = Strings[alarmKey];
+ if (!text) {
+ text = alarmKey;
+ }
+ for (j = 0; j < data.positions.length; j++) {
+ if (data.positions[j].id === array[i].positionId && data.positions[j].attributes.alarm !== null) {
+ if (typeof data.positions[j].attributes.alarm === 'string' && data.positions[j].attributes.alarm.length >= 2) {
+ alarmKey = 'alarm' + data.positions[j].attributes.alarm.charAt(0).toUpperCase() + data.positions[j].attributes.alarm.slice(1);
+ text = Strings[alarmKey];
+ if (!text) {
+ text = alarmKey;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ typeKey = 'event' + array[i].type.charAt(0).toUpperCase() + array[i].type.slice(1);
+ text = Strings[typeKey];
+ if (!text) {
+ text = typeKey;
+ }
+ }
+ if (array[i].geofenceId !== 0) {
+ geofence = Ext.getStore('Geofences').getById(array[i].geofenceId);
+ if (typeof geofence !== 'undefined') {
+ text += ' \"' + geofence.get('name') + '"';
+ }
+ }
+ device = Ext.getStore('Devices').getById(array[i].deviceId);
+ if (typeof device !== 'undefined') {
+ if (self.mutePressed()) {
+ self.beep();
+ }
+ Ext.toast(text, device.get('name'));
+ }
+ }
+ }
+ };
+ }
+});
diff --git a/web/app/model/Attribute.js b/web/app/model/Attribute.js
new file mode 100644
index 00000000..78acdb1d
--- /dev/null
+++ b/web/app/model/Attribute.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Attribute', {
+ extend: 'Ext.data.Model',
+
+ fields: [{
+ name: 'priority',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'value',
+ type: 'string'
+ }]
+});
diff --git a/web/app/model/Command.js b/web/app/model/Command.js
new file mode 100644
index 00000000..3e848b57
--- /dev/null
+++ b/web/app/model/Command.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Command', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'type',
+ type: 'string'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Device.js b/web/app/model/Device.js
new file mode 100644
index 00000000..100f50f5
--- /dev/null
+++ b/web/app/model/Device.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Device', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'uniqueId',
+ type: 'string'
+ }, {
+ name: 'status',
+ type: 'string'
+ }, {
+ name: 'lastUpdate',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'groupId',
+ type: 'int'
+ }, {
+ name: 'geofenceIds'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Event.js b/web/app/model/Event.js
new file mode 100644
index 00000000..698ebb53
--- /dev/null
+++ b/web/app/model/Event.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Event', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'type',
+ type: 'string'
+ }, {
+ name: 'serverTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'positionId',
+ type: 'int'
+ }, {
+ name: 'geofenceId',
+ type: 'int'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Geofence.js b/web/app/model/Geofence.js
new file mode 100644
index 00000000..a832455a
--- /dev/null
+++ b/web/app/model/Geofence.js
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Geofence', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'description',
+ type: 'string'
+ }, {
+ name: 'area',
+ type: 'string'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Group.js b/web/app/model/Group.js
new file mode 100644
index 00000000..bb18b5b3
--- /dev/null
+++ b/web/app/model/Group.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Group', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'groupId',
+ type: 'int'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Notification.js b/web/app/model/Notification.js
new file mode 100644
index 00000000..9b4d61eb
--- /dev/null
+++ b/web/app/model/Notification.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Notification', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'type',
+ type: 'string'
+ }, {
+ name: 'userId',
+ type: 'int'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Position.js b/web/app/model/Position.js
new file mode 100644
index 00000000..e559a7ea
--- /dev/null
+++ b/web/app/model/Position.js
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Position', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'protocol',
+ type: 'string'
+ }, {
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'serverTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'deviceTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'fixTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'valid',
+ type: 'boolean'
+ }, {
+ name: 'latitude',
+ type: 'float'
+ }, {
+ name: 'longitude',
+ type: 'float'
+ }, {
+ name: 'altitude',
+ type: 'float'
+ }, {
+ name: 'speed',
+ type: 'float'
+ }, {
+ name: 'course',
+ type: 'float'
+ }, {
+ name: 'address',
+ type: 'string'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/ReportSummary.js b/web/app/model/ReportSummary.js
new file mode 100644
index 00000000..430d00b9
--- /dev/null
+++ b/web/app/model/ReportSummary.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.ReportSummary', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'deviceName',
+ type: 'string'
+ }, {
+ name: 'maxSpeed',
+ type: 'float'
+ }, {
+ name: 'averageSpeed',
+ type: 'float'
+ }, {
+ name: 'distance',
+ type: 'float'
+ }, {
+ name: 'engineHours',
+ type: 'int'
+ }]
+});
diff --git a/web/app/model/ReportTrip.js b/web/app/model/ReportTrip.js
new file mode 100644
index 00000000..cbd03d77
--- /dev/null
+++ b/web/app/model/ReportTrip.js
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.ReportTrip', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'deviceName',
+ type: 'string'
+ }, {
+ name: 'maxSpeed',
+ type: 'float'
+ }, {
+ name: 'averageSpeed',
+ type: 'float'
+ }, {
+ name: 'distance',
+ type: 'float'
+ }, {
+ name: 'duration',
+ type: 'int'
+ }, {
+ name: 'startTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'startAddress',
+ type: 'string'
+ }, {
+ name: 'endTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'endAddress',
+ type: 'string'
+ }]
+});
diff --git a/web/app/model/Server.js b/web/app/model/Server.js
new file mode 100644
index 00000000..2ed8f12f
--- /dev/null
+++ b/web/app/model/Server.js
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Server', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'registration',
+ type: 'boolean'
+ }, {
+ name: 'readonly',
+ type: 'boolean'
+ }, {
+ name: 'map',
+ type: 'string'
+ }, {
+ name: 'bingKey',
+ type: 'string'
+ }, {
+ name: 'mapUrl',
+ type: 'string'
+ }, {
+ name: 'distanceUnit',
+ type: 'string'
+ }, {
+ name: 'speedUnit',
+ type: 'string'
+ }, {
+ name: 'latitude',
+ type: 'float'
+ }, {
+ name: 'longitude',
+ type: 'float'
+ }, {
+ name: 'zoom',
+ type: 'int'
+ }, {
+ name: 'twelveHourFormat',
+ type: 'boolean'
+ }, {
+ name: 'attributes'
+ }],
+
+ proxy: {
+ type: 'ajax',
+ url: 'api/server',
+ actionMethods: {
+ update: 'PUT'
+ },
+ writer: {
+ type: 'json',
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/model/User.js b/web/app/model/User.js
new file mode 100644
index 00000000..b162bada
--- /dev/null
+++ b/web/app/model/User.js
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.User', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'email',
+ type: 'string'
+ }, {
+ name: 'password',
+ type: 'string'
+ }, {
+ name: 'admin',
+ type: 'boolean'
+ }, {
+ name: 'map',
+ type: 'string'
+ }, {
+ name: 'distanceUnit',
+ type: 'string'
+ }, {
+ name: 'speedUnit',
+ type: 'string'
+ }, {
+ name: 'latitude',
+ type: 'float'
+ }, {
+ name: 'longitude',
+ type: 'float'
+ }, {
+ name: 'zoom',
+ type: 'int'
+ }, {
+ name: 'twelveHourFormat',
+ type: 'boolean'
+ }, {
+ name: 'attributes'
+ }],
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users',
+ writer: {
+ type: 'json',
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/AllDevices.js b/web/app/store/AllDevices.js
new file mode 100644
index 00000000..3f51926e
--- /dev/null
+++ b/web/app/store/AllDevices.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.AllDevices', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Device',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/devices',
+ extraParams: {
+ all: true
+ }
+ }
+});
diff --git a/web/app/store/AllGeofences.js b/web/app/store/AllGeofences.js
new file mode 100644
index 00000000..35209967
--- /dev/null
+++ b/web/app/store/AllGeofences.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.AllGeofences', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Geofence',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/geofences',
+ extraParams: {
+ all: true
+ }
+ }
+});
diff --git a/web/app/store/AllGroups.js b/web/app/store/AllGroups.js
new file mode 100644
index 00000000..8ce0cc2f
--- /dev/null
+++ b/web/app/store/AllGroups.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.AllGroups', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Group',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/groups',
+ extraParams: {
+ all: true
+ }
+ }
+});
diff --git a/web/app/store/AllNotifications.js b/web/app/store/AllNotifications.js
new file mode 100644
index 00000000..9e9cb791
--- /dev/null
+++ b/web/app/store/AllNotifications.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.AllNotifications', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Notification',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users/notifications',
+ extraParams: {
+ all: true
+ }
+ },
+ sortOnLoad: true,
+ sorters: { property: 'type', direction : 'ASC' }
+});
diff --git a/web/app/store/Attributes.js b/web/app/store/Attributes.js
new file mode 100644
index 00000000..2019582e
--- /dev/null
+++ b/web/app/store/Attributes.js
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Attributes', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Attribute',
+
+ sorters: [{
+ property: 'priority'
+ }]
+});
diff --git a/web/app/store/CommandTypes.js b/web/app/store/CommandTypes.js
new file mode 100644
index 00000000..48405db6
--- /dev/null
+++ b/web/app/store/CommandTypes.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.CommandTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['type', 'name'],
+
+ listeners: {
+ 'beforeload' : function (store) {
+ var proxy;
+ proxy = store.getProxy();
+ proxy.setUrl('api/commandtypes?deviceId' + proxy.extraParams.deviceId);
+ }
+ },
+
+ proxy: {
+ type: 'rest',
+ url: '',
+ reader: {
+ type: 'json',
+ getData: function (data) {
+ Ext.each(data, function (entry) {
+ var nameKey, name;
+ entry.name = entry.type;
+ if (typeof entry.type !== 'undefined') {
+ nameKey = 'command' + entry.type.charAt(0).toUpperCase() + entry.type.slice(1);
+ name = Strings[nameKey];
+ if (typeof name !== 'undefined') {
+ entry.name = name;
+ }
+ }
+ });
+ return data;
+ }
+ }
+ }
+});
diff --git a/web/app/store/Devices.js b/web/app/store/Devices.js
new file mode 100644
index 00000000..c3c37331
--- /dev/null
+++ b/web/app/store/Devices.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Devices', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Device',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/devices',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/DistanceUnits.js b/web/app/store/DistanceUnits.js
new file mode 100644
index 00000000..2805d52f
--- /dev/null
+++ b/web/app/store/DistanceUnits.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.DistanceUnits', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name', 'factor'],
+
+ data: [{
+ key: 'km',
+ name: Strings.sharedKm,
+ factor: 0.001
+ }, {
+ key: 'mi',
+ name: Strings.sharedMi,
+ factor: 0.000621371
+ }],
+
+ formatValue: function (value, unit) {
+ var model;
+ if (!unit) {
+ unit = 'km';
+ }
+ model = this.findRecord('key', unit);
+ return (value * model.get('factor')).toFixed(2) + ' ' + model.get('name');
+ }
+});
diff --git a/web/app/store/Events.js b/web/app/store/Events.js
new file mode 100644
index 00000000..2698933f
--- /dev/null
+++ b/web/app/store/Events.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Events', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Event',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/events'
+ }
+});
diff --git a/web/app/store/GeofenceTypes.js b/web/app/store/GeofenceTypes.js
new file mode 100644
index 00000000..68c76bef
--- /dev/null
+++ b/web/app/store/GeofenceTypes.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.GeofenceTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name'],
+
+ data: [{
+ key: 'Polygon',
+ name: Strings.mapShapePolygon
+ }, {
+ key: 'Circle',
+ name: Strings.mapShapeCircle
+ }]
+});
diff --git a/web/app/store/Geofences.js b/web/app/store/Geofences.js
new file mode 100644
index 00000000..a0b01ae6
--- /dev/null
+++ b/web/app/store/Geofences.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Geofences', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Geofence',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/geofences',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/Groups.js b/web/app/store/Groups.js
new file mode 100644
index 00000000..8740b25d
--- /dev/null
+++ b/web/app/store/Groups.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Groups', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Group',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/groups',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/Languages.js b/web/app/store/Languages.js
new file mode 100644
index 00000000..027c96be
--- /dev/null
+++ b/web/app/store/Languages.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Languages', {
+ extend: 'Ext.data.Store',
+ fields: ['code', 'name'],
+
+ data: (function () {
+ var code, data = [];
+ for (code in Locale.languages) {
+ if (Locale.languages.hasOwnProperty(code)) {
+ data.push({
+ code: code,
+ name: Locale.languages[code].name
+ });
+ }
+ }
+ return data;
+ })()
+});
diff --git a/web/app/store/LatestPositions.js b/web/app/store/LatestPositions.js
new file mode 100644
index 00000000..c656bdcb
--- /dev/null
+++ b/web/app/store/LatestPositions.js
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.LatestPositions', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Position'
+});
diff --git a/web/app/store/MapTypes.js b/web/app/store/MapTypes.js
new file mode 100644
index 00000000..4c26ad45
--- /dev/null
+++ b/web/app/store/MapTypes.js
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.MapTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name'],
+
+ data: [{
+ key: 'osm',
+ name: Strings.mapOsm
+ }, {
+ key: 'bingRoad',
+ name: Strings.mapBingRoad
+ }, {
+ key: 'bingAerial',
+ name: Strings.mapBingAerial
+ }, {
+ key: 'custom',
+ name: Strings.mapCustom
+ }]
+});
diff --git a/web/app/store/Notifications.js b/web/app/store/Notifications.js
new file mode 100644
index 00000000..04cd9b86
--- /dev/null
+++ b/web/app/store/Notifications.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Notifications', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Notification',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users/notifications'
+ }
+});
diff --git a/web/app/store/Positions.js b/web/app/store/Positions.js
new file mode 100644
index 00000000..8675983e
--- /dev/null
+++ b/web/app/store/Positions.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Positions', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Position',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/positions'
+ }
+});
diff --git a/web/app/store/ReportEventTypes.js b/web/app/store/ReportEventTypes.js
new file mode 100644
index 00000000..27bc1fd5
--- /dev/null
+++ b/web/app/store/ReportEventTypes.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.ReportEventTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['type', 'name'],
+
+ statics: {
+ allEvents: '%'
+ }
+});
diff --git a/web/app/store/ReportEvents.js b/web/app/store/ReportEvents.js
new file mode 100644
index 00000000..1759ffde
--- /dev/null
+++ b/web/app/store/ReportEvents.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.ReportEvents', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Event',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/events',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportRoute.js b/web/app/store/ReportRoute.js
new file mode 100644
index 00000000..ab6da94c
--- /dev/null
+++ b/web/app/store/ReportRoute.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.ReportRoute', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Position',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/route',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportSummary.js b/web/app/store/ReportSummary.js
new file mode 100644
index 00000000..7c9a4fca
--- /dev/null
+++ b/web/app/store/ReportSummary.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.ReportSummary', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.ReportSummary',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/summary',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportTrips.js b/web/app/store/ReportTrips.js
new file mode 100644
index 00000000..e0d86aa4
--- /dev/null
+++ b/web/app/store/ReportTrips.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.ReportTrips', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.ReportTrip',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/trips',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportTypes.js b/web/app/store/ReportTypes.js
new file mode 100644
index 00000000..09ef61db
--- /dev/null
+++ b/web/app/store/ReportTypes.js
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.ReportTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name'],
+
+ data: [{
+ key: 'route',
+ name: Strings.reportRoute
+ }, {
+ key: 'events',
+ name: Strings.reportEvents
+ }, {
+ key: 'trips',
+ name: Strings.reportTrips
+ }, {
+ key: 'summary',
+ name: Strings.reportSummary
+ }]
+});
diff --git a/web/app/store/SpeedUnits.js b/web/app/store/SpeedUnits.js
new file mode 100644
index 00000000..296f5863
--- /dev/null
+++ b/web/app/store/SpeedUnits.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.SpeedUnits', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name', 'factor'],
+
+ data: [{
+ key: 'kn',
+ name: Strings.sharedKn,
+ factor: 1
+ }, {
+ key: 'kmh',
+ name: Strings.sharedKmh,
+ factor: 1.852
+ }, {
+ key: 'mph',
+ name: Strings.sharedMph,
+ factor: 1.15078
+ }],
+
+ formatValue: function (value, unit) {
+ var model;
+ if (!unit) {
+ unit = 'kn';
+ }
+ model = this.findRecord('key', unit);
+ return (value * model.get('factor')).toFixed(1) + ' ' + model.get('name');
+ }
+});
diff --git a/web/app/store/TimeUnits.js b/web/app/store/TimeUnits.js
new file mode 100644
index 00000000..e0326382
--- /dev/null
+++ b/web/app/store/TimeUnits.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.TimeUnits', {
+ extend: 'Ext.data.Store',
+ fields: ['name', 'factor'],
+
+ data: [{
+ name: Strings.sharedSecond,
+ factor: 1
+ }, {
+ name: Strings.sharedMinute,
+ factor: 60
+ }, {
+ name: Strings.sharedHour,
+ factor: 3600
+ }]
+});
diff --git a/web/app/store/Users.js b/web/app/store/Users.js
new file mode 100644
index 00000000..53a49ff8
--- /dev/null
+++ b/web/app/store/Users.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Users', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.User',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/view/AttributeController.js b/web/app/view/AttributeController.js
new file mode 100644
index 00000000..932a6436
--- /dev/null
+++ b/web/app/view/AttributeController.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.AttributeController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.attributeDialog',
+
+ onSaveClick: function (button) {
+ var dialog, store, record;
+ dialog = button.up('window').down('form');
+ dialog.updateRecord();
+ record = dialog.getRecord();
+ store = record.store;
+ if (store) {
+ if (record.phantom) {
+ store.add(record);
+ }
+ store.sync({
+ failure: function (batch) {
+ store.rejectChanges();
+ Traccar.app.showError(batch.exceptions[0].getError().response);
+ }
+ });
+ } else {
+ record.save();
+ }
+ button.up('window').close();
+ }
+});
diff --git a/web/app/view/AttributeDialog.js b/web/app/view/AttributeDialog.js
new file mode 100644
index 00000000..213891ec
--- /dev/null
+++ b/web/app/view/AttributeDialog.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.AttributeDialog', {
+ extend: 'Traccar.view.BaseDialog',
+
+ requires: [
+ 'Traccar.view.AttributeController'
+ ],
+
+ controller: 'attributeDialog',
+ title: Strings.sharedAttribute,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: Strings.sharedName
+ }, {
+ xtype: 'textfield',
+ name: 'value',
+ fieldLabel: Strings.stateValue
+ }]
+ },
+
+ buttons: [{
+ text: Strings.sharedSave,
+ handler: 'onSaveClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/Attributes.js b/web/app/view/Attributes.js
new file mode 100644
index 00000000..4bc7d550
--- /dev/null
+++ b/web/app/view/Attributes.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Attributes', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'attributesView',
+
+ requires: [
+ 'Traccar.view.AttributesController',
+ 'Traccar.view.EditToolbar'
+ ],
+
+ controller: 'attributes',
+
+ selType: 'rowmodel',
+
+ tbar: {
+ xtype: 'editToolbar'
+ },
+
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }, {
+ text: Strings.stateValue,
+ dataIndex: 'value',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/AttributesController.js b/web/app/view/AttributesController.js
new file mode 100644
index 00000000..91d69a8e
--- /dev/null
+++ b/web/app/view/AttributesController.js
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.AttributesController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.attributes',
+
+ requires: [
+ 'Traccar.view.AttributeDialog',
+ 'Traccar.store.Attributes',
+ 'Traccar.model.Attribute'
+ ],
+
+ init: function () {
+ var store, propertyName, i = 0, attributes;
+ store = Ext.create('Traccar.store.Attributes');
+ store.setProxy(Ext.create('Ext.data.proxy.Memory'));
+ if (typeof this.getView().record.get('attributes') === 'undefined') {
+ this.getView().record.set('attributes', {});
+ }
+ attributes = this.getView().record.get('attributes');
+ for (propertyName in attributes) {
+ if (attributes.hasOwnProperty(propertyName)) {
+ store.add(Ext.create('Traccar.model.Attribute', {
+ priority: i++,
+ name: propertyName,
+ value: this.getView().record.get('attributes')[propertyName]
+ }));
+ }
+ }
+ store.addListener('add', function (store, records, index, eOpts) {
+ var i;
+ for (i = 0; i < records.length; i++) {
+ this.getView().record.get('attributes')[records[i].get('name')] = records[i].get('value');
+ }
+ this.getView().record.dirty = true;
+ }, this);
+ store.addListener('update', function (store, record, operation, modifiedFieldNames, details, eOpts) {
+ if (operation === Ext.data.Model.EDIT) {
+ if (record.modified.name !== record.get('name')) {
+ delete this.getView().record.get('attributes')[record.modified.name];
+ }
+ this.getView().record.get('attributes')[record.get('name')] = record.get('value');
+ this.getView().record.dirty = true;
+ }
+ }, this);
+ store.addListener('remove', function (store, records, index, isMove, eOpts) {
+ var i;
+ for (i = 0; i < records.length; i++) {
+ delete this.getView().record.get('attributes')[records[i].get('name')];
+ }
+ this.getView().record.dirty = true;
+ }, this);
+
+ this.getView().setStore(store);
+ },
+
+ onAddClick: function () {
+ var attribute, dialog;
+ attribute = Ext.create('Traccar.model.Attribute');
+ attribute.store = this.getView().getStore();
+ dialog = Ext.create('Traccar.view.AttributeDialog');
+ dialog.down('form').loadRecord(attribute);
+ dialog.show();
+ },
+
+ onEditClick: function () {
+ var attribute, dialog;
+ attribute = this.getView().getSelectionModel().getSelection()[0];
+ dialog = Ext.create('Traccar.view.AttributeDialog');
+ dialog.down('form').loadRecord(attribute);
+ dialog.show();
+ },
+
+ onRemoveClick: function () {
+ var attribute = this.getView().getSelectionModel().getSelection()[0];
+ Ext.Msg.show({
+ title: Strings.stateName,
+ message: Strings.sharedRemoveConfirm,
+ buttons: Ext.Msg.YESNO,
+ buttonText: {
+ yes: Strings.sharedRemove,
+ no: Strings.sharedCancel
+ },
+ scope: this,
+ fn: function (btn) {
+ var store = this.getView().getStore();
+ if (btn === 'yes') {
+ store.remove(attribute);
+ store.sync();
+ }
+ }
+ });
+ },
+
+ onSelectionChange: function (selected) {
+ var disabled = selected.length > 0;
+ this.lookupReference('toolbarEditButton').setDisabled(disabled);
+ this.lookupReference('toolbarRemoveButton').setDisabled(disabled);
+ }
+});
diff --git a/web/app/view/BaseDialog.js b/web/app/view/BaseDialog.js
new file mode 100644
index 00000000..fb09f12d
--- /dev/null
+++ b/web/app/view/BaseDialog.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.BaseDialog', {
+ extend: 'Ext.window.Window',
+
+ bodyPadding: Traccar.Style.panelPadding,
+ resizable: false,
+ modal: true
+});
diff --git a/web/app/view/BaseEditDialog.js b/web/app/view/BaseEditDialog.js
new file mode 100644
index 00000000..1af095c9
--- /dev/null
+++ b/web/app/view/BaseEditDialog.js
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.BaseEditDialog', {
+ extend: 'Traccar.view.BaseDialog',
+
+ buttons: [{
+ text: Strings.sharedAttributes,
+ handler: 'showAttributesView'
+ }, {
+ xtype: 'tbfill'
+ }, {
+ text: Strings.sharedSave,
+ handler: 'onSaveClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/BaseEditDialogController.js b/web/app/view/BaseEditDialogController.js
new file mode 100644
index 00000000..79fd8f2b
--- /dev/null
+++ b/web/app/view/BaseEditDialogController.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.BaseEditDialogController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.baseEditDialog',
+
+ requires: [
+ 'Traccar.view.Attributes'
+ ],
+
+ onSaveClick: function (button) {
+ var dialog, store, record;
+ dialog = button.up('window').down('form');
+ dialog.updateRecord();
+ record = dialog.getRecord();
+ store = record.store;
+ if (store) {
+ if (record.phantom) {
+ store.add(record);
+ }
+ store.sync({
+ failure: function (batch) {
+ store.rejectChanges();
+ Traccar.app.showError(batch.exceptions[0].getError().response);
+ }
+ });
+ } else {
+ record.save();
+ }
+ button.up('window').close();
+ },
+
+ showAttributesView: function (button) {
+ var dialog, record;
+ dialog = button.up('window').down('form');
+ record = dialog.getRecord();
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedAttributes,
+ modal: false,
+ items: {
+ xtype: 'attributesView',
+ record: record
+ }
+ }).show();
+ }
+});
diff --git a/web/app/view/BaseMap.js b/web/app/view/BaseMap.js
new file mode 100644
index 00000000..62b2c573
--- /dev/null
+++ b/web/app/view/BaseMap.js
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.BaseMap', {
+ extend: 'Ext.form.Panel',
+ xtype: 'baseMapView',
+
+ layout: 'fit',
+
+ getMap: function () {
+ return this.map;
+ },
+
+ getMapView: function () {
+ return this.mapView;
+ },
+
+ initMap: function () {
+ var user, server, layer, type, bingKey, lat, lon, zoom, target;
+
+ user = Traccar.app.getUser();
+ server = Traccar.app.getServer();
+
+ type = user.get('map') || server.get('map');
+ bingKey = server.get('bingKey');
+
+ if (type === 'custom') {
+ layer = new ol.layer.Tile({
+ source: new ol.source.XYZ({
+ url: server.get('mapUrl'),
+ attributions: [new ol.Attribution({
+ html: ''
+ })]
+ })
+ });
+ } else if (type === 'bingRoad') {
+ layer = new ol.layer.Tile({
+ source: new ol.source.BingMaps({
+ key: bingKey,
+ imagerySet: 'Road'
+ })
+ });
+ } else if (type === 'bingAerial') {
+ layer = new ol.layer.Tile({
+ source: new ol.source.BingMaps({
+ key: bingKey,
+ imagerySet: 'Aerial'
+ })
+ });
+ } else {
+ layer = new ol.layer.Tile({
+ source: new ol.source.OSM({})
+ });
+ }
+
+ lat = user.get('latitude') || server.get('latitude') || Traccar.Style.mapDefaultLat;
+ lon = user.get('longitude') || server.get('longitude') || Traccar.Style.mapDefaultLon;
+ zoom = user.get('zoom') || server.get('zoom') || Traccar.Style.mapDefaultZoom;
+
+ this.mapView = new ol.View({
+ center: ol.proj.fromLonLat([lon, lat]),
+ zoom: zoom,
+ maxZoom: Traccar.Style.mapMaxZoom
+ });
+
+ this.map = new ol.Map({
+ target: this.body.dom.id,
+ layers: [layer],
+ view: this.mapView
+ });
+
+ target = this.map.getTarget();
+ if (typeof target === 'string') {
+ target = Ext.get(target).dom;
+ }
+
+ this.map.on('pointermove', function (e) {
+ var hit = this.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
+ return true;
+ });
+ if (hit) {
+ target.style.cursor = 'pointer';
+ } else {
+ target.style.cursor = '';
+ }
+ });
+
+ this.map.on('click', function (e) {
+ this.map.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
+ this.fireEvent('selectfeature', feature);
+ }, this);
+ }, this);
+ },
+
+ listeners: {
+ afterrender: function () {
+ this.initMap();
+ },
+
+ resize: function () {
+ this.map.updateSize();
+ }
+ }
+});
diff --git a/web/app/view/BasePermissionsController.js b/web/app/view/BasePermissionsController.js
new file mode 100644
index 00000000..ea0efa90
--- /dev/null
+++ b/web/app/view/BasePermissionsController.js
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.BasePermissionsController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.basePermissionsController',
+
+ init: function () {
+ var params = {}, linkStoreName, storeName;
+ params[this.getView().baseObjectName] = this.getView().baseObject;
+ linkStoreName = this.getView().linkStoreName;
+ storeName = this.getView().storeName;
+ linkStoreName = (typeof linkStoreName === 'undefined') ? storeName : linkStoreName;
+ this.getView().setStore(Ext.getStore(storeName));
+ this.getView().getStore().load({
+ scope: this,
+ callback: function (records, operation, success) {
+ var linkStore = Ext.create('Traccar.store.' + linkStoreName);
+ linkStore.load({
+ params: params,
+ scope: this,
+ callback: function (records, operation, success) {
+ var i, index;
+ if (success) {
+ for (i = 0; i < records.length; i++) {
+ index = this.getView().getStore().getById(records[i].getId());
+ this.getView().getSelectionModel().select(index, true, true);
+ }
+ }
+ }
+ });
+ }
+ });
+ },
+
+ onBeforeSelect: function (object, record, index) {
+ var data = {};
+ data[this.getView().baseObjectName] = this.getView().baseObject;
+ data[this.getView().linkObjectName] = record.getId();
+ Ext.Ajax.request({
+ scope: this,
+ url: this.getView().urlApi,
+ jsonData: Ext.util.JSON.encode(data),
+ callback: function (options, success, response) {
+ if (!success) {
+ Traccar.app.showError(response);
+ }
+ }
+ });
+ },
+
+ onBeforeDeselect: function (object, record, index) {
+ var data = {};
+ data[this.getView().baseObjectName] = this.getView().baseObject;
+ data[this.getView().linkObjectName] = record.getId();
+ Ext.Ajax.request({
+ scope: this,
+ method: 'DELETE',
+ url: this.getView().urlApi,
+ jsonData: Ext.util.JSON.encode(data),
+ callback: function (options, success, response) {
+ if (!success) {
+ Traccar.app.showError(response);
+ }
+ }
+ });
+ }
+});
diff --git a/web/app/view/BaseWindow.js b/web/app/view/BaseWindow.js
new file mode 100644
index 00000000..b6c777d1
--- /dev/null
+++ b/web/app/view/BaseWindow.js
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.BaseWindow', {
+ extend: 'Ext.window.Window',
+
+ width: Traccar.Style.windowWidth,
+ height: Traccar.Style.windowHeight,
+ layout: 'fit',
+ modal: true
+});
diff --git a/web/app/view/CommandDialog.js b/web/app/view/CommandDialog.js
new file mode 100644
index 00000000..a374ab0e
--- /dev/null
+++ b/web/app/view/CommandDialog.js
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.CommandDialog', {
+ extend: 'Traccar.view.BaseDialog',
+
+ requires: [
+ 'Traccar.view.CommandDialogController'
+ ],
+
+ controller: 'commandDialog',
+ title: Strings.commandTitle,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'combobox',
+ name: 'type',
+ fieldLabel: Strings.sharedType,
+ store: 'CommandTypes',
+ displayField: 'name',
+ valueField: 'type',
+ listeners: {
+ select: 'onSelect'
+ }
+ }, {
+ xtype: 'fieldcontainer',
+ reference: 'paramPositionPeriodic',
+ name: 'attributes',
+ hidden: true,
+
+ items: [{
+ xtype: 'numberfield',
+ fieldLabel: Strings.commandFrequency,
+ name: 'frequency'
+ }, {
+ xtype: 'combobox',
+ fieldLabel: Strings.commandUnit,
+ name: 'unit',
+ store: 'TimeUnits',
+ displayField: 'name',
+ valueField: 'factor'
+ }]
+ }, {
+ xtype: 'fieldcontainer',
+ reference: 'paramOutputControl',
+ name: 'attributes',
+ hidden: true,
+
+ items: [{
+ xtype: 'numberfield',
+ fieldLabel: Strings.commandIndex,
+ name: 'index',
+ allowBlank: false
+ }, {
+ xtype: 'textfield',
+ fieldLabel: Strings.commandData,
+ name: 'data'
+ }]
+ }, {
+ xtype: 'fieldcontainer',
+ reference: 'paramSendSmsUssd',
+ name: 'attributes',
+ hidden: true,
+
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: Strings.commandPhone,
+ name: 'phone'
+ }, {
+ xtype: 'textfield',
+ reference: 'paramSmsMessage',
+ fieldLabel: Strings.commandMessage,
+ name: 'message',
+ hidden: true
+ }]
+ }, {
+ xtype: 'textfield',
+ reference: 'paramCustom',
+ fieldLabel: Strings.commandCustom,
+ name: 'customCommand',
+ hidden: true,
+ allowBlank: false
+ }]
+ },
+
+ buttons: [{
+ text: Strings.commandSend,
+ handler: 'onSendClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/CommandDialogController.js b/web/app/view/CommandDialogController.js
new file mode 100644
index 00000000..40200657
--- /dev/null
+++ b/web/app/view/CommandDialogController.js
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.CommandDialogController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.commandDialog',
+
+ onSelect: function (selected) {
+ this.lookupReference('paramPositionPeriodic').setHidden(
+ selected.getValue() !== 'positionPeriodic');
+ this.lookupReference('paramOutputControl').setHidden(
+ selected.getValue() !== 'outputControl');
+ this.lookupReference('paramSendSmsUssd').setHidden(
+ selected.getValue() !== 'sendSms' && selected.getValue() !== 'sendUssd');
+ this.lookupReference('paramSmsMessage').setHidden(
+ selected.getValue() !== 'sendSms');
+ this.lookupReference('paramCustom').setHidden(
+ selected.getValue() !== 'custom');
+ },
+
+ onSendClick: function (button) {
+ var attributes, value, record, form, index, phone;
+
+ form = button.up('window').down('form');
+ form.updateRecord();
+ record = form.getRecord();
+
+ if (record.get('type') === 'positionPeriodic') {
+ attributes = this.lookupReference('paramPositionPeriodic');
+ value = attributes.down('numberfield[name="frequency"]').getValue();
+ value *= attributes.down('combobox[name="unit"]').getValue();
+
+ record.set('attributes', {
+ frequency: value
+ });
+ }
+
+ if (record.get('type') === 'outputControl') {
+ attributes = this.lookupReference('paramOutputControl');
+ index = attributes.down('numberfield[name="index"]').getValue();
+ value = attributes.down('textfield[name="data"]').getValue();
+
+ record.set('attributes', {
+ index: index,
+ data: value
+ });
+ }
+
+ if (record.get('type') === 'sendUssd') {
+ attributes = this.lookupReference('paramSendSmsUssd');
+ phone = attributes.down('textfield[name="phone"]').getValue();
+ record.set('attributes', {
+ phone: phone
+ });
+ }
+
+ if (record.get('type') === 'sendSms') {
+ attributes = this.lookupReference('paramSendSmsUssd');
+ phone = attributes.down('textfield[name="phone"]').getValue();
+ value = attributes.down('textfield[name="message"]').getValue();
+ record.set('attributes', {
+ phone: phone,
+ message: value
+ });
+ }
+
+ if (record.get('type') === 'custom') {
+ value = this.lookupReference('paramCustom').getValue();
+ record.set('attributes', {
+ data: value
+ });
+ }
+
+ Ext.Ajax.request({
+ scope: this,
+ url: 'api/commands',
+ jsonData: record.getData(),
+ callback: this.onSendResult
+ });
+ },
+
+ onSendResult: function (options, success, response) {
+ if (success) {
+ Ext.toast(Strings.commandSent);
+ this.closeView();
+ } else {
+ Traccar.app.showError(response);
+ }
+ }
+});
diff --git a/web/app/view/CustomTimeField.js b/web/app/view/CustomTimeField.js
new file mode 100644
index 00000000..1bd8c730
--- /dev/null
+++ b/web/app/view/CustomTimeField.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.CustomTimeField', {
+ extend: 'Ext.form.field.Time',
+ xtype: 'customTimeField',
+
+ constructor: function (config) {
+ if (Traccar.app.getPreference('twelveHourFormat', false)) {
+ config.format = Traccar.Style.timeFormat12;
+ } else {
+ config.format = Traccar.Style.timeFormat24;
+ }
+ this.callParent(arguments);
+ }
+});
diff --git a/web/app/view/DeviceDialog.js b/web/app/view/DeviceDialog.js
new file mode 100644
index 00000000..e88618fc
--- /dev/null
+++ b/web/app/view/DeviceDialog.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.DeviceDialog', {
+ extend: 'Traccar.view.BaseEditDialog',
+
+ requires: [
+ 'Traccar.view.BaseEditDialog'
+ ],
+
+ controller: 'baseEditDialog',
+ title: Strings.deviceDialog,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: Strings.sharedName,
+ allowBlank: false
+ }, {
+ xtype: 'textfield',
+ name: 'uniqueId',
+ fieldLabel: Strings.deviceIdentifier,
+ allowBlank: false
+ }, {
+ xtype: 'combobox',
+ name: 'groupId',
+ fieldLabel: Strings.groupParent,
+ store: 'Groups',
+ queryMode: 'local',
+ displayField: 'name',
+ valueField: 'id'
+ }]
+ }
+});
diff --git a/web/app/view/DeviceGeofences.js b/web/app/view/DeviceGeofences.js
new file mode 100644
index 00000000..9e2c12a7
--- /dev/null
+++ b/web/app/view/DeviceGeofences.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.DeviceGeofences', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'deviceGeofencesView',
+
+ requires: [
+ 'Traccar.view.BasePermissionsController'
+ ],
+
+ controller: 'basePermissionsController',
+
+ selModel: {
+ selType: 'checkboxmodel',
+ checkOnly: true,
+ showHeaderCheckbox: false
+ },
+
+ listeners: {
+ beforedeselect: 'onBeforeDeselect',
+ beforeselect: 'onBeforeSelect'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/Devices.js b/web/app/view/Devices.js
new file mode 100644
index 00000000..ab6436ea
--- /dev/null
+++ b/web/app/view/Devices.js
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Devices', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'devicesView',
+
+ requires: [
+ 'Traccar.view.DevicesController',
+ 'Traccar.view.EditToolbar',
+ 'Traccar.view.SettingsMenu'
+ ],
+
+ controller: 'devices',
+ rootVisible: false,
+
+ initComponent: function () {
+ this.store = Ext.create('Ext.data.ChainedStore', {
+ source: 'Devices',
+ groupField: 'groupId'
+ });
+ this.callParent();
+ },
+
+ title: Strings.deviceTitle,
+ selType: 'rowmodel',
+
+ tbar: {
+ xtype: 'editToolbar',
+ items: [{
+ xtype: 'button',
+ disabled: true,
+ handler: 'onGeofencesClick',
+ reference: 'toolbarGeofencesButton',
+ glyph: 'xf21d@FontAwesome',
+ tooltip: Strings.sharedGeofences,
+ tooltipType: 'title'
+ }, {
+ disabled: true,
+ handler: 'onCommandClick',
+ reference: 'deviceCommandButton',
+ glyph: 'xf093@FontAwesome',
+ tooltip: Strings.deviceCommand,
+ tooltipType: 'title'
+ }, {
+ xtype: 'tbfill'
+ }, {
+ id: 'muteButton',
+ glyph: 'xf1f7@FontAwesome',
+ tooltip: Strings.sharedMute,
+ tooltipType: 'title',
+ pressed : true,
+ enableToggle: true,
+ listeners: {
+ toggle: function (button, pressed) {
+ if (pressed) {
+ button.setGlyph('xf1f7@FontAwesome');
+ } else {
+ button.setGlyph('xf0a2@FontAwesome');
+ }
+ },
+ scope: this
+ }
+ }, {
+ id: 'deviceFollowButton',
+ glyph: 'xf05b@FontAwesome',
+ tooltip: Strings.deviceFollow,
+ tooltipType: 'title',
+ enableToggle: true,
+ toggleHandler: 'onFollowClick'
+ }, {
+ xtype: 'settingsMenu'
+ }]
+ },
+
+ bbar: [{
+ xtype: 'tbtext',
+ html: Strings.groupParent
+ }, {
+ xtype: 'combobox',
+ store: 'Groups',
+ queryMode: 'local',
+ displayField: 'name',
+ valueField: 'id',
+ flex: 1,
+ listeners: {
+ change: function () {
+ if (Ext.isNumber(this.getValue())) {
+ this.up('grid').store.filter({
+ id: 'groupFilter',
+ filterFn: function (item) {
+ var groupId, group, groupStore, filter = true;
+ groupId = item.get('groupId');
+ groupStore = Ext.getStore('Groups');
+
+ while (groupId) {
+ group = groupStore.getById(groupId);
+ if (group) {
+ if (group.get('id') === this.getValue()) {
+ filter = false;
+ break;
+ }
+ groupId = group.get('groupId');
+ } else {
+ groupId = 0;
+ }
+ }
+
+ return !filter;
+ },
+ scope: this
+ });
+ } else {
+ this.up('grid').store.removeFilter('groupFilter');
+ }
+ }
+ }
+ }, {
+ xtype: 'tbtext',
+ html: Strings.sharedSearch
+ }, {
+ xtype: 'textfield',
+ flex: 1,
+ listeners: {
+ change: function () {
+ this.up('grid').store.filter('name', this.getValue());
+ }
+ }
+ }],
+
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }, {
+ text: Strings.deviceLastUpdate,
+ dataIndex: 'lastUpdate',
+ flex: 1,
+ renderer: function (value, metaData, record) {
+ switch (record.get('status')) {
+ case 'online':
+ metaData.tdCls = 'view-color-green';
+ break;
+ case 'offline':
+ metaData.tdCls = 'view-color-red';
+ break;
+ default:
+ metaData.tdCls = 'view-color-yellow';
+ break;
+ }
+ if (Traccar.app.getPreference('twelveHourFormat', false)) {
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat12);
+ } else {
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat24);
+ }
+ }
+ }]
+
+});
diff --git a/web/app/view/DevicesController.js b/web/app/view/DevicesController.js
new file mode 100644
index 00000000..68dd1602
--- /dev/null
+++ b/web/app/view/DevicesController.js
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.DevicesController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.devices',
+
+ requires: [
+ 'Traccar.view.CommandDialog',
+ 'Traccar.view.DeviceDialog',
+ 'Traccar.view.DeviceGeofences',
+ 'Traccar.view.BaseWindow',
+ 'Traccar.model.Device',
+ 'Traccar.model.Command'
+ ],
+
+ config: {
+ listen: {
+ controller: {
+ '*': {
+ selectdevice: 'selectDevice',
+ selectreport: 'selectReport'
+ }
+ },
+ store: {
+ '#Devices': {
+ update: 'onUpdateDevice'
+ }
+ }
+ }
+ },
+
+ init: function () {
+ var readonly = Traccar.app.getServer().get('readonly') && !Traccar.app.getUser().get('admin');
+ this.lookupReference('toolbarAddButton').setVisible(!readonly);
+ this.lookupReference('toolbarEditButton').setVisible(!readonly);
+ this.lookupReference('toolbarRemoveButton').setVisible(!readonly);
+ this.lookupReference('toolbarGeofencesButton').setVisible(!readonly);
+ },
+
+ onAddClick: function () {
+ var device, dialog;
+ device = Ext.create('Traccar.model.Device');
+ device.store = Ext.getStore('Devices');
+ dialog = Ext.create('Traccar.view.DeviceDialog');
+ dialog.down('form').loadRecord(device);
+ dialog.show();
+ },
+
+ onEditClick: function () {
+ var device, dialog;
+ device = this.getView().getSelectionModel().getSelection()[0];
+ dialog = Ext.create('Traccar.view.DeviceDialog');
+ dialog.down('form').loadRecord(device);
+ dialog.show();
+ },
+
+ onRemoveClick: function () {
+ var device = this.getView().getSelectionModel().getSelection()[0];
+ Ext.Msg.show({
+ title: Strings.deviceDialog,
+ message: Strings.sharedRemoveConfirm,
+ buttons: Ext.Msg.YESNO,
+ buttonText: {
+ yes: Strings.sharedRemove,
+ no: Strings.sharedCancel
+ },
+ fn: function (btn) {
+ var store;
+ if (btn === 'yes') {
+ store = Ext.getStore('Devices');
+ store.remove(device);
+ store.sync();
+ }
+ }
+ });
+ },
+
+ onGeofencesClick: function () {
+ var device = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedGeofences,
+ items: {
+ xtype: 'deviceGeofencesView',
+ baseObjectName: 'deviceId',
+ linkObjectName: 'geofenceId',
+ storeName: 'Geofences',
+ urlApi: 'api/devices/geofences',
+ baseObject: device.getId()
+ }
+ }).show();
+ },
+
+ onCommandClick: function () {
+ var device, deviceId, command, dialog, comboStore;
+ device = this.getView().getSelectionModel().getSelection()[0];
+ deviceId = device.get('id');
+ command = Ext.create('Traccar.model.Command');
+ command.set('deviceId', deviceId);
+ dialog = Ext.create('Traccar.view.CommandDialog');
+ comboStore = dialog.down('form').down('combobox').getStore();
+ comboStore.getProxy().setExtraParam('deviceId', deviceId);
+ dialog.down('form').loadRecord(command);
+ dialog.show();
+ },
+
+ onFollowClick: function (button, pressed) {
+ var device;
+ if (pressed) {
+ device = this.getView().getSelectionModel().getSelection()[0];
+ this.fireEvent('selectdevice', device, true);
+ }
+ },
+
+ updateButtons: function (selected) {
+ var empty = selected.getCount() === 0;
+ this.lookupReference('toolbarEditButton').setDisabled(empty);
+ this.lookupReference('toolbarRemoveButton').setDisabled(empty);
+ this.lookupReference('toolbarGeofencesButton').setDisabled(empty);
+ this.lookupReference('deviceCommandButton').setDisabled(empty || (selected.getLastSelected().get('status') !== 'online'));
+ },
+
+ onSelectionChange: function (selected) {
+ this.updateButtons(selected);
+ if (selected.getCount() > 0) {
+ this.fireEvent('selectdevice', selected.getLastSelected(), true);
+ }
+ },
+
+ selectDevice: function (device, center) {
+ this.getView().getSelectionModel().select([device], false, true);
+ },
+
+ selectReport: function (position) {
+ if (position !== undefined) {
+ this.getView().getSelectionModel().deselectAll();
+ }
+ },
+
+ onUpdateDevice: function (store, data) {
+ this.updateButtons(this.getView().getSelectionModel());
+ }
+});
diff --git a/web/app/view/EditToolbar.js b/web/app/view/EditToolbar.js
new file mode 100644
index 00000000..523d27e4
--- /dev/null
+++ b/web/app/view/EditToolbar.js
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.EditToolbar', {
+ extend: 'Ext.toolbar.Toolbar',
+ xtype: 'editToolbar',
+
+ initComponent: function () {
+ this.callParent(arguments);
+ this.add(0, [{
+ xtype: 'button',
+ handler: 'onAddClick',
+ reference: 'toolbarAddButton',
+ glyph: 'xf067@FontAwesome',
+ tooltip: Strings.sharedAdd,
+ tooltipType: 'title'
+ }, {
+ xtype: 'button',
+ disabled: true,
+ handler: 'onEditClick',
+ reference: 'toolbarEditButton',
+ glyph: 'xf040@FontAwesome',
+ tooltip: Strings.sharedEdit,
+ tooltipType: 'title'
+ }, {
+ xtype: 'button',
+ disabled: true,
+ handler: 'onRemoveClick',
+ reference: 'toolbarRemoveButton',
+ glyph: 'xf00d@FontAwesome',
+ tooltip: Strings.sharedRemove,
+ tooltipType: 'title'
+ }]);
+ }
+});
diff --git a/web/app/view/GeofenceDialog.js b/web/app/view/GeofenceDialog.js
new file mode 100644
index 00000000..febef235
--- /dev/null
+++ b/web/app/view/GeofenceDialog.js
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GeofenceDialog', {
+ extend: 'Traccar.view.BaseDialog',
+
+ requires: [
+ 'Traccar.view.GeofenceDialogController'
+ ],
+
+ controller: 'geofenceDialog',
+ title: Strings.sharedGeofence,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: Strings.sharedName
+ }, {
+ xtype: 'textfield',
+ name: 'description',
+ fieldLabel: Strings.sharedDescription
+ }, {
+ xtype: 'hiddenfield',
+ name: 'area',
+ allowBlank: false,
+ reference: 'areaField'
+ }]
+ },
+
+ buttons: [{
+ text: Strings.sharedArea,
+ glyph: 'xf21d@FontAwesome',
+ handler: 'onAreaClick'
+ }, {
+ xtype: 'tbfill'
+ }, {
+ text: Strings.sharedSave,
+ handler: 'onSaveClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/GeofenceDialogController.js b/web/app/view/GeofenceDialogController.js
new file mode 100644
index 00000000..5638db36
--- /dev/null
+++ b/web/app/view/GeofenceDialogController.js
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GeofenceDialogController', {
+ extend: 'Traccar.view.BaseEditDialogController',
+ alias: 'controller.geofenceDialog',
+
+ requires: [
+ 'Traccar.view.GeofenceMap'
+ ],
+
+ config: {
+ listen: {
+ controller: {
+ '*': {
+ savearea: 'saveArea'
+ }
+ }
+ }
+ },
+
+ saveArea: function (value) {
+ this.lookupReference('areaField').setValue(value);
+ },
+
+ onAreaClick: function (button) {
+ var dialog, record;
+ dialog = button.up('window').down('form');
+ record = dialog.getRecord();
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedArea,
+ items: {
+ xtype: 'geofenceMapView',
+ area: record.get('area')
+ }
+ }).show();
+ }
+});
diff --git a/web/app/view/GeofenceMap.js b/web/app/view/GeofenceMap.js
new file mode 100644
index 00000000..933df236
--- /dev/null
+++ b/web/app/view/GeofenceMap.js
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GeofenceMap', {
+ extend: 'Traccar.view.BaseMap',
+ xtype: 'geofenceMapView',
+
+ requires: [
+ 'Traccar.view.GeofenceMapController',
+ 'Traccar.GeofenceConverter'
+ ],
+
+ controller: 'geofenceMap',
+ bodyBorder: true,
+
+ tbar: {
+ items: [{
+ xtype: 'combobox',
+ store: 'GeofenceTypes',
+ valueField: 'key',
+ displayField: 'name',
+ listeners: {
+ select: 'onTypeSelect'
+ }
+ }, {
+ xtype: 'tbfill'
+ }, {
+ text: Strings.sharedSave,
+ handler: 'onSaveClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'onCancelClick'
+ }]
+ },
+
+ getFeatures: function () {
+ return this.features;
+ },
+
+ initMap: function () {
+ var map, featureOverlay, geometry;
+ this.callParent();
+
+ map = this.map;
+
+ this.features = new ol.Collection();
+ if (this.area !== '') {
+ geometry = Traccar.GeofenceConverter.wktToGeometry(this.mapView, this.area);
+ this.features.push(new ol.Feature(geometry));
+ if (geometry instanceof ol.geom.Circle) {
+ this.mapView.setCenter(geometry.getCenter());
+ } else if (geometry instanceof ol.geom.Polygon) {
+ this.mapView.setCenter(geometry.getCoordinates()[0][0]);
+ }
+ }
+ featureOverlay = new ol.layer.Vector({
+ source: new ol.source.Vector({
+ features: this.features
+ }),
+ style: new ol.style.Style({
+ fill: new ol.style.Fill({
+ color: Traccar.Style.mapGeofenceOverlay
+ }),
+ stroke: new ol.style.Stroke({
+ color: Traccar.Style.mapGeofenceColor,
+ width: Traccar.Style.mapGeofenceWidth
+ }),
+ image: new ol.style.Circle({
+ radius: Traccar.Style.mapGeofenceRadius,
+ fill: new ol.style.Fill({
+ color: Traccar.Style.mapGeofenceColor
+ })
+ })
+ })
+ });
+ featureOverlay.setMap(map);
+
+ map.addInteraction(new ol.interaction.Modify({
+ features: this.features,
+ deleteCondition: function (event) {
+ return ol.events.condition.shiftKeyOnly(event) && ol.events.condition.singleClick(event);
+ }
+ }));
+ },
+
+ addInteraction: function (type) {
+ this.draw = new ol.interaction.Draw({
+ features: this.features,
+ type: type
+ });
+ this.draw.on('drawstart', function () {
+ this.features.clear();
+ }, this);
+ this.map.addInteraction(this.draw);
+ },
+
+ removeInteraction: function () {
+ if (this.draw) {
+ this.map.removeInteraction(this.draw);
+ this.draw = null;
+ }
+ }
+});
diff --git a/web/app/view/GeofenceMapController.js b/web/app/view/GeofenceMapController.js
new file mode 100644
index 00000000..c508127d
--- /dev/null
+++ b/web/app/view/GeofenceMapController.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GeofenceMapController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.geofenceMap',
+
+ requires: [
+ 'Traccar.GeofenceConverter'
+ ],
+
+ onSaveClick: function (button) {
+ var geometry, projection;
+ if (this.getView().getFeatures().getLength() > 0) {
+ geometry = this.getView().getFeatures().pop().getGeometry();
+ projection = this.getView().getMapView().getProjection();
+ this.fireEvent('savearea', Traccar.GeofenceConverter.geometryToWkt(projection, geometry));
+ button.up('window').close();
+ }
+ },
+
+ onCancelClick: function (button) {
+ button.up('window').close();
+ },
+
+ onTypeSelect: function (combo) {
+ this.getView().removeInteraction();
+ this.getView().addInteraction(combo.getValue());
+ }
+});
diff --git a/web/app/view/Geofences.js b/web/app/view/Geofences.js
new file mode 100644
index 00000000..4a5fc9e3
--- /dev/null
+++ b/web/app/view/Geofences.js
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Geofences', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'geofencesView',
+
+ requires: [
+ 'Traccar.view.GeofencesController',
+ 'Traccar.view.EditToolbar'
+ ],
+
+ controller: 'geofences',
+ store: 'Geofences',
+
+ selType: 'rowmodel',
+
+ tbar: {
+ xtype: 'editToolbar'
+ },
+
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }, {
+ text: Strings.sharedDescription,
+ dataIndex: 'description',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/GeofencesController.js b/web/app/view/GeofencesController.js
new file mode 100644
index 00000000..5faee139
--- /dev/null
+++ b/web/app/view/GeofencesController.js
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GeofencesController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.geofences',
+
+ requires: [
+ 'Traccar.view.GeofenceDialog',
+ 'Traccar.model.Geofence'
+ ],
+
+ init: function () {
+ Ext.getStore('Geofences').load();
+ },
+
+ onAddClick: function () {
+ var geofence, dialog;
+ geofence = Ext.create('Traccar.model.Geofence');
+ geofence.store = this.getView().getStore();
+ dialog = Ext.create('Traccar.view.GeofenceDialog');
+ dialog.down('form').loadRecord(geofence);
+ dialog.show();
+ },
+
+ onEditClick: function () {
+ var geofence, dialog;
+ geofence = this.getView().getSelectionModel().getSelection()[0];
+ dialog = Ext.create('Traccar.view.GeofenceDialog');
+ dialog.down('form').loadRecord(geofence);
+ dialog.show();
+ },
+
+ onRemoveClick: function () {
+ var geofence = this.getView().getSelectionModel().getSelection()[0];
+ Ext.Msg.show({
+ title: Strings.sharedGeofence,
+ message: Strings.sharedRemoveConfirm,
+ buttons: Ext.Msg.YESNO,
+ buttonText: {
+ yes: Strings.sharedRemove,
+ no: Strings.sharedCancel
+ },
+ fn: function (btn) {
+ var store = Ext.getStore('Geofences');
+ if (btn === 'yes') {
+ store.remove(geofence);
+ store.sync();
+ }
+ }
+ });
+ },
+
+ onSelectionChange: function (selected) {
+ var disabled = selected.length > 0;
+ this.lookupReference('toolbarEditButton').setDisabled(disabled);
+ this.lookupReference('toolbarRemoveButton').setDisabled(disabled);
+ }
+});
diff --git a/web/app/view/GroupDialog.js b/web/app/view/GroupDialog.js
new file mode 100644
index 00000000..032397a2
--- /dev/null
+++ b/web/app/view/GroupDialog.js
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GroupDialog', {
+ extend: 'Traccar.view.BaseEditDialog',
+
+ requires: [
+ 'Traccar.view.BaseEditDialogController'
+ ],
+
+ controller: 'baseEditDialog',
+ title: Strings.groupDialog,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: Strings.sharedName,
+ allowBlank: false
+ }, {
+ xtype: 'combobox',
+ name: 'groupId',
+ fieldLabel: Strings.groupParent,
+ store: 'Groups',
+ queryMode: 'local',
+ displayField: 'name',
+ valueField: 'id'
+ }]
+ }
+});
diff --git a/web/app/view/GroupGeofences.js b/web/app/view/GroupGeofences.js
new file mode 100644
index 00000000..8ef2984e
--- /dev/null
+++ b/web/app/view/GroupGeofences.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GroupGeofences', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'groupGeofencesView',
+
+ requires: [
+ 'Traccar.view.BasePermissionsController'
+ ],
+
+ controller: 'basePermissionsController',
+
+ selModel: {
+ selType: 'checkboxmodel',
+ checkOnly: true,
+ showHeaderCheckbox: false
+ },
+
+ listeners: {
+ beforedeselect: 'onBeforeDeselect',
+ beforeselect: 'onBeforeSelect'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/Groups.js b/web/app/view/Groups.js
new file mode 100644
index 00000000..59d20df3
--- /dev/null
+++ b/web/app/view/Groups.js
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Groups', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'groupsView',
+
+ requires: [
+ 'Traccar.view.GroupsController',
+ 'Traccar.view.EditToolbar'
+ ],
+
+ controller: 'groups',
+ store: 'Groups',
+
+ selType: 'rowmodel',
+
+ tbar: {
+ xtype: 'editToolbar',
+ items: [{
+ xtype: 'button',
+ disabled: true,
+ handler: 'onGeofencesClick',
+ reference: 'toolbarGeofencesButton',
+ glyph: 'xf21d@FontAwesome',
+ tooltip: Strings.sharedGeofences,
+ tooltipType: 'title'
+ }]
+ },
+
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/GroupsController.js b/web/app/view/GroupsController.js
new file mode 100644
index 00000000..06057fda
--- /dev/null
+++ b/web/app/view/GroupsController.js
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.GroupsController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.groups',
+
+ requires: [
+ 'Traccar.view.GroupDialog',
+ 'Traccar.view.GroupGeofences',
+ 'Traccar.view.BaseWindow',
+ 'Traccar.model.Group'
+ ],
+
+ onAddClick: function () {
+ var group, dialog;
+ group = Ext.create('Traccar.model.Group');
+ group.store = this.getView().getStore();
+ dialog = Ext.create('Traccar.view.GroupDialog');
+ dialog.down('form').loadRecord(group);
+ dialog.show();
+ },
+
+ onEditClick: function () {
+ var group, dialog;
+ group = this.getView().getSelectionModel().getSelection()[0];
+ dialog = Ext.create('Traccar.view.GroupDialog');
+ dialog.down('form').loadRecord(group);
+ dialog.show();
+ },
+
+ onRemoveClick: function () {
+ var group = this.getView().getSelectionModel().getSelection()[0];
+ Ext.Msg.show({
+ title: Strings.groupDialog,
+ message: Strings.sharedRemoveConfirm,
+ buttons: Ext.Msg.YESNO,
+ buttonText: {
+ yes: Strings.sharedRemove,
+ no: Strings.sharedCancel
+ },
+ fn: function (btn) {
+ var store = Ext.getStore('Groups');
+ if (btn === 'yes') {
+ store.remove(group);
+ store.sync();
+ }
+ }
+ });
+ },
+
+ onGeofencesClick: function () {
+ var admin, group;
+ admin = Traccar.app.getUser().get('admin');
+ group = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedGeofences,
+ items: {
+ xtype: 'groupGeofencesView',
+ baseObjectName: 'groupId',
+ linkObjectName: 'geofenceId',
+ storeName: admin ? 'AllGeofences' : 'Geofences',
+ urlApi: 'api/groups/geofences',
+ baseObject: group.getId()
+ }
+ }).show();
+ },
+
+ onSelectionChange: function (selected) {
+ var disabled = selected.length > 0;
+ this.lookupReference('toolbarEditButton').setDisabled(disabled);
+ this.lookupReference('toolbarRemoveButton').setDisabled(disabled);
+ this.lookupReference('toolbarGeofencesButton').setDisabled(disabled);
+ }
+});
diff --git a/web/app/view/Login.js b/web/app/view/Login.js
new file mode 100644
index 00000000..db3c5526
--- /dev/null
+++ b/web/app/view/Login.js
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Login', {
+ extend: 'Traccar.view.BaseDialog',
+ alias: 'widget.login',
+
+ requires: [
+ 'Traccar.view.LoginController'
+ ],
+
+ controller: 'login',
+
+ title: Strings.loginTitle,
+ closable: false,
+ modal: false,
+
+ items: {
+ xtype: 'form',
+ reference: 'form',
+
+ autoEl: {
+ tag: 'form',
+ method: 'POST',
+ action: 'fake-login.html',
+ target: 'submitTarget'
+ },
+
+ items: [{
+ xtype: 'combobox',
+ name: 'language',
+ fieldLabel: Strings.loginLanguage,
+ store: 'Languages',
+ displayField: 'name',
+ valueField: 'code',
+ submitValue: false,
+ listeners: {
+ select: 'onSelectLanguage'
+ },
+ reference: 'languageField'
+ }, {
+ xtype: 'textfield',
+ name: 'email',
+ reference: 'userField',
+ fieldLabel: Strings.userEmail,
+ allowBlank: false,
+ enableKeyEvents: true,
+ listeners: {
+ specialKey: 'onSpecialKey',
+ afterrender: 'onAfterRender'
+ },
+ inputAttrTpl: ['autocomplete="on"']
+ }, {
+ xtype: 'textfield',
+ name: 'password',
+ reference: 'passwordField',
+ fieldLabel: Strings.userPassword,
+ inputType: 'password',
+ allowBlank: false,
+ enableKeyEvents: true,
+ listeners: {
+ specialKey: 'onSpecialKey'
+ },
+ inputAttrTpl: ['autocomplete="on"']
+ }, {
+ xtype: 'checkboxfield',
+ reference: 'rememberField',
+ fieldLabel: Strings.userRemember
+ }, {
+ xtype: 'component',
+ html: '<iframe id="submitTarget" name="submitTarget" style="display:none"></iframe>'
+ }, {
+ xtype: 'component',
+ html: '<input type="submit" id="submitButton" style="display:none">'
+ }]
+ },
+
+ buttons: [{
+ text: Strings.loginRegister,
+ handler: 'onRegisterClick',
+ reference: 'registerButton'
+ }, {
+ text: Strings.loginLogin,
+ handler: 'onLoginClick'
+ }]
+});
diff --git a/web/app/view/LoginController.js b/web/app/view/LoginController.js
new file mode 100644
index 00000000..698cc7f9
--- /dev/null
+++ b/web/app/view/LoginController.js
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.LoginController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.login',
+
+ requires: [
+ 'Traccar.view.Register'
+ ],
+
+ init: function () {
+ this.lookupReference('registerButton').setDisabled(
+ !Traccar.app.getServer().get('registration'));
+ this.lookupReference('languageField').setValue(Locale.language);
+ },
+
+ login: function () {
+ var form = this.lookupReference('form');
+ if (form.isValid()) {
+ Ext.getBody().mask(Strings.sharedLoading);
+ Ext.Ajax.request({
+ scope: this,
+ method: 'POST',
+ url: 'api/session',
+ params: form.getValues(),
+ callback: function (options, success, response) {
+ Ext.getBody().unmask();
+ if (success) {
+ if (this.lookupReference('rememberField').getValue()) {
+ Ext.util.Cookies.set('user', this.lookupReference('userField').getValue(), Ext.Date.add(new Date(), Ext.Date.YEAR, 1));
+ Ext.util.Cookies.set('password', this.lookupReference('passwordField').getValue(), Ext.Date.add(new Date(), Ext.Date.YEAR, 1));
+ }
+ Traccar.app.setUser(Ext.decode(response.responseText));
+ this.fireViewEvent('login');
+ } else {
+ Traccar.app.showError(Strings.loginFailed);
+ }
+ }
+ });
+ }
+ },
+
+ logout: function () {
+ Ext.util.Cookies.clear('user');
+ Ext.util.Cookies.clear('password');
+ Ext.Ajax.request({
+ scope: this,
+ method: 'DELETE',
+ url: 'api/session',
+ callback: function () {
+ window.location.reload();
+ }
+ });
+ },
+
+ onSelectLanguage: function (selected) {
+ var paramName, paramValue, url, prefix, suffix;
+ paramName = 'locale';
+ paramValue = selected.getValue();
+ url = window.location.href;
+ if (url.indexOf(paramName + '=') >= 0) {
+ prefix = url.substring(0, url.indexOf(paramName));
+ suffix = url.substring(url.indexOf(paramName));
+ suffix = suffix.substring(suffix.indexOf('=') + 1);
+ suffix = (suffix.indexOf('&') >= 0) ? suffix.substring(suffix.indexOf('&')) : '';
+ url = prefix + paramName + '=' + paramValue + suffix;
+ } else {
+ if (url.indexOf('?') < 0) {
+ url += '?' + paramName + '=' + paramValue;
+ } else {
+ url += '&' + paramName + '=' + paramValue;
+ }
+ }
+ window.location.href = url;
+ },
+
+ onAfterRender: function (field) {
+ field.focus();
+ },
+
+ onSpecialKey: function (field, e) {
+ if (e.getKey() === e.ENTER) {
+ this.login();
+ }
+ },
+
+ onLoginClick: function () {
+ Ext.getElementById('submitButton').click();
+ this.login();
+ },
+
+ onRegisterClick: function () {
+ Ext.create('Traccar.view.Register').show();
+ }
+});
diff --git a/web/app/view/Main.js b/web/app/view/Main.js
new file mode 100644
index 00000000..c15faab4
--- /dev/null
+++ b/web/app/view/Main.js
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Main', {
+ extend: 'Ext.container.Viewport',
+ alias: 'widget.main',
+
+ requires: [
+ 'Traccar.view.Devices',
+ 'Traccar.view.State',
+ 'Traccar.view.Report',
+ 'Traccar.view.Map'
+ ],
+
+ layout: 'border',
+
+ defaults: {
+ header: false,
+ collapsible: true,
+ split: true
+ },
+
+ items: [{
+ region: 'west',
+ layout: 'border',
+ width: Traccar.Style.deviceWidth,
+ title: Strings.devicesAndState,
+
+ defaults: {
+ split: true,
+ flex: 1
+ },
+
+ items: [{
+ region: 'center',
+ xtype: 'devicesView'
+ }, {
+ region: 'south',
+ xtype: 'stateView'
+ }]
+ }, {
+ region: 'south',
+ xtype: 'reportView',
+ height: Traccar.Style.reportHeight,
+ collapsed: true,
+ titleCollapse: true,
+ floatable: false
+ }, {
+ region: 'center',
+ xtype: 'mapView',
+ header: true,
+ collapsible: false
+ }]
+});
diff --git a/web/app/view/MainMobile.js b/web/app/view/MainMobile.js
new file mode 100644
index 00000000..fe264260
--- /dev/null
+++ b/web/app/view/MainMobile.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.MainMobile', {
+ extend: 'Ext.container.Viewport',
+ alias: 'widget.mainMobile',
+
+ requires: [
+ 'Traccar.view.Devices',
+ 'Traccar.view.State',
+ 'Traccar.view.Map'
+ ],
+
+ layout: 'border',
+
+ defaults: {
+ header: false,
+ collapsible: true,
+ split: true
+ },
+
+ items: [{
+ region: 'east',
+ xtype: 'stateView',
+ flex: 4,
+ collapsed: true,
+ titleCollapse: true,
+ floatable: false
+ }, {
+ region: 'center',
+ xtype: 'mapView',
+ collapsible: false,
+ flex: 2
+ }, {
+ region: 'south',
+ xtype: 'devicesView',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/Map.js b/web/app/view/Map.js
new file mode 100644
index 00000000..8ff57c06
--- /dev/null
+++ b/web/app/view/Map.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Map', {
+ extend: 'Traccar.view.BaseMap',
+ xtype: 'mapView',
+
+ requires: [
+ 'Traccar.view.MapController'
+ ],
+
+ controller: 'map',
+
+ title: Strings.mapTitle,
+
+ getLatestSource: function () {
+ return this.latestSource;
+ },
+
+ getRouteSource: function () {
+ return this.routeSource;
+ },
+
+ getReportSource: function () {
+ return this.reportSource;
+ },
+
+ initMap: function () {
+ this.callParent();
+
+ this.latestSource = new ol.source.Vector({});
+ this.map.addLayer(new ol.layer.Vector({
+ source: this.latestSource
+ }));
+
+ this.routeSource = new ol.source.Vector({});
+ this.map.addLayer(new ol.layer.Vector({
+ source: this.routeSource
+ }));
+
+ this.reportSource = new ol.source.Vector({});
+ this.map.addLayer(new ol.layer.Vector({
+ source: this.reportSource
+ }));
+ }
+});
diff --git a/web/app/view/MapController.js b/web/app/view/MapController.js
new file mode 100644
index 00000000..3b0db6b0
--- /dev/null
+++ b/web/app/view/MapController.js
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.MapController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.map',
+
+ config: {
+ listen: {
+ controller: {
+ '*': {
+ selectdevice: 'selectDevice',
+ selectreport: 'selectReport',
+ mapstaterequest: 'getMapState'
+ }
+ },
+ store: {
+ '#Devices': {
+ add: 'updateDevice',
+ update: 'updateDevice'
+ },
+ '#LatestPositions': {
+ add: 'updateLatest',
+ update: 'updateLatest'
+ },
+ '#ReportRoute': {
+ load: 'loadReport',
+ clear: 'clearReport'
+ }
+ },
+ component: {
+ '#': {
+ selectfeature: 'selectFeature'
+ }
+ }
+ }
+ },
+
+ init: function () {
+ this.latestMarkers = {};
+ this.reportMarkers = {};
+ },
+
+ getDeviceColor: function (device) {
+ switch (device.get('status')) {
+ case 'online':
+ return Traccar.Style.mapColorOnline;
+ case 'offline':
+ return Traccar.Style.mapColorOffline;
+ default:
+ return Traccar.Style.mapColorUnknown;
+ }
+ },
+
+ changeMarkerColor: function (style, color) {
+ return new ol.style.Style({
+ image: new ol.style.Arrow({
+ radius: style.getImage().getRadius(),
+ fill: new ol.style.Fill({
+ color: color
+ }),
+ stroke: style.getImage().getStroke(),
+ rotation: style.getImage().getRotation()
+ }),
+ text: style.getText()
+ });
+ },
+
+ updateDevice: function (store, data) {
+ var i, device, deviceId, marker;
+
+ if (!Ext.isArray(data)) {
+ data = [data];
+ }
+
+ for (i = 0; i < data.length; i++) {
+ device = data[i];
+ deviceId = device.get('id');
+
+ if (deviceId in this.latestMarkers) {
+ marker = this.latestMarkers[deviceId];
+ marker.setStyle(
+ this.changeMarkerColor(marker.getStyle(), this.getDeviceColor(device)));
+ }
+ }
+ },
+
+ followSelected: function () {
+ return Ext.getCmp('deviceFollowButton') && Ext.getCmp('deviceFollowButton').pressed;
+ },
+
+ updateLatest: function (store, data) {
+ var i, position, geometry, device, deviceId, marker, style;
+
+ if (!Ext.isArray(data)) {
+ data = [data];
+ }
+
+ for (i = 0; i < data.length; i++) {
+ position = data[i];
+ deviceId = position.get('deviceId');
+ device = Ext.getStore('Devices').findRecord('id', deviceId, 0, false, false, true);
+
+ if (device) {
+ geometry = new ol.geom.Point(ol.proj.fromLonLat([
+ position.get('longitude'),
+ position.get('latitude')
+ ]));
+
+ if (deviceId in this.latestMarkers) {
+ marker = this.latestMarkers[deviceId];
+ marker.setGeometry(geometry);
+ } else {
+ marker = new ol.Feature(geometry);
+ marker.set('record', device);
+ this.latestMarkers[deviceId] = marker;
+ this.getView().getLatestSource().addFeature(marker);
+
+ style = this.getLatestMarker(this.getDeviceColor(device));
+ style.getText().setText(device.get('name'));
+ marker.setStyle(style);
+ }
+
+ marker.getStyle().getImage().setRotation(position.get('course') * Math.PI / 180);
+
+ if (marker === this.selectedMarker && this.followSelected()) {
+ this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates());
+ }
+ }
+ }
+ },
+
+ loadReport: function (store, data) {
+ var i, position, point, geometry, marker, style;
+
+ this.clearReport(store);
+
+ if (data.length > 0) {
+ this.reportRoute = [];
+ for (i = 0; i < data.length; i++) {
+ if (i === 0 || data[i].get('deviceId') !== data[i - 1].get('deviceId')) {
+ this.reportRoute.push(new ol.Feature({
+ geometry: new ol.geom.LineString([])
+ }));
+ this.reportRoute[this.reportRoute.length - 1].setStyle(this.getRouteStyle(data[i].get('deviceId')));
+ this.getView().getRouteSource().addFeature(this.reportRoute[this.reportRoute.length - 1]);
+ }
+ position = data[i];
+
+ point = ol.proj.fromLonLat([
+ position.get('longitude'),
+ position.get('latitude')
+ ]);
+ geometry = new ol.geom.Point(point);
+
+ marker = new ol.Feature(geometry);
+ marker.set('record', position);
+ this.reportMarkers[position.get('id')] = marker;
+ this.getView().getReportSource().addFeature(marker);
+
+ style = this.getReportMarker(position.get('deviceId'));
+ style.getImage().setRotation(position.get('course') * Math.PI / 180);
+ /*style.getText().setText(
+ Ext.Date.format(position.get('fixTime'), Traccar.Style.dateTimeFormat24));*/
+
+ marker.setStyle(style);
+
+ this.reportRoute[this.reportRoute.length - 1].getGeometry().appendCoordinate(point);
+ }
+
+ this.getView().getMapView().fit(this.reportRoute[0].getGeometry(), this.getView().getMap().getSize());
+ }
+ },
+
+ clearReport: function (store) {
+ var key, i;
+
+ if (this.reportRoute) {
+ for (i = 0; i < this.reportRoute.length; i++) {
+ this.getView().getRouteSource().removeFeature(this.reportRoute[i]);
+ }
+ this.reportRoute = null;
+ }
+
+ if (this.reportMarkers) {
+ for (key in this.reportMarkers) {
+ if (this.reportMarkers.hasOwnProperty(key)) {
+ this.getView().getReportSource().removeFeature(this.reportMarkers[key]);
+ }
+ }
+ this.reportMarkers = {};
+ }
+ },
+
+ getRouteStyle: function (deviceId) {
+ var index = 0;
+ if (deviceId !== undefined) {
+ index = deviceId % Traccar.Style.mapRouteColor.length;
+ }
+ return new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: Traccar.Style.mapRouteColor[index],
+ width: Traccar.Style.mapRouteWidth
+ })
+ });
+ },
+
+ getMarkerStyle: function (radius, color) {
+ return new ol.style.Style({
+ image: new ol.style.Arrow({
+ radius: radius,
+ fill: new ol.style.Fill({
+ color: color
+ }),
+ stroke: new ol.style.Stroke({
+ color: Traccar.Style.mapArrowStrokeColor,
+ width: Traccar.Style.mapArrowStrokeWidth
+ })
+ }),
+ text: new ol.style.Text({
+ textBaseline: 'bottom',
+ fill: new ol.style.Fill({
+ color: Traccar.Style.mapTextColor
+ }),
+ stroke: new ol.style.Stroke({
+ color: Traccar.Style.mapTextStrokeColor,
+ width: Traccar.Style.mapTextStrokeWidth
+ }),
+ offsetY: -radius / 2 - Traccar.Style.mapTextOffset,
+ font : Traccar.Style.mapTextFont
+ })
+ });
+ },
+
+ getLatestMarker: function (color) {
+ return this.getMarkerStyle(
+ Traccar.Style.mapRadiusNormal, color);
+ },
+
+ getReportMarker: function (deviceId) {
+ var index = 0;
+ if (deviceId !== undefined) {
+ index = deviceId % Traccar.Style.mapRouteColor.length;
+ }
+ return this.getMarkerStyle(
+ Traccar.Style.mapRadiusNormal, Traccar.Style.mapRouteColor[index]);
+ },
+
+ resizeMarker: function (style, radius) {
+ return new ol.style.Style({
+ image: new ol.style.Arrow({
+ radius: radius,
+ fill: style.getImage().getFill(),
+ stroke: style.getImage().getStroke(),
+ rotation: style.getImage().getRotation()
+ }),
+ text: style.getText()
+ });
+ },
+
+ selectMarker: function (marker, center) {
+ if (this.selectedMarker) {
+ this.selectedMarker.setStyle(
+ this.resizeMarker(this.selectedMarker.getStyle(), Traccar.Style.mapRadiusNormal));
+ }
+
+ if (marker) {
+ marker.setStyle(
+ this.resizeMarker(marker.getStyle(), Traccar.Style.mapRadiusSelected));
+ if (center) {
+ this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates());
+ }
+ }
+
+ this.selectedMarker = marker;
+ },
+
+ selectDevice: function (device, center) {
+ this.selectMarker(this.latestMarkers[device.get('id')], center);
+ },
+
+ selectReport: function (position, center) {
+ this.selectMarker(this.reportMarkers[position.get('id')], center);
+ },
+
+ selectFeature: function (feature) {
+ var record = feature.get('record');
+ if (record) {
+ if (record instanceof Traccar.model.Device) {
+ this.fireEvent('selectdevice', record, false);
+ } else {
+ this.fireEvent('selectreport', record, false);
+ }
+ }
+ },
+
+ getMapState: function () {
+ var zoom, center, projection;
+ projection = this.getView().getMapView().getProjection();
+ center = ol.proj.transform(this.getView().getMapView().getCenter(), projection, 'EPSG:4326');
+ zoom = this.getView().getMapView().getZoom();
+ this.fireEvent('mapstate', center[1], center[0], zoom);
+ }
+});
diff --git a/web/app/view/MapPickerDialogController.js b/web/app/view/MapPickerDialogController.js
new file mode 100644
index 00000000..4f202d19
--- /dev/null
+++ b/web/app/view/MapPickerDialogController.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.MapPickerDialogController', {
+ extend: 'Traccar.view.BaseEditDialogController',
+ alias: 'controller.mapPickerDialog',
+
+ config: {
+ listen: {
+ controller: {
+ '*': {
+ mapstate: 'setMapState'
+ }
+ }
+ }
+ },
+
+ getMapState: function (button) {
+ this.fireEvent('mapstaterequest');
+ },
+
+ setMapState: function (lat, lon, zoom) {
+ this.lookupReference('latitude').setValue(lat);
+ this.lookupReference('longitude').setValue(lon);
+ this.lookupReference('zoom').setValue(zoom);
+ }
+});
diff --git a/web/app/view/Notifications.js b/web/app/view/Notifications.js
new file mode 100644
index 00000000..5ff5f061
--- /dev/null
+++ b/web/app/view/Notifications.js
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Notifications', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'notificationsView',
+
+ requires: [
+ 'Traccar.view.NotificationsController'
+ ],
+
+ controller: 'notificationsController',
+ store: 'AllNotifications',
+
+ selModel: {
+ selType: 'cellmodel'
+ },
+
+ viewConfig: {
+ markDirty: false
+ },
+
+ columns: [{
+ text: Strings.notificationType,
+ dataIndex: 'type',
+ flex: 1,
+ renderer: function (value) {
+ var typeKey = 'event' + value.charAt(0).toUpperCase() + value.slice(1);
+ return Strings[typeKey];
+ }
+ }, {
+ text: Strings.notificationWeb,
+ dataIndex: 'attributes.web',
+ xtype: 'checkcolumn',
+ flex: 1,
+ listeners: {
+ beforeCheckChange: 'onBeforeCheckChange',
+ checkChange: 'onCheckChange'
+ },
+ renderer: function (value, metaData, record) {
+ var fields = this.dataIndex.split('\.', 2);
+ return (new Ext.ux.CheckColumn()).renderer(record.get(fields[0])[fields[1]], metaData);
+ }
+ }, {
+ text: Strings.notificationMail,
+ dataIndex: 'attributes.mail',
+ xtype: 'checkcolumn',
+ flex: 1,
+ listeners: {
+ beforeCheckChange: 'onBeforeCheckChange',
+ checkChange: 'onCheckChange'
+ },
+ renderer: function (value, metaData, record) {
+ var fields = this.dataIndex.split('\.', 2);
+ return (new Ext.ux.CheckColumn()).renderer(record.get(fields[0])[fields[1]], metaData);
+ }
+ }]
+});
diff --git a/web/app/view/NotificationsController.js b/web/app/view/NotificationsController.js
new file mode 100644
index 00000000..4e041eb9
--- /dev/null
+++ b/web/app/view/NotificationsController.js
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.NotificationsController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.notificationsController',
+
+ requires: [
+ 'Traccar.store.Notifications'
+ ],
+
+ init: function () {
+ this.userId = this.getView().user.getId();
+ this.getView().getStore().load({
+ scope: this,
+ callback: function (records, operation, success) {
+ var notificationsStore = Ext.create('Traccar.store.Notifications');
+ notificationsStore.load({
+ params: {
+ userId: this.userId
+ },
+ scope: this,
+ callback: function (records, operation, success) {
+ var i, index, attributes, storeRecord;
+ if (success) {
+ for (i = 0; i < records.length; i++) {
+ index = this.getView().getStore().findExact('type', records[i].get('type'));
+ attributes = records[i].get('attributes');
+ storeRecord = this.getView().getStore().getAt(index);
+ storeRecord.set('attributes', attributes);
+ storeRecord.commit();
+ }
+ }
+ }
+ });
+ }
+ });
+ },
+
+ onBeforeCheckChange: function (column, rowIndex, checked, eOpts) {
+ var fields, record, data;
+ fields = column.dataIndex.split('\.', 2);
+ record = this.getView().getStore().getAt(rowIndex);
+ data = record.get(fields[0]);
+ if (!data[fields[1]]) {
+ data[fields[1]] = 'true';
+ } else {
+ delete data[fields[1]];
+ }
+ record.set(fields[0], data);
+ record.commit();
+ },
+
+ onCheckChange: function (column, rowIndex, checked, eOpts) {
+ var record = this.getView().getStore().getAt(rowIndex);
+ Ext.Ajax.request({
+ scope: this,
+ url: 'api/users/notifications',
+ jsonData: {
+ userId: this.userId,
+ type: record.get('type'),
+ attributes: record.get('attributes')
+ },
+ callback: function (options, success, response) {
+ if (!success) {
+ Traccar.app.showError(response);
+ }
+ }
+ });
+ }
+});
diff --git a/web/app/view/Register.js b/web/app/view/Register.js
new file mode 100644
index 00000000..198e10b8
--- /dev/null
+++ b/web/app/view/Register.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Register', {
+ extend: 'Traccar.view.BaseDialog',
+
+ requires: [
+ 'Traccar.view.RegisterController'
+ ],
+
+ controller: 'register',
+
+ title: Strings.loginRegister,
+
+ items: {
+ xtype: 'form',
+ reference: 'form',
+ jsonSubmit: true,
+
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: Strings.sharedName,
+ allowBlank: false
+ }, {
+ xtype: 'textfield',
+ name: 'email',
+ fieldLabel: Strings.userEmail,
+ vtype: 'email',
+ allowBlank: false
+ }, {
+ xtype: 'textfield',
+ name: 'password',
+ fieldLabel: Strings.userPassword,
+ inputType: 'password',
+ allowBlank: false
+ }]
+ },
+
+ buttons: [{
+ text: Strings.sharedSave,
+ handler: 'onCreateClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/RegisterController.js b/web/app/view/RegisterController.js
new file mode 100644
index 00000000..b79c5f59
--- /dev/null
+++ b/web/app/view/RegisterController.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.RegisterController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.register',
+
+ onCreateClick: function () {
+ var form = this.lookupReference('form');
+ if (form.isValid()) {
+ Ext.Ajax.request({
+ scope: this,
+ method: 'POST',
+ url: 'api/users',
+ jsonData: form.getValues(),
+ callback: this.onCreateReturn
+ });
+ }
+ },
+
+ onCreateReturn: function (options, success, response) {
+ if (success) {
+ this.closeView();
+ Ext.toast(Strings.loginCreated);
+ } else {
+ Traccar.app.showError(response);
+ }
+ }
+
+});
diff --git a/web/app/view/Report.js b/web/app/view/Report.js
new file mode 100644
index 00000000..7e77ef4f
--- /dev/null
+++ b/web/app/view/Report.js
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Report', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'reportView',
+
+ requires: [
+ 'Traccar.view.ReportController'
+ ],
+
+ controller: 'report',
+
+ title: Strings.reportTitle,
+
+ tbar: [{
+ xtype: 'tbtext',
+ html: Strings.sharedType
+ }, {
+ xtype: 'combobox',
+ reference: 'reportTypeField',
+ store: 'ReportTypes',
+ displayField: 'name',
+ valueField: 'key',
+ typeAhead: true,
+ listeners: {
+ change: 'onTypeChange'
+ }
+ }, '-', {
+ text: Strings.reportConfigure,
+ handler: 'onConfigureClick'
+ }, '-', {
+ text: Strings.reportShow,
+ reference: 'showButton',
+ disabled: true,
+ handler: 'onReportClick'
+ }, {
+ text: Strings.reportCsv,
+ reference: 'csvButton',
+ disabled: true,
+ handler: 'onReportClick'
+ }, {
+ text: Strings.reportClear,
+ handler: 'onClearClick'
+ }],
+
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ }
+});
diff --git a/web/app/view/ReportConfigController.js b/web/app/view/ReportConfigController.js
new file mode 100644
index 00000000..df0c1ce4
--- /dev/null
+++ b/web/app/view/ReportConfigController.js
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.ReportConfigController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.reportConfigDialog',
+
+ requires: [
+ 'Traccar.store.ReportEventTypes',
+ 'Traccar.store.AllNotifications'
+ ],
+
+ init: function () {
+ var store = this.lookupReference('eventTypeField').getStore();
+ if (store.getCount() === 0) {
+ store.add({
+ type: Traccar.store.ReportEventTypes.allEvents,
+ name: Strings.eventAll
+ });
+ Ext.create('Traccar.store.AllNotifications').load({
+ scope: this,
+ callback: function (records, operation, success) {
+ var i, value, name, typeKey;
+ if (success) {
+ for (i = 0; i < records.length; i++) {
+ value = records[i].get('type');
+ typeKey = 'event' + value.charAt(0).toUpperCase() + value.slice(1);
+ name = Strings[typeKey];
+ store.add({type: value, name: name});
+ }
+ }
+ }
+ });
+ }
+ },
+
+ onSaveClick: function (button) {
+ var eventType;
+ this.getView().callingPanel.deviceId = this.lookupReference('deviceField').getValue();
+ this.getView().callingPanel.groupId = this.lookupReference('groupField').getValue();
+ eventType = this.lookupReference('eventTypeField').getValue();
+ if (eventType.indexOf(Traccar.store.ReportEventTypes.allEvents) > -1) {
+ eventType = [Traccar.store.ReportEventTypes.allEvents];
+ } else if (eventType.length === this.lookupReference('eventTypeField').getStore().getCount() - 1) {
+ eventType = [Traccar.store.ReportEventTypes.allEvents];
+ }
+ this.getView().callingPanel.eventType = eventType;
+ this.getView().callingPanel.fromDate = this.lookupReference('fromDateField').getValue();
+ this.getView().callingPanel.fromTime = this.lookupReference('fromTimeField').getValue();
+ this.getView().callingPanel.toDate = this.lookupReference('toDateField').getValue();
+ this.getView().callingPanel.toTime = this.lookupReference('toTimeField').getValue();
+ this.getView().callingPanel.updateButtons();
+ button.up('window').close();
+ }
+});
diff --git a/web/app/view/ReportConfigDialog.js b/web/app/view/ReportConfigDialog.js
new file mode 100644
index 00000000..547bd297
--- /dev/null
+++ b/web/app/view/ReportConfigDialog.js
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.ReportConfigDialog', {
+ extend: 'Traccar.view.BaseDialog',
+
+ requires: [
+ 'Traccar.view.ReportConfigController',
+ 'Traccar.view.CustomTimeField'
+ ],
+
+ controller: 'reportConfigDialog',
+ title: Strings.reportConfigure,
+
+ items: [{
+ fieldLabel: Strings.reportDevice,
+ xtype: 'tagfield',
+ width: Traccar.Style.reportTagfieldWidth,
+ reference: 'deviceField',
+ store: 'Devices',
+ valueField: 'id',
+ displayField: 'name',
+ queryMode: 'local'
+ }, {
+ fieldLabel: Strings.reportGroup,
+ xtype: 'tagfield',
+ width: Traccar.Style.reportTagfieldWidth,
+ reference: 'groupField',
+ store: 'Groups',
+ valueField: 'id',
+ displayField: 'name',
+ queryMode: 'local'
+ }, {
+ fieldLabel: Strings.reportEventTypes,
+ xtype: 'tagfield',
+ width: Traccar.Style.reportTagfieldWidth,
+ reference: 'eventTypeField',
+ store: 'ReportEventTypes',
+ hidden: true,
+ valueField: 'type',
+ displayField: 'name',
+ queryMode: 'local'
+ }, {
+ xtype: 'fieldcontainer',
+ layout: 'hbox',
+ items: [{
+ xtype: 'datefield',
+ fieldLabel: Strings.reportFrom,
+ reference: 'fromDateField',
+ startDay: Traccar.Style.weekStartDay,
+ format: Traccar.Style.dateFormat,
+ value: new Date(new Date().getTime() - 30 * 60 * 1000)
+ }, {
+ xtype: 'customTimeField',
+ reference: 'fromTimeField',
+ maxWidth: Traccar.Style.reportTime,
+ value: new Date(new Date().getTime() - 30 * 60 * 1000)
+ }]
+ }, {
+ xtype: 'fieldcontainer',
+ layout: 'hbox',
+ items: [{
+ xtype: 'datefield',
+ fieldLabel: Strings.reportTo,
+ reference: 'toDateField',
+ startDay: Traccar.Style.weekStartDay,
+ format: Traccar.Style.dateFormat,
+ value: new Date()
+ }, {
+ xtype: 'customTimeField',
+ reference: 'toTimeField',
+ maxWidth: Traccar.Style.reportTime,
+ value: new Date()
+ }]
+ }],
+
+ buttons: [{
+ text: Strings.sharedSave,
+ handler: 'onSaveClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/ReportController.js b/web/app/view/ReportController.js
new file mode 100644
index 00000000..19baec81
--- /dev/null
+++ b/web/app/view/ReportController.js
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2015 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ * Copyright 2016 Andrey Kunitsyn (abyss@fox5.ru)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.ReportController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.report',
+
+ requires: [
+ 'Traccar.AttributeFormatter',
+ 'Traccar.view.ReportConfigDialog',
+ 'Traccar.store.ReportEventTypes'
+ ],
+
+ config: {
+ listen: {
+ controller: {
+ '*': {
+ selectdevice: 'selectDevice',
+ selectreport: 'selectReport'
+ }
+ }
+ }
+ },
+
+ onConfigureClick: function () {
+ var dialog = Ext.create('Traccar.view.ReportConfigDialog');
+ dialog.lookupReference('eventTypeField').setHidden(this.lookupReference('reportTypeField').getValue() !== 'events');
+ dialog.callingPanel = this;
+ dialog.lookupReference('deviceField').setValue(this.deviceId);
+ dialog.lookupReference('groupField').setValue(this.groupId);
+ if (this.eventType !== undefined) {
+ dialog.lookupReference('eventTypeField').setValue(this.eventType);
+ } else {
+ dialog.lookupReference('eventTypeField').setValue([Traccar.store.ReportEventTypes.allEvents]);
+ }
+ if (this.fromDate !== undefined) {
+ dialog.lookupReference('fromDateField').setValue(this.fromDate);
+ }
+ if (this.fromTime !== undefined) {
+ dialog.lookupReference('fromTimeField').setValue(this.fromTime);
+ }
+ if (this.toDate !== undefined) {
+ dialog.lookupReference('toDateField').setValue(this.toDate);
+ }
+ if (this.toTime !== undefined) {
+ dialog.lookupReference('toTimeField').setValue(this.toTime);
+ }
+ dialog.show();
+ },
+
+ updateButtons: function () {
+ var reportType, disabled, devices, time;
+ reportType = this.lookupReference('reportTypeField').getValue();
+ devices = (this.deviceId && this.deviceId.length !== 0) || (this.groupId && this.groupId.length !== 0);
+ time = this.fromDate && this.fromTime && this.toDate && this.toTime;
+ disabled = !reportType || !devices || !time;
+ this.lookupReference('showButton').setDisabled(disabled);
+ this.lookupReference('csvButton').setDisabled(disabled);
+ },
+
+ onReportClick: function (button) {
+ var reportType, from, to, store, url;
+
+ reportType = this.lookupReference('reportTypeField').getValue();
+
+ if (reportType && (this.deviceId || this.groupId)) {
+ from = new Date(
+ this.fromDate.getFullYear(), this.fromDate.getMonth(), this.fromDate.getDate(),
+ this.fromTime.getHours(), this.fromTime.getMinutes(), this.fromTime.getSeconds(), this.fromTime.getMilliseconds());
+
+ to = new Date(
+ this.toDate.getFullYear(), this.toDate.getMonth(), this.toDate.getDate(),
+ this.toTime.getHours(), this.toTime.getMinutes(), this.toTime.getSeconds(), this.toTime.getMilliseconds());
+
+ if (button.reference === 'showButton') {
+ store = this.getView().getStore();
+ store.load({
+ params: {
+ deviceId: this.deviceId,
+ groupId: this.groupId,
+ type: this.eventType,
+ from: from.toISOString(),
+ to: to.toISOString()
+ }
+ });
+ } else if (button.reference === 'csvButton') {
+ url = this.getView().getStore().getProxy().url;
+ this.downloadCsv(url, {
+ deviceId: this.deviceId,
+ groupId: this.groupId,
+ type: this.eventType,
+ from: from.toISOString(),
+ to: to.toISOString()
+ });
+ }
+ }
+ },
+
+ onClearClick: function () {
+ this.getView().getStore().removeAll();
+ },
+
+ onSelectionChange: function (selected) {
+ if (selected.getCount() > 0) {
+ this.fireEvent('selectreport', selected.getLastSelected(), true);
+ }
+ },
+
+ selectDevice: function (device) {
+ if (device) {
+ this.getView().getSelectionModel().deselectAll();
+ }
+ },
+
+ selectReport: function (position, center) {
+ if (position instanceof Traccar.model.Position) {
+ this.getView().getSelectionModel().select([position], false, true);
+ }
+ },
+
+ downloadCsv: function (requestUrl, requestParams) {
+ Ext.Ajax.request({
+ url: requestUrl,
+ method: 'GET',
+ params: requestParams,
+ headers: {
+ Accept: 'text/csv'
+ },
+ success: function (response) {
+ var disposition, filename, type, blob, url, downloadUrl, elementA;
+ disposition = response.getResponseHeader('Content-Disposition');
+ filename = disposition.slice(disposition.indexOf('=') + 1, disposition.length);
+ type = response.getResponseHeader('Content-Type');
+ blob = new Blob([response.responseText], {type: type});
+ if (typeof window.navigator.msSaveBlob !== 'undefined') {
+ // IE workaround
+ window.navigator.msSaveBlob(blob, filename);
+ } else {
+ url = window.URL || window.webkitURL;
+ downloadUrl = url.createObjectURL(blob);
+ if (filename) {
+ elementA = document.createElement('a');
+ elementA.href = downloadUrl;
+ elementA.download = filename;
+ document.body.appendChild(elementA);
+ elementA.click();
+ }
+ setTimeout(function () {
+ url.revokeObjectURL(downloadUrl);
+ }, 100);
+ }
+ }
+ });
+ },
+
+ onTypeChange: function (combobox, newValue, oldValue) {
+ if (oldValue !== null) {
+ this.onClearClick();
+ }
+
+ if (newValue === 'route') {
+ this.getView().reconfigure('ReportRoute', this.routeColumns);
+ } else if (newValue === 'events') {
+ this.getView().reconfigure('ReportEvents', this.eventsColumns);
+ } else if (newValue === 'summary') {
+ this.getView().reconfigure('ReportSummary', this.summaryColumns);
+ } else if (newValue === 'trips') {
+ this.getView().reconfigure('ReportTrips', this.tripsColumns);
+ }
+
+ this.updateButtons();
+ },
+
+ routeColumns: [{
+ text: Strings.positionValid,
+ dataIndex: 'valid',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('valid')
+ }, {
+ text: Strings.positionFixTime,
+ dataIndex: 'fixTime',
+ flex: 1,
+ xtype: 'datecolumn',
+ renderer: Traccar.AttributeFormatter.getFormatter('fixTime')
+ }, {
+ text: Strings.positionLatitude,
+ dataIndex: 'latitude',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('latitude')
+ }, {
+ text: Strings.positionLongitude,
+ dataIndex: 'longitude',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('latitude')
+ }, {
+ text: Strings.positionAltitude,
+ dataIndex: 'altitude',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('altitude')
+ }, {
+ text: Strings.positionSpeed,
+ dataIndex: 'speed',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('speed')
+ }, {
+ text: Strings.positionAddress,
+ dataIndex: 'address',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('address')
+ }],
+
+ eventsColumns: [{
+ text: Strings.positionFixTime,
+ dataIndex: 'serverTime',
+ flex: 1,
+ xtype: 'datecolumn',
+ renderer: Traccar.AttributeFormatter.getFormatter('serverTime')
+ }, {
+ text: Strings.reportDeviceName,
+ dataIndex: 'deviceId',
+ flex: 1,
+ renderer: function (value) {
+ return Ext.getStore('Devices').findRecord('id', value).get('name');
+ }
+ }, {
+ text: Strings.sharedType,
+ dataIndex: 'type',
+ flex: 1,
+ renderer: function (value) {
+ var typeKey = 'event' + value.charAt(0).toUpperCase() + value.slice(1);
+ return Strings[typeKey];
+ }
+ }, {
+ text: Strings.sharedGeofence,
+ dataIndex: 'geofenceId',
+ flex: 1,
+ renderer: function (value) {
+ if (value !== 0) {
+ return Ext.getStore('Geofences').findRecord('id', value).get('name');
+ }
+ }
+ }],
+
+ summaryColumns: [{
+ text: Strings.reportDeviceName,
+ dataIndex: 'deviceId',
+ flex: 1,
+ renderer: function (value) {
+ return Ext.getStore('Devices').findRecord('id', value).get('name');
+ }
+ }, {
+ text: Strings.sharedDistance,
+ dataIndex: 'distance',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('distance')
+ }, {
+ text: Strings.reportAverageSpeed,
+ dataIndex: 'averageSpeed',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('speed')
+ }, {
+ text: Strings.reportMaximumSpeed,
+ dataIndex: 'maxSpeed',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('speed')
+ }, {
+ text: Strings.reportEngineHours,
+ dataIndex: 'engineHours',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('hours')
+ }],
+
+ tripsColumns: [{
+ text: Strings.reportDeviceName,
+ dataIndex: 'deviceId',
+ flex: 1,
+ renderer: function (value) {
+ return Ext.getStore('Devices').findRecord('id', value).get('name');
+ }
+ }, {
+ text: Strings.reportStartTime,
+ dataIndex: 'startTime',
+ flex: 1,
+ xtype: 'datecolumn',
+ renderer: Traccar.AttributeFormatter.getFormatter('startTime')
+ }, {
+ text: Strings.reportStartAddress,
+ dataIndex: 'startAddress',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('address')
+ }, {
+ text: Strings.reportEndTime,
+ dataIndex: 'endTime',
+ flex: 1,
+ xtype: 'datecolumn',
+ renderer: Traccar.AttributeFormatter.getFormatter('endTime')
+ }, {
+ text: Strings.reportEndAddress,
+ dataIndex: 'endAddress',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('address')
+ }, {
+ text: Strings.sharedDistance,
+ dataIndex: 'distance',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('distance')
+ }, {
+ text: Strings.reportAverageSpeed,
+ dataIndex: 'averageSpeed',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('speed')
+ }, {
+ text: Strings.reportMaximumSpeed,
+ dataIndex: 'maxSpeed',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('speed')
+ }, {
+ text: Strings.reportDuration,
+ dataIndex: 'duration',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('duration')
+ }, {
+ text: Strings.reportSpentFuel,
+ dataIndex: 'spentFuel',
+ flex: 1,
+ renderer: Traccar.AttributeFormatter.getFormatter('spentFuel')
+ }]
+
+});
diff --git a/web/app/view/ServerDialog.js b/web/app/view/ServerDialog.js
new file mode 100644
index 00000000..46c76ffa
--- /dev/null
+++ b/web/app/view/ServerDialog.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.ServerDialog', {
+ extend: 'Traccar.view.BaseEditDialog',
+
+ requires: [
+ 'Traccar.view.MapPickerDialogController'
+ ],
+
+ controller: 'mapPickerDialog',
+ title: Strings.serverTitle,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'checkboxfield',
+ name: 'registration',
+ fieldLabel: Strings.serverRegistration,
+ allowBlank: false
+ }, {
+ xtype: 'checkboxfield',
+ name: 'readonly',
+ fieldLabel: Strings.serverReadonly,
+ allowBlank: false
+ }, {
+ xtype: 'combobox',
+ name: 'map',
+ fieldLabel: Strings.mapLayer,
+ store: 'MapTypes',
+ displayField: 'name',
+ valueField: 'key'
+ }, {
+ xtype: 'textfield',
+ name: 'bingKey',
+ fieldLabel: Strings.mapBingKey
+ }, {
+ xtype: 'textfield',
+ name: 'mapUrl',
+ fieldLabel: Strings.mapCustom
+ }, {
+ xtype: 'combobox',
+ name: 'distanceUnit',
+ fieldLabel: Strings.sharedDistance,
+ store: 'DistanceUnits',
+ displayField: 'name',
+ valueField: 'key'
+ }, {
+ xtype: 'combobox',
+ name: 'speedUnit',
+ fieldLabel: Strings.settingsSpeedUnit,
+ store: 'SpeedUnits',
+ displayField: 'name',
+ valueField: 'key'
+ }, {
+ xtype: 'numberfield',
+ reference: 'latitude',
+ name: 'latitude',
+ fieldLabel: Strings.positionLatitude,
+ decimalPrecision: Traccar.Style.coordinatePrecision
+ }, {
+ xtype: 'numberfield',
+ reference: 'longitude',
+ name: 'longitude',
+ fieldLabel: Strings.positionLongitude,
+ decimalPrecision: Traccar.Style.coordinatePrecision
+ }, {
+ xtype: 'numberfield',
+ reference: 'zoom',
+ name: 'zoom',
+ fieldLabel: Strings.serverZoom
+ }, {
+ xtype: 'checkboxfield',
+ name: 'twelveHourFormat',
+ fieldLabel: Strings.settingsTwelveHourFormat,
+ allowBlank: false
+ }]
+ },
+
+ buttons: [{
+ text: Strings.sharedAttributes,
+ handler: 'showAttributesView'
+ }, {
+ glyph: 'xf041@FontAwesome',
+ minWidth: 0,
+ handler: 'getMapState',
+ tooltip: Strings.sharedGetMapState,
+ tooltipType: 'title'
+ }, {
+ xtype: 'tbfill'
+ }, {
+ text: Strings.sharedSave,
+ handler: 'onSaveClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/SettingsMenu.js b/web/app/view/SettingsMenu.js
new file mode 100644
index 00000000..bf184424
--- /dev/null
+++ b/web/app/view/SettingsMenu.js
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.SettingsMenu', {
+ extend: 'Ext.button.Button',
+ xtype: 'settingsMenu',
+
+ requires: [
+ 'Traccar.view.SettingsMenuController'
+ ],
+
+ glyph: 'xf013@FontAwesome',
+ text: Strings.settingsTitle,
+
+ menu: {
+ controller: 'settings',
+
+ items: [{
+ text: Strings.settingsUser,
+ handler: 'onUserClick'
+ }, {
+ hidden: true,
+ text: Strings.settingsGroups,
+ handler: 'onGroupsClick',
+ reference: 'settingsGroupsButton'
+ }, {
+ hidden: true,
+ text: Strings.sharedGeofences,
+ handler: 'onGeofencesClick',
+ reference: 'settingsGeofencesButton'
+ }, {
+ text: Strings.settingsServer,
+ hidden: true,
+ handler: 'onServerClick',
+ reference: 'settingsServerButton'
+ }, {
+ text: Strings.settingsUsers,
+ hidden: true,
+ handler: 'onUsersClick',
+ reference: 'settingsUsersButton'
+ }, {
+ hidden: true,
+ text: Strings.sharedNotifications,
+ handler: 'onNotificationsClick',
+ reference: 'settingsNotificationsButton'
+ }, {
+ text: Strings.loginLogout,
+ handler: 'onLogoutClick'
+ }]
+ }
+});
diff --git a/web/app/view/SettingsMenuController.js b/web/app/view/SettingsMenuController.js
new file mode 100644
index 00000000..ebaa7007
--- /dev/null
+++ b/web/app/view/SettingsMenuController.js
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.SettingsMenuController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.settings',
+
+ requires: [
+ 'Traccar.view.LoginController',
+ 'Traccar.view.UserDialog',
+ 'Traccar.view.ServerDialog',
+ 'Traccar.view.Users',
+ 'Traccar.view.Groups',
+ 'Traccar.view.Geofences',
+ 'Traccar.view.Notifications',
+ 'Traccar.view.BaseWindow'
+ ],
+
+ init: function () {
+ var admin, readonly;
+ admin = Traccar.app.getUser().get('admin');
+ readonly = Traccar.app.getServer().get('readonly');
+ if (admin) {
+ this.lookupReference('settingsServerButton').setHidden(false);
+ this.lookupReference('settingsUsersButton').setHidden(false);
+ }
+ if (admin || !readonly) {
+ this.lookupReference('settingsGroupsButton').setHidden(false);
+ this.lookupReference('settingsGeofencesButton').setHidden(false);
+ this.lookupReference('settingsNotificationsButton').setHidden(false);
+ }
+ },
+
+ onUserClick: function () {
+ var dialog = Ext.create('Traccar.view.UserDialog');
+ dialog.down('form').loadRecord(Traccar.app.getUser());
+ dialog.show();
+ },
+
+ onGroupsClick: function () {
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.settingsGroups,
+ modal: false,
+ items: {
+ xtype: 'groupsView'
+ }
+ }).show();
+ },
+
+ onGeofencesClick: function () {
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedGeofences,
+ modal: false,
+ items: {
+ xtype: 'geofencesView'
+ }
+ }).show();
+ },
+
+ onServerClick: function () {
+ var dialog = Ext.create('Traccar.view.ServerDialog');
+ dialog.down('form').loadRecord(Traccar.app.getServer());
+ dialog.show();
+ },
+
+ onUsersClick: function () {
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.settingsUsers,
+ modal: false,
+ items: {
+ xtype: 'usersView'
+ }
+ }).show();
+ },
+
+ onNotificationsClick: function () {
+ var user = Traccar.app.getUser();
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedNotifications,
+ modal: false,
+ items: {
+ xtype: 'notificationsView',
+ user: user
+ }
+ }).show();
+ },
+
+ onLogoutClick: function () {
+ Ext.create('Traccar.view.LoginController').logout();
+ }
+});
diff --git a/web/app/view/State.js b/web/app/view/State.js
new file mode 100644
index 00000000..547fb0cf
--- /dev/null
+++ b/web/app/view/State.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.State', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'stateView',
+
+ requires: [
+ 'Traccar.view.StateController'
+ ],
+
+ controller: 'state',
+ store: 'Attributes',
+
+ title: Strings.stateTitle,
+
+ columns: [{
+ text: Strings.stateName,
+ dataIndex: 'name',
+ flex: 1
+ }, {
+ text: Strings.stateValue,
+ dataIndex: 'value',
+ flex: 1,
+ renderer: function (value, metaData, record) {
+ if (record.get('name') === 'Alarm') {
+ metaData.tdCls = 'view-color-red';
+ }
+ return value;
+ }
+ }]
+});
diff --git a/web/app/view/StateController.js b/web/app/view/StateController.js
new file mode 100644
index 00000000..567a3925
--- /dev/null
+++ b/web/app/view/StateController.js
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.StateController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.state',
+
+ requires: [
+ 'Traccar.AttributeFormatter',
+ 'Traccar.model.Attribute'
+ ],
+
+ config: {
+ listen: {
+ controller: {
+ '*': {
+ selectDevice: 'selectDevice',
+ selectReport: 'selectReport'
+ }
+ },
+ store: {
+ '#LatestPositions': {
+ add: 'updateLatest',
+ update: 'updateLatest'
+ },
+ '#Positions': {
+ clear: 'clearReport'
+ }
+ }
+ }
+ },
+
+ keys: (function () {
+ var i, list, result;
+ result = {};
+ list = ['fixTime', 'latitude', 'longitude', 'valid', 'altitude', 'speed', 'course', 'address', 'protocol'];
+ for (i = 0; i < list.length; i++) {
+ result[list[i]] = {
+ priority: i,
+ name: Strings['position' + list[i].replace(/^\w/g, function (s) {
+ return s.toUpperCase();
+ })]
+ };
+ }
+ return result;
+ }()),
+
+ updateLatest: function (store, data) {
+ var i;
+ if (!Ext.isArray(data)) {
+ data = [data];
+ }
+ for (i = 0; i < data.length; i++) {
+ if (this.deviceId === data[i].get('deviceId')) {
+ this.updatePosition(data[i]);
+ }
+ }
+ },
+
+ formatValue: function (value) {
+ if (typeof (id) === 'number') {
+ return Number(value.toFixed(2));
+ } else {
+ return value;
+ }
+ },
+
+ updatePosition: function (position) {
+ var attributes, store, key;
+ store = Ext.getStore('Attributes');
+ store.removeAll();
+
+ for (key in position.data) {
+ if (position.data.hasOwnProperty(key) && this.keys[key] !== undefined) {
+ store.add(Ext.create('Traccar.model.Attribute', {
+ priority: this.keys[key].priority,
+ name: this.keys[key].name,
+ value: Traccar.AttributeFormatter.getFormatter(key)(position.get(key))
+ }));
+ }
+ }
+
+ attributes = position.get('attributes');
+ if (attributes instanceof Object) {
+ for (key in attributes) {
+ if (attributes.hasOwnProperty(key)) {
+ store.add(Ext.create('Traccar.model.Attribute', {
+ priority: 1024,
+ name: key.replace(/^./, function (match) {
+ return match.toUpperCase();
+ }),
+ value: Traccar.AttributeFormatter.getFormatter(key)(attributes[key])
+ }));
+ }
+ }
+ }
+ },
+
+ selectDevice: function (device) {
+ var position;
+ this.deviceId = device.get('id');
+ position = Ext.getStore('LatestPositions').findRecord('deviceId', this.deviceId, 0, false, false, true);
+ if (position) {
+ this.updatePosition(position);
+ } else {
+ Ext.getStore('Attributes').removeAll();
+ }
+ },
+
+ selectReport: function (position) {
+ this.deviceId = null;
+ this.updatePosition(position);
+ },
+
+ clearReport: function (store) {
+ Ext.getStore('Attributes').removeAll();
+ }
+});
diff --git a/web/app/view/UserDevices.js b/web/app/view/UserDevices.js
new file mode 100644
index 00000000..6a1a718a
--- /dev/null
+++ b/web/app/view/UserDevices.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.UserDevices', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'userDevicesView',
+
+ requires: [
+ 'Traccar.view.BasePermissionsController'
+ ],
+
+ controller: 'basePermissionsController',
+
+ selModel: {
+ selType: 'checkboxmodel',
+ checkOnly: true,
+ showHeaderCheckbox: false
+ },
+
+ listeners: {
+ beforedeselect: 'onBeforeDeselect',
+ beforeselect: 'onBeforeSelect'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }, {
+ text: Strings.deviceIdentifier,
+ dataIndex: 'uniqueId',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/UserDialog.js b/web/app/view/UserDialog.js
new file mode 100644
index 00000000..f9e704ee
--- /dev/null
+++ b/web/app/view/UserDialog.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.UserDialog', {
+ extend: 'Traccar.view.BaseEditDialog',
+
+ requires: [
+ 'Traccar.view.UserDialogController'
+ ],
+
+ controller: 'userDialog',
+ title: Strings.settingsUser,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: Strings.sharedName
+ }, {
+ xtype: 'textfield',
+ name: 'email',
+ fieldLabel: Strings.userEmail,
+ allowBlank: false
+ }, {
+ xtype: 'textfield',
+ name: 'password',
+ fieldLabel: Strings.userPassword,
+ inputType: 'password',
+ allowBlank: false
+ }, {
+ xtype: 'checkboxfield',
+ name: 'admin',
+ fieldLabel: Strings.userAdmin,
+ allowBlank: false,
+ disabled: true,
+ reference: 'adminField'
+ }, {
+ xtype: 'combobox',
+ name: 'map',
+ fieldLabel: Strings.mapLayer,
+ store: 'MapTypes',
+ displayField: 'name',
+ valueField: 'key'
+ }, {
+ xtype: 'combobox',
+ name: 'distanceUnit',
+ fieldLabel: Strings.sharedDistance,
+ store: 'DistanceUnits',
+ displayField: 'name',
+ valueField: 'key'
+ }, {
+ xtype: 'combobox',
+ name: 'speedUnit',
+ fieldLabel: Strings.settingsSpeedUnit,
+ store: 'SpeedUnits',
+ displayField: 'name',
+ valueField: 'key'
+ }, {
+ xtype: 'numberfield',
+ reference: 'latitude',
+ name: 'latitude',
+ fieldLabel: Strings.positionLatitude,
+ decimalPrecision: Traccar.Style.coordinatePrecision
+ }, {
+ xtype: 'numberfield',
+ reference: 'longitude',
+ name: 'longitude',
+ fieldLabel: Strings.positionLongitude,
+ decimalPrecision: Traccar.Style.coordinatePrecision
+ }, {
+ xtype: 'numberfield',
+ reference: 'zoom',
+ name: 'zoom',
+ fieldLabel: Strings.serverZoom
+ }, {
+ xtype: 'checkboxfield',
+ name: 'twelveHourFormat',
+ fieldLabel: Strings.settingsTwelveHourFormat,
+ allowBlank: false
+ }]
+ },
+
+ buttons: [{
+ text: Strings.sharedAttributes,
+ handler: 'showAttributesView'
+ }, {
+ glyph: 'xf041@FontAwesome',
+ minWidth: 0,
+ handler: 'getMapState',
+ tooltip: Strings.sharedGetMapState,
+ tooltipType: 'title'
+ }, {
+ xtype: 'tbfill'
+ }, {
+ text: Strings.sharedSave,
+ handler: 'onSaveClick'
+ }, {
+ text: Strings.sharedCancel,
+ handler: 'closeView'
+ }]
+});
diff --git a/web/app/view/UserDialogController.js b/web/app/view/UserDialogController.js
new file mode 100644
index 00000000..52d649b9
--- /dev/null
+++ b/web/app/view/UserDialogController.js
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.UserDialogController', {
+ extend: 'Traccar.view.MapPickerDialogController',
+ alias: 'controller.userDialog',
+
+ init: function () {
+ if (Traccar.app.getUser().get('admin')) {
+ this.lookupReference('adminField').setDisabled(false);
+ }
+ },
+
+ onSaveClick: function (button) {
+ var dialog, record, store;
+ dialog = button.up('window').down('form');
+ dialog.updateRecord();
+ record = dialog.getRecord();
+ if (record === Traccar.app.getUser()) {
+ record.save();
+ } else {
+ store = Ext.getStore('Users');
+ if (record.phantom) {
+ store.add(record);
+ }
+ store.sync({
+ failure: function (batch) {
+ store.rejectChanges();
+ Traccar.app.showError(batch.exceptions[0].getError().response);
+ }
+ });
+ }
+ button.up('window').close();
+ }
+});
diff --git a/web/app/view/UserGeofences.js b/web/app/view/UserGeofences.js
new file mode 100644
index 00000000..03a02ff3
--- /dev/null
+++ b/web/app/view/UserGeofences.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.UserGeofences', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'userGeofencesView',
+
+ requires: [
+ 'Traccar.view.BasePermissionsController'
+ ],
+
+ controller: 'basePermissionsController',
+
+ selModel: {
+ selType: 'checkboxmodel',
+ checkOnly: true,
+ showHeaderCheckbox: false
+ },
+
+ listeners: {
+ beforedeselect: 'onBeforeDeselect',
+ beforeselect: 'onBeforeSelect'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/UserGroups.js b/web/app/view/UserGroups.js
new file mode 100644
index 00000000..84032f02
--- /dev/null
+++ b/web/app/view/UserGroups.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.UserGroups', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'userGroupsView',
+
+ requires: [
+ 'Traccar.view.BasePermissionsController'
+ ],
+
+ controller: 'basePermissionsController',
+
+ selModel: {
+ selType: 'checkboxmodel',
+ checkOnly: true,
+ showHeaderCheckbox: false
+ },
+
+ listeners: {
+ beforedeselect: 'onBeforeDeselect',
+ beforeselect: 'onBeforeSelect'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/Users.js b/web/app/view/Users.js
new file mode 100644
index 00000000..4abfff1e
--- /dev/null
+++ b/web/app/view/Users.js
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.Users', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'usersView',
+
+ requires: [
+ 'Traccar.view.UsersController',
+ 'Traccar.view.EditToolbar'
+ ],
+
+ controller: 'users',
+ store: 'Users',
+
+ selType: 'rowmodel',
+
+ tbar: {
+ xtype: 'editToolbar',
+ items: [{
+ text: Strings.deviceTitle,
+ disabled: true,
+ handler: 'onDevicesClick',
+ reference: 'userDevicesButton'
+ }, {
+ text: Strings.settingsGroups,
+ disabled: true,
+ handler: 'onGroupsClick',
+ reference: 'userGroupsButton'
+ }, {
+ text: Strings.sharedGeofences,
+ disabled: true,
+ handler: 'onGeofencesClick',
+ reference: 'userGeofencesButton'
+ }, {
+ text: Strings.sharedNotifications,
+ disabled: true,
+ handler: 'onNotificationsClick',
+ reference: 'userNotificationsButton'
+ }]
+ },
+
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ },
+
+ columns: [{
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
+ }, {
+ text: Strings.userEmail,
+ dataIndex: 'email',
+ flex: 1
+ }, {
+ text: Strings.userAdmin,
+ dataIndex: 'admin',
+ flex: 1
+ }]
+});
diff --git a/web/app/view/UsersController.js b/web/app/view/UsersController.js
new file mode 100644
index 00000000..fad5f245
--- /dev/null
+++ b/web/app/view/UsersController.js
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.UsersController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.users',
+
+ requires: [
+ 'Traccar.view.UserDialog',
+ 'Traccar.view.UserDevices',
+ 'Traccar.view.UserGroups',
+ 'Traccar.view.UserGeofences',
+ 'Traccar.view.Notifications',
+ 'Traccar.view.BaseWindow',
+ 'Traccar.model.User'
+ ],
+
+ init: function () {
+ Ext.getStore('Users').load();
+ },
+
+ onAddClick: function () {
+ var user, dialog;
+ user = Ext.create('Traccar.model.User');
+ dialog = Ext.create('Traccar.view.UserDialog');
+ dialog.down('form').loadRecord(user);
+ dialog.show();
+ },
+
+ onEditClick: function () {
+ var user, dialog;
+ user = this.getView().getSelectionModel().getSelection()[0];
+ dialog = Ext.create('Traccar.view.UserDialog');
+ dialog.down('form').loadRecord(user);
+ dialog.show();
+ },
+
+ onRemoveClick: function () {
+ var user = this.getView().getSelectionModel().getSelection()[0];
+ Ext.Msg.show({
+ title: Strings.settingsUser,
+ message: Strings.sharedRemoveConfirm,
+ buttons: Ext.Msg.YESNO,
+ buttonText: {
+ yes: Strings.sharedRemove,
+ no: Strings.sharedCancel
+ },
+ fn: function (btn) {
+ var store = Ext.getStore('Users');
+ if (btn === 'yes') {
+ store.remove(user);
+ store.sync();
+ }
+ }
+ });
+ },
+
+ onDevicesClick: function () {
+ var user = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.deviceTitle,
+ items: {
+ xtype: 'userDevicesView',
+ baseObjectName: 'userId',
+ linkObjectName: 'deviceId',
+ storeName: 'AllDevices',
+ linkStoreName: 'Devices',
+ urlApi: 'api/permissions/devices',
+ baseObject: user.getId()
+ }
+ }).show();
+ },
+
+ onGroupsClick: function () {
+ var user = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.settingsGroups,
+ items: {
+ xtype: 'userGroupsView',
+ baseObjectName: 'userId',
+ linkObjectName: 'groupId',
+ storeName: 'AllGroups',
+ linkStoreName: 'Groups',
+ urlApi: 'api/permissions/groups',
+ baseObject: user.getId()
+ }
+ }).show();
+ },
+
+ onGeofencesClick: function () {
+ var user = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedGeofences,
+ items: {
+ xtype: 'userGeofencesView',
+ baseObjectName: 'userId',
+ linkObjectName: 'geofenceId',
+ storeName: 'AllGeofences',
+ linkStoreName: 'Geofences',
+ urlApi: 'api/permissions/geofences',
+ baseObject: user.getId()
+ }
+ }).show();
+ },
+
+ onNotificationsClick: function () {
+ var user = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedNotifications,
+ modal: false,
+ items: {
+ xtype: 'notificationsView',
+ user: user
+ }
+ }).show();
+ },
+
+ onSelectionChange: function (selected) {
+ var disabled = selected.length > 0;
+ this.lookupReference('toolbarEditButton').setDisabled(disabled);
+ this.lookupReference('toolbarRemoveButton').setDisabled(disabled);
+ this.lookupReference('userDevicesButton').setDisabled(disabled);
+ this.lookupReference('userGroupsButton').setDisabled(disabled);
+ this.lookupReference('userGeofencesButton').setDisabled(disabled);
+ this.lookupReference('userNotificationsButton').setDisabled(disabled);
+ }
+});