aboutsummaryrefslogtreecommitdiff
path: root/web/app
diff options
context:
space:
mode:
Diffstat (limited to 'web/app')
-rw-r--r--web/app/Application.js12
-rw-r--r--web/app/DeviceImages.js9
-rw-r--r--web/app/GeofenceConverter.js22
-rw-r--r--web/app/Style.js6
-rw-r--r--web/app/controller/Root.js183
-rw-r--r--web/app/model/Calendar.js34
-rw-r--r--web/app/model/Geofence.js3
-rw-r--r--web/app/model/Notification.js8
-rw-r--r--web/app/store/AllCalendars.js30
-rw-r--r--web/app/store/Calendars.js30
-rw-r--r--web/app/store/GeofenceTypes.js3
-rw-r--r--web/app/store/Notifications.js4
-rw-r--r--web/app/store/Positions.js5
-rw-r--r--web/app/view/CalendarDialog.js58
-rw-r--r--web/app/view/CalendarDialogController.js37
-rw-r--r--web/app/view/Calendars.js52
-rw-r--r--web/app/view/CalendarsController.js74
-rw-r--r--web/app/view/GeofenceDialog.js8
-rw-r--r--web/app/view/GeofencesController.js1
-rw-r--r--web/app/view/Login.js14
-rw-r--r--web/app/view/MapController.js323
-rw-r--r--web/app/view/MapMarkerController.js387
-rw-r--r--web/app/view/Notifications.js19
-rw-r--r--web/app/view/NotificationsController.js44
-rw-r--r--web/app/view/ReportConfigController.js6
-rw-r--r--web/app/view/ReportController.js96
-rw-r--r--web/app/view/ServerDialog.js8
-rw-r--r--web/app/view/SettingsMenu.js6
-rw-r--r--web/app/view/SettingsMenuController.js13
-rw-r--r--web/app/view/State.js5
-rw-r--r--web/app/view/UserCalendars.js49
-rw-r--r--web/app/view/UserDialog.js16
-rw-r--r--web/app/view/UserDialogController.js10
-rw-r--r--web/app/view/Users.js10
-rw-r--r--web/app/view/UsersController.js21
35 files changed, 1112 insertions, 494 deletions
diff --git a/web/app/Application.js b/web/app/Application.js
index 6f4cb1dd..daa25b85 100644
--- a/web/app/Application.js
+++ b/web/app/Application.js
@@ -37,7 +37,8 @@ Ext.define('Traccar.Application', {
'Notification',
'AttributeAlias',
'ReportSummary',
- 'ReportTrip'
+ 'ReportTrip',
+ 'Calendar'
],
stores: [
@@ -70,7 +71,9 @@ Ext.define('Traccar.Application', {
'ReportTypes',
'ReportEventTypes',
'Statistics',
- 'DeviceImages'
+ 'DeviceImages',
+ 'Calendars',
+ 'AllCalendars'
],
controllers: [
@@ -81,6 +84,11 @@ Ext.define('Traccar.Application', {
return window.matchMedia && window.matchMedia('(max-width: 768px)').matches;
},
+ getEventString: function (eventType) {
+ var key = 'event' + eventType.charAt(0).toUpperCase() + eventType.slice(1);
+ return Strings[key] || key;
+ },
+
showReports: function (show) {
var rootPanel = Ext.getCmp('rootPanel');
if (rootPanel) {
diff --git a/web/app/DeviceImages.js b/web/app/DeviceImages.js
index a05a8153..b31f3ed3 100644
--- a/web/app/DeviceImages.js
+++ b/web/app/DeviceImages.js
@@ -98,15 +98,8 @@ Ext.define('Traccar.DeviceImages', {
},
rotateImageIcon: function (image, angle) {
- var svg = Traccar.DeviceImages.getImageSvg(image.fill, image.zoom, angle, image.category);
+ var svg = this.getImageSvg(image.fill, image.zoom, angle, image.category);
image.getImage().src = this.formatSrc(svg);
image.angle = angle;
- },
-
- changeImageColor: function (image, color, category) {
- var svg = Traccar.DeviceImages.getImageSvg(color, image.zoom, image.angle, category);
- image.getImage().src = this.formatSrc(svg);
- image.fill = color;
- image.category = category;
}
});
diff --git a/web/app/GeofenceConverter.js b/web/app/GeofenceConverter.js
index f0e28b3f..9e3c1327 100644
--- a/web/app/GeofenceConverter.js
+++ b/web/app/GeofenceConverter.js
@@ -49,6 +49,20 @@ Ext.define('Traccar.GeofenceConverter', {
geometry = new ol.geom.Circle(center, radius);
}
}
+ } else if (wkt.lastIndexOf('LINESTRING', 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.LineString(points);
+ }
+ }
}
return geometry;
},
@@ -74,6 +88,14 @@ Ext.define('Traccar.GeofenceConverter', {
result += points[0][i][1] + ' ' + points[0][i][0] + ', ';
}
result = result.substring(0, result.length - 2) + '))';
+ } else if (geometry instanceof ol.geom.LineString) {
+ geometry.transform(projection, 'EPSG:4326');
+ points = geometry.getCoordinates();
+ result = 'LINESTRING (';
+ for (i = 0; i < points.length; i += 1) {
+ result += points[i][1] + ' ' + points[i][0] + ', ';
+ }
+ result = result.substring(0, result.length - 2) + ')';
}
return result;
}
diff --git a/web/app/Style.js b/web/app/Style.js
index 4960aefa..c5fce9b6 100644
--- a/web/app/Style.js
+++ b/web/app/Style.js
@@ -18,6 +18,8 @@
Ext.define('Traccar.Style', {
singleton: true,
+ reconnectTimeout: 60 * 1000,
+
normalPadding: 10,
windowWidth: 640,
@@ -76,7 +78,5 @@ Ext.define('Traccar.Style', {
coordinatePrecision: 6,
numberPrecision: 2,
- reportTagfieldWidth: 375,
-
- headerButtonsMargin: '0 5'
+ reportTagfieldWidth: 375
});
diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js
index e30446c9..0cc2a148 100644
--- a/web/app/controller/Root.js
+++ b/web/app/controller/Root.js
@@ -75,7 +75,7 @@ Ext.define('Traccar.controller.Root', {
},
loadApp: function () {
- var attribution;
+ var attribution, eventId;
Ext.getStore('Groups').load();
Ext.getStore('Geofences').load();
Ext.getStore('AttributeAliases').load();
@@ -94,6 +94,11 @@ Ext.define('Traccar.controller.Root', {
} else {
Ext.create('widget.main');
}
+ eventId = Ext.Object.fromQueryString(window.location.search).eventId;
+ if (eventId) {
+ this.fireEvent('showsingleevent', eventId);
+ this.removeUrlParameter('eventId');
+ }
},
beep: function () {
@@ -108,103 +113,117 @@ Ext.define('Traccar.controller.Root', {
return muteButton && !muteButton.pressed;
},
+ removeUrlParameter: function (param) {
+ var params = Ext.Object.fromQueryString(window.location.search);
+ delete params[param];
+ if (Ext.Object.isEmpty(params)) {
+ window.history.pushState(null, null, window.location.pathname);
+ } else {
+ window.history.pushState(null, null, window.location.pathname + '?' + Ext.Object.toQueryString(params));
+ }
+ },
+
asyncUpdate: function (first) {
- var protocol, pathname, socket, self = this;
+ var self = this, protocol, pathname, socket;
protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
pathname = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
socket = new WebSocket(protocol + '//' + window.location.host + pathname + 'api/socket');
socket.onclose = function (event) {
- self.asyncUpdate(false);
+ Ext.toast(Strings.errorSocket, Strings.errorTitle, 'br');
+
+ Ext.Ajax.request({
+ url: 'api/devices',
+ success: function(response) {
+ self.updateDevices(Ext.decode(response.responseText));
+ }
+ });
+
+ Ext.Ajax.request({
+ url: 'api/positions',
+ headers: {
+ Accept: 'application/json'
+ },
+ success: function(response) {
+ self.updatePositions(Ext.decode(response.responseText));
+ }
+ });
+
+ setTimeout(function() {
+ self.asyncUpdate(false);
+ }, Traccar.Style.reconnectTimeout);
};
socket.onmessage = function (event) {
- var i, j, store, data, array, entity, device, typeKey, alarmKey, text, geofence;
-
- data = Ext.decode(event.data);
+ var 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
- });
- }
- }
+ self.updateDevices(data.devices);
+ }
+ if (data.positions) {
+ self.updatePositions(data.positions);
}
+ if (data.events) {
+ self.updateEvents(data.events);
+ }
+ };
+ },
- 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]));
- }
- }
+ updateDevices: function (array) {
+ var i, store, entity;
+ 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.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'));
- }
+ updatePositions: function (array) {
+ var i, store, data, entity;
+ 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]));
+ }
+ }
+ },
+
+ updateEvents: function (array) {
+ var i, store, device, alarmKey, text, geofence;
+ store = Ext.getStore('Events');
+ for (i = 0; i < array.length; i++) {
+ store.add(array[i]);
+ if (array[i].type === 'commandResult') {
+ text = Strings.eventCommandResult + ': ' + array[i].attributes.result;
+ } else if (array[i].type === 'alarm') {
+ alarmKey = 'alarm' + array[i].attributes.alarm.charAt(0).toUpperCase() + array[i].attributes.alarm.slice(1);
+ text = Strings[alarmKey] || alarmKey;
+ } else {
+ text = Traccar.app.getEventString(array[i].type);
+ }
+ if (array[i].geofenceId !== 0) {
+ geofence = Ext.getStore('Geofences').getById(array[i].geofenceId);
+ if (geofence) {
+ text += ' \"' + geofence.get('name') + '"';
}
}
- };
+ device = Ext.getStore('Devices').getById(array[i].deviceId);
+ if (device) {
+ if (this.mutePressed()) {
+ this.beep();
+ }
+ Ext.toast(text, device.get('name'), 'br');
+ }
+ }
}
});
diff --git a/web/app/model/Calendar.js b/web/app/model/Calendar.js
new file mode 100644
index 00000000..00b076b3
--- /dev/null
+++ b/web/app/model/Calendar.js
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.model.Calendar', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'calendarData'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Geofence.js b/web/app/model/Geofence.js
index 63c8e8e2..12a9f878 100644
--- a/web/app/model/Geofence.js
+++ b/web/app/model/Geofence.js
@@ -32,6 +32,9 @@ Ext.define('Traccar.model.Geofence', {
name: 'area',
type: 'string'
}, {
+ name: 'calendarId',
+ type: 'int'
+ }, {
name: 'attributes'
}]
});
diff --git a/web/app/model/Notification.js b/web/app/model/Notification.js
index 1e6c36c5..54f6674c 100644
--- a/web/app/model/Notification.js
+++ b/web/app/model/Notification.js
@@ -17,7 +17,7 @@
Ext.define('Traccar.model.Notification', {
extend: 'Ext.data.Model',
- identifier: 'negative',
+ idProperty: 'type',
fields: [{
name: 'id',
@@ -30,5 +30,11 @@ Ext.define('Traccar.model.Notification', {
type: 'int'
}, {
name: 'attributes'
+ }, {
+ name: 'web',
+ type: 'bool'
+ }, {
+ name: 'mail',
+ type: 'bool'
}]
});
diff --git a/web/app/store/AllCalendars.js b/web/app/store/AllCalendars.js
new file mode 100644
index 00000000..26557287
--- /dev/null
+++ b/web/app/store/AllCalendars.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.store.AllCalendars', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Calendar',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/calendars',
+ extraParams: {
+ all: true
+ }
+ }
+});
diff --git a/web/app/store/Calendars.js b/web/app/store/Calendars.js
new file mode 100644
index 00000000..fa8e5c66
--- /dev/null
+++ b/web/app/store/Calendars.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.store.Calendars', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Calendar',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/calendars',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/GeofenceTypes.js b/web/app/store/GeofenceTypes.js
index c102de69..45b79897 100644
--- a/web/app/store/GeofenceTypes.js
+++ b/web/app/store/GeofenceTypes.js
@@ -25,5 +25,8 @@ Ext.define('Traccar.store.GeofenceTypes', {
}, {
key: 'Circle',
name: Strings.mapShapeCircle
+ }, {
+ key: 'LineString',
+ name: Strings.mapShapePolyline
}]
});
diff --git a/web/app/store/Notifications.js b/web/app/store/Notifications.js
index e4d2991a..d79702fc 100644
--- a/web/app/store/Notifications.js
+++ b/web/app/store/Notifications.js
@@ -22,5 +22,7 @@ Ext.define('Traccar.store.Notifications', {
proxy: {
type: 'rest',
url: 'api/users/notifications'
- }
+ },
+ sortOnLoad: true,
+ sorters: { property: 'type', direction : 'ASC' }
});
diff --git a/web/app/store/Positions.js b/web/app/store/Positions.js
index 8f185af1..388a3320 100644
--- a/web/app/store/Positions.js
+++ b/web/app/store/Positions.js
@@ -21,6 +21,9 @@ Ext.define('Traccar.store.Positions', {
proxy: {
type: 'rest',
- url: 'api/positions'
+ url: 'api/positions',
+ headers: {
+ 'Accept': 'application/json'
+ }
}
});
diff --git a/web/app/view/CalendarDialog.js b/web/app/view/CalendarDialog.js
new file mode 100644
index 00000000..2609a6da
--- /dev/null
+++ b/web/app/view/CalendarDialog.js
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.view.CalendarDialog', {
+ extend: 'Traccar.view.BaseEditDialog',
+
+ requires: [
+ 'Traccar.view.CalendarDialogController'
+ ],
+
+ controller: 'calendarDialog',
+ title: Strings.sharedCalendar,
+
+ items: {
+ xtype: 'form',
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: Strings.sharedName,
+ allowBlank: false
+ }, {
+ xtype: 'filefield',
+ name: 'file',
+ fieldLabel: Strings.sharedFile,
+ allowBlank: false,
+ buttonConfig: {
+ glyph: 'xf093@FontAwesome',
+ text: '',
+ tooltip: Strings.sharedSelectFile,
+ tooltipType: 'title',
+ minWidth: 0
+ },
+ listeners: {
+ change: 'onFileChange'
+ }
+ }, {
+ xtype: 'hiddenfield',
+ name: 'calendarData',
+ allowBlank: false,
+ reference: 'calendarDataField'
+ }]
+ }
+});
diff --git a/web/app/view/CalendarDialogController.js b/web/app/view/CalendarDialogController.js
new file mode 100644
index 00000000..48400bc5
--- /dev/null
+++ b/web/app/view/CalendarDialogController.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.view.CalendarDialogController', {
+ extend: 'Traccar.view.BaseEditDialogController',
+ alias: 'controller.calendarDialog',
+
+ onFileChange: function (fileField) {
+ var reader;
+ if (fileField.fileInputEl.dom.files.length > 0) {
+ reader = new FileReader();
+ reader.onload = function (event) {
+ fileField.up('window').lookupReference('calendarDataField').setValue(
+ btoa(String.fromCharCode.apply(null, new Uint8Array(event.target.result))));
+ };
+ reader.onerror = function (event) {
+ Traccar.app.showError(event.target.error);
+ };
+ reader.readAsArrayBuffer(fileField.fileInputEl.dom.files[0]);
+ }
+ }
+});
diff --git a/web/app/view/Calendars.js b/web/app/view/Calendars.js
new file mode 100644
index 00000000..a905a5ba
--- /dev/null
+++ b/web/app/view/Calendars.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.view.Calendars', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'calendarsView',
+
+ requires: [
+ 'Traccar.view.CalendarsController',
+ 'Traccar.view.EditToolbar'
+ ],
+
+ controller: 'calendars',
+ store: 'Calendars',
+
+ selType: 'rowmodel',
+
+ tbar: {
+ xtype: 'editToolbar'
+ },
+
+ listeners: {
+ selectionchange: 'onSelectionChange'
+ },
+
+ forceFit: true,
+
+ columns: {
+ defaults: {
+ minWidth: Traccar.Style.columnWidthNormal
+ },
+ items: [{
+ text: Strings.sharedName,
+ dataIndex: 'name'
+ }]
+ }
+});
diff --git a/web/app/view/CalendarsController.js b/web/app/view/CalendarsController.js
new file mode 100644
index 00000000..d5ab57a5
--- /dev/null
+++ b/web/app/view/CalendarsController.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.view.CalendarsController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.calendars',
+
+ requires: [
+ 'Traccar.view.CalendarDialog',
+ 'Traccar.model.Calendar'
+ ],
+
+ init: function () {
+ Ext.getStore('Calendars').load();
+ },
+
+ onAddClick: function () {
+ var calendar, dialog;
+ calendar = Ext.create('Traccar.model.Calendar');
+ calendar.store = this.getView().getStore();
+ dialog = Ext.create('Traccar.view.CalendarDialog');
+ dialog.down('form').loadRecord(calendar);
+ dialog.show();
+ },
+
+ onEditClick: function () {
+ var calendar, dialog;
+ calendar = this.getView().getSelectionModel().getSelection()[0];
+ dialog = Ext.create('Traccar.view.CalendarDialog');
+ dialog.down('form').loadRecord(calendar);
+ dialog.show();
+ },
+
+ onRemoveClick: function () {
+ var calendar = this.getView().getSelectionModel().getSelection()[0];
+ Ext.Msg.show({
+ title: Strings.sharedCalendar,
+ message: Strings.sharedRemoveConfirm,
+ buttons: Ext.Msg.YESNO,
+ buttonText: {
+ yes: Strings.sharedRemove,
+ no: Strings.sharedCancel
+ },
+ fn: function (btn) {
+ var store = Ext.getStore('Calendars');
+ if (btn === 'yes') {
+ store.remove(calendar);
+ 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/GeofenceDialog.js b/web/app/view/GeofenceDialog.js
index 9f5bdbf7..7b2112b3 100644
--- a/web/app/view/GeofenceDialog.js
+++ b/web/app/view/GeofenceDialog.js
@@ -36,6 +36,14 @@ Ext.define('Traccar.view.GeofenceDialog', {
name: 'description',
fieldLabel: Strings.sharedDescription
}, {
+ xtype: 'combobox',
+ name: 'calendarId',
+ store: 'Calendars',
+ queryMode: 'local',
+ displayField: 'name',
+ valueField: 'id',
+ fieldLabel: Strings.sharedCalendar
+ }, {
xtype: 'hiddenfield',
name: 'area',
allowBlank: false,
diff --git a/web/app/view/GeofencesController.js b/web/app/view/GeofencesController.js
index 032ba7fa..78389066 100644
--- a/web/app/view/GeofencesController.js
+++ b/web/app/view/GeofencesController.js
@@ -26,6 +26,7 @@ Ext.define('Traccar.view.GeofencesController', {
init: function () {
Ext.getStore('Geofences').load();
+ Ext.getStore('Calendars').load();
},
onAddClick: function () {
diff --git a/web/app/view/Login.js b/web/app/view/Login.js
index d71fed8b..5d0da0f5 100644
--- a/web/app/view/Login.js
+++ b/web/app/view/Login.js
@@ -25,7 +25,7 @@ Ext.define('Traccar.view.Login', {
controller: 'login',
- title: Strings.loginTitle,
+ header: false,
closable: false,
modal: false,
@@ -41,6 +41,16 @@ Ext.define('Traccar.view.Login', {
},
items: [{
+ xtype: 'image',
+ src: 'logo.svg',
+ alt: Strings.loginLogo,
+ width: 180,
+ height: 48,
+ style: {
+ display: 'block',
+ margin: '10px auto 25px'
+ }
+ }, {
xtype: 'combobox',
name: 'language',
fieldLabel: Strings.loginLanguage,
@@ -79,6 +89,8 @@ Ext.define('Traccar.view.Login', {
inputAttrTpl: ['autocomplete="on"']
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
reference: 'rememberField',
fieldLabel: Strings.userRemember
}, {
diff --git a/web/app/view/MapController.js b/web/app/view/MapController.js
index 9fcd4939..c52fdd5f 100644
--- a/web/app/view/MapController.js
+++ b/web/app/view/MapController.js
@@ -16,59 +16,33 @@
*/
Ext.define('Traccar.view.MapController', {
- extend: 'Ext.app.ViewController',
+ extend: 'Traccar.view.MapMarkerController',
alias: 'controller.map',
requires: [
- 'Traccar.model.Position',
- 'Traccar.model.Device',
- 'Traccar.GeofenceConverter',
- 'Traccar.DeviceImages'
+ 'Traccar.GeofenceConverter'
],
config: {
listen: {
controller: {
'*': {
- selectdevice: 'selectDevice',
- selectreport: 'selectReport',
mapstaterequest: 'getMapState'
}
},
store: {
- '#Devices': {
- add: 'updateDevice',
- update: 'updateDevice',
- remove: 'removeDevice'
- },
- '#LatestPositions': {
- add: 'updateLatest',
- update: 'updateLatest'
- },
- '#ReportRoute': {
- load: 'loadReport',
- clear: 'clearReport'
- },
'#Geofences': {
load: 'showGeofences',
add: 'updateGeofences',
update: 'updateGeofences',
remove: 'updateGeofences'
}
- },
- component: {
- '#': {
- selectfeature: 'selectFeature'
- }
}
}
},
init: function () {
- this.latestMarkers = {};
- this.reportMarkers = {};
- this.liveRoutes = {};
- this.liveRouteLength = Traccar.app.getAttributePreference('web.liveRouteLength', 10);
+ this.callParent();
this.lookupReference('showReportsButton').setVisible(Traccar.app.isMobile());
},
@@ -76,58 +50,6 @@ Ext.define('Traccar.view.MapController', {
Traccar.app.showReports(true);
},
- getDeviceColor: function (device) {
- switch (device.get('status')) {
- case 'online':
- return Traccar.Style.mapColorOnline;
- case 'offline':
- return Traccar.Style.mapColorOffline;
- default:
- return Traccar.Style.mapColorUnknown;
- }
- },
-
- updateDevice: function (store, data) {
- var i, device, deviceId, marker, style;
-
- 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];
- style = marker.getStyle();
- if (style.getImage().fill !== this.getDeviceColor(device) ||
- style.getImage().category !== device.get('category')) {
- Traccar.DeviceImages.changeImageColor(style.getImage(),
- this.getDeviceColor(device), device.get('category'));
- marker.changed();
- }
- if (style.getText().getText() !== device.get('name')) {
- style.getText().setText(device.get('name'));
- marker.changed();
- }
- }
- }
- },
-
- removeDevice: function (store, data) {
- var i, deviceId;
- if (!Ext.isArray(data)) {
- data = [data];
- }
- for (i = 0; i < data.length; i++) {
- deviceId = data[i].get('id');
- if (this.latestMarkers[deviceId]) {
- this.getView().getLatestSource().removeFeature(this.latestMarkers[deviceId]);
- }
- }
- },
-
onFollowClick: function (button, pressed) {
if (pressed && this.selectedMarker) {
this.getView().getMapView().setCenter(this.selectedMarker.getGeometry().getCoordinates());
@@ -138,245 +60,6 @@ Ext.define('Traccar.view.MapController', {
this.getView().getLiveRouteLayer().setVisible(button.pressed);
},
- updateLatest: function (store, data) {
- var i, position, device;
-
- if (!Ext.isArray(data)) {
- data = [data];
- }
-
- for (i = 0; i < data.length; i++) {
- position = data[i];
- device = Ext.getStore('Devices').findRecord('id', position.get('deviceId'), 0, false, false, true);
-
- if (device) {
- this.updateLatestMarker(position, device);
- this.updateLiveRoute(position);
- }
- }
- },
-
- updateLatestMarker: function (position, device) {
- var geometry, deviceId, marker, style;
- geometry = new ol.geom.Point(ol.proj.fromLonLat([
- position.get('longitude'),
- position.get('latitude')
- ]));
- deviceId = position.get('deviceId');
- if (deviceId in this.latestMarkers) {
- marker = this.latestMarkers[deviceId];
- style = marker.getStyle();
- if (style.getImage().angle !== position.get('course')) {
- Traccar.DeviceImages.rotateImageIcon(style.getImage(), position.get('course'));
- }
- marker.setGeometry(geometry);
- } else {
- marker = new ol.Feature(geometry);
- marker.set('record', device);
-
- style = this.getLatestMarker(this.getDeviceColor(device),
- position.get('course'),
- device.get('category'));
- style.getText().setText(device.get('name'));
- marker.setStyle(style);
- this.latestMarkers[deviceId] = marker;
- this.getView().getLatestSource().addFeature(marker);
-
- }
-
- if (marker === this.selectedMarker && this.lookupReference('deviceFollowButton').pressed) {
- this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates());
- }
- },
-
- updateLiveRoute: function (position) {
- var deviceId, liveLine, liveCoordinates, lastLiveCoordinates, newCoordinates;
- deviceId = position.get('deviceId');
- if (deviceId in this.liveRoutes) {
- liveCoordinates = this.liveRoutes[deviceId].getGeometry().getCoordinates();
- lastLiveCoordinates = liveCoordinates[liveCoordinates.length - 1];
- newCoordinates = ol.proj.fromLonLat([position.get('longitude'), position.get('latitude')]);
- if (lastLiveCoordinates[0] === newCoordinates[0] &&
- lastLiveCoordinates[1] === newCoordinates[1]) {
- return;
- }
- if (liveCoordinates.length >= this.liveRouteLength) {
- liveCoordinates.shift();
- }
- liveCoordinates.push(newCoordinates);
- this.liveRoutes[deviceId].getGeometry().setCoordinates(liveCoordinates);
- } else {
- liveLine = new ol.Feature({
- geometry: new ol.geom.LineString([
- ol.proj.fromLonLat([
- position.get('longitude'),
- position.get('latitude')
- ])
- ])
- });
- liveLine.setStyle(this.getRouteStyle(deviceId));
- this.liveRoutes[deviceId] = liveLine;
- this.getView().getLiveRouteSource().addFeature(liveLine);
- }
- },
-
- 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'), position.get('course'));
- /*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 (zoom, color, angle, category) {
- var image = Traccar.DeviceImages.getImageIcon(color, zoom, angle, category);
- return new ol.style.Style({
- image: image,
- 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: -image.getSize()[1] / 2 - Traccar.Style.mapTextOffset,
- font : Traccar.Style.mapTextFont
- })
- });
- },
-
- getLatestMarker: function (color, angle, category) {
- return this.getMarkerStyle(false, color, angle, category);
- },
-
- getReportMarker: function (deviceId, angle) {
- var index = 0;
- if (deviceId !== undefined) {
- index = deviceId % Traccar.Style.mapRouteColor.length;
- }
- return this.getMarkerStyle(false, Traccar.Style.mapRouteColor[index], angle, 'arrow');
- },
-
- resizeMarker: function (style, zoom) {
- var image, text;
- image = Traccar.DeviceImages.getImageIcon(style.getImage().fill,
- zoom,
- style.getImage().angle,
- style.getImage().category);
- text = style.getText();
- text.setOffsetY(-image.getSize()[1] / 2 - Traccar.Style.mapTextOffset);
- return new ol.style.Style({
- image: image,
- text: text
- });
- },
-
- selectMarker: function (marker, center) {
- if (this.selectedMarker) {
- this.selectedMarker.setStyle(
- this.resizeMarker(this.selectedMarker.getStyle(), false));
- }
-
- if (marker) {
- marker.setStyle(
- this.resizeMarker(marker.getStyle(), true));
- 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) {
- if (position instanceof Traccar.model.Position) {
- 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();
diff --git a/web/app/view/MapMarkerController.js b/web/app/view/MapMarkerController.js
new file mode 100644
index 00000000..5fa9f4ca
--- /dev/null
+++ b/web/app/view/MapMarkerController.js
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.view.MapMarkerController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.mapMarker',
+
+ requires: [
+ 'Traccar.model.Position',
+ 'Traccar.model.Device',
+ 'Traccar.DeviceImages'
+ ],
+
+ config: {
+ listen: {
+ controller: {
+ '*': {
+ selectdevice: 'selectDevice',
+ selectreport: 'selectReport'
+ }
+ },
+ store: {
+ '#Devices': {
+ add: 'updateDevice',
+ update: 'updateDevice',
+ remove: 'removeDevice'
+ },
+ '#LatestPositions': {
+ add: 'updateLatest',
+ update: 'updateLatest'
+ },
+ '#ReportRoute': {
+ add: 'addReportMarkers',
+ load: 'loadReport',
+ clear: 'clearReport'
+ }
+ },
+ component: {
+ '#': {
+ selectfeature: 'selectFeature'
+ }
+ }
+ }
+ },
+
+ init: function () {
+ this.latestMarkers = {};
+ this.reportMarkers = {};
+ this.liveRoutes = {};
+ this.liveRouteLength = Traccar.app.getAttributePreference('web.liveRouteLength', 10);
+ },
+
+ getDeviceColor: function (device) {
+ switch (device.get('status')) {
+ case 'online':
+ return Traccar.Style.mapColorOnline;
+ case 'offline':
+ return Traccar.Style.mapColorOffline;
+ default:
+ return Traccar.Style.mapColorUnknown;
+ }
+ },
+
+ updateDevice: function (store, data) {
+ var i, device, deviceId, marker, style;
+
+ 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];
+ style = marker.getStyle();
+ if (style.getImage().fill !== this.getDeviceColor(device) ||
+ style.getImage().category !== device.get('category')) {
+ marker.setStyle(this.updateDeviceMarker(style, this.getDeviceColor(device), device.get('category')));
+ }
+ if (style.getText().getText() !== device.get('name')) {
+ style.getText().setText(device.get('name'));
+ marker.changed();
+ }
+ }
+ }
+ },
+
+ removeDevice: function (store, data) {
+ var i, deviceId;
+ if (!Ext.isArray(data)) {
+ data = [data];
+ }
+ for (i = 0; i < data.length; i++) {
+ deviceId = data[i].get('id');
+ if (this.latestMarkers[deviceId]) {
+ this.getView().getLatestSource().removeFeature(this.latestMarkers[deviceId]);
+ }
+ }
+ },
+
+ updateLatest: function (store, data) {
+ var i, position, device;
+
+ if (!Ext.isArray(data)) {
+ data = [data];
+ }
+
+ for (i = 0; i < data.length; i++) {
+ position = data[i];
+ device = Ext.getStore('Devices').findRecord('id', position.get('deviceId'), 0, false, false, true);
+
+ if (device) {
+ this.updateLatestMarker(position, device);
+ this.updateLiveRoute(position);
+ }
+ }
+ },
+
+ updateLatestMarker: function (position, device) {
+ var geometry, deviceId, marker, style;
+ geometry = new ol.geom.Point(ol.proj.fromLonLat([
+ position.get('longitude'),
+ position.get('latitude')
+ ]));
+ deviceId = position.get('deviceId');
+ if (deviceId in this.latestMarkers) {
+ marker = this.latestMarkers[deviceId];
+ style = marker.getStyle();
+ if (style.getImage().angle !== position.get('course')) {
+ Traccar.DeviceImages.rotateImageIcon(style.getImage(), position.get('course'));
+ }
+ marker.setGeometry(geometry);
+ } else {
+ marker = new ol.Feature(geometry);
+ marker.set('record', device);
+
+ style = this.getLatestMarker(this.getDeviceColor(device),
+ position.get('course'),
+ device.get('category'));
+ style.getText().setText(device.get('name'));
+ marker.setStyle(style);
+ this.latestMarkers[deviceId] = marker;
+ this.getView().getLatestSource().addFeature(marker);
+
+ }
+
+ if (marker === this.selectedMarker && this.lookupReference('deviceFollowButton').pressed) {
+ this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates());
+ }
+ },
+
+ updateLiveRoute: function (position) {
+ var deviceId, liveLine, liveCoordinates, lastLiveCoordinates, newCoordinates;
+ deviceId = position.get('deviceId');
+ if (deviceId in this.liveRoutes) {
+ liveCoordinates = this.liveRoutes[deviceId].getGeometry().getCoordinates();
+ lastLiveCoordinates = liveCoordinates[liveCoordinates.length - 1];
+ newCoordinates = ol.proj.fromLonLat([position.get('longitude'), position.get('latitude')]);
+ if (lastLiveCoordinates[0] === newCoordinates[0] &&
+ lastLiveCoordinates[1] === newCoordinates[1]) {
+ return;
+ }
+ if (liveCoordinates.length >= this.liveRouteLength) {
+ liveCoordinates.shift();
+ }
+ liveCoordinates.push(newCoordinates);
+ this.liveRoutes[deviceId].getGeometry().setCoordinates(liveCoordinates);
+ } else {
+ liveLine = new ol.Feature({
+ geometry: new ol.geom.LineString([
+ ol.proj.fromLonLat([
+ position.get('longitude'),
+ position.get('latitude')
+ ])
+ ])
+ });
+ liveLine.setStyle(this.getRouteStyle(deviceId));
+ this.liveRoutes[deviceId] = liveLine;
+ this.getView().getLiveRouteSource().addFeature(liveLine);
+ }
+ },
+
+ loadReport: function (store, data) {
+ var i, position, point;
+
+ this.addReportMarkers(store, data);
+
+ this.reportRoute = [];
+ for (i = 0; i < data.length; i++) {
+ position = data[i];
+ point = ol.proj.fromLonLat([
+ position.get('longitude'),
+ position.get('latitude')
+ ]);
+ 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]);
+ }
+ this.reportRoute[this.reportRoute.length - 1].getGeometry().appendCoordinate(point);
+ }
+ },
+
+ addReportMarkers: function (store, data) {
+ var i, position, point, geometry, marker, style, minx, miny, maxx, maxy;
+ this.clearReport();
+ for (i = 0; i < data.length; i++) {
+ position = data[i];
+ point = ol.proj.fromLonLat([
+ position.get('longitude'),
+ position.get('latitude')
+ ]);
+ if (i === 0) {
+ minx = maxx = point[0];
+ miny = maxy = point[1];
+ } else {
+ minx = Math.min(point[0], minx);
+ miny = Math.min(point[1], miny);
+ maxx = Math.max(point[0], maxx);
+ maxy = Math.max(point[1], maxy);
+ }
+ geometry = new ol.geom.Point(point);
+ marker = new ol.Feature(geometry);
+ marker.set('record', position);
+ style = this.getReportMarker(position.get('deviceId'), position.get('course'));
+ /*style.getText().setText(
+ Ext.Date.format(position.get('fixTime'), Traccar.Style.dateTimeFormat24));*/
+ marker.setStyle(style);
+ this.reportMarkers[position.get('id')] = marker;
+ this.getView().getReportSource().addFeature(marker);
+ }
+ if (minx !== maxx || miny !== maxy) {
+ this.getView().getMapView().fit([minx, miny, maxx, maxy], this.getView().getMap().getSize());
+ } else if (geometry) {
+ this.getView().getMapView().fit(geometry, this.getView().getMap().getSize());
+ }
+ },
+
+ clearReport: function () {
+ 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 (zoom, color, angle, category) {
+ var image = Traccar.DeviceImages.getImageIcon(color, zoom, angle, category);
+ return new ol.style.Style({
+ image: image,
+ 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: -image.getSize()[1] / 2 - Traccar.Style.mapTextOffset,
+ font : Traccar.Style.mapTextFont
+ })
+ });
+ },
+
+ getLatestMarker: function (color, angle, category) {
+ return this.getMarkerStyle(false, color, angle, category);
+ },
+
+ getReportMarker: function (deviceId, angle) {
+ var index = 0;
+ if (deviceId !== undefined) {
+ index = deviceId % Traccar.Style.mapRouteColor.length;
+ }
+ return this.getMarkerStyle(false, Traccar.Style.mapRouteColor[index], angle, 'arrow');
+ },
+
+ resizeMarker: function (style, zoom) {
+ var image, text;
+ image = Traccar.DeviceImages.getImageIcon(style.getImage().fill,
+ zoom,
+ style.getImage().angle,
+ style.getImage().category);
+ text = style.getText();
+ text.setOffsetY(-image.getSize()[1] / 2 - Traccar.Style.mapTextOffset);
+ return new ol.style.Style({
+ image: image,
+ text: text
+ });
+ },
+
+ updateDeviceMarker: function (style, color, category) {
+ var image, text;
+ image = Traccar.DeviceImages.getImageIcon(color,
+ style.getImage().zoom,
+ style.getImage().angle,
+ category);
+ text = style.getText();
+ text.setOffsetY(-image.getSize()[1] / 2 - Traccar.Style.mapTextOffset);
+ return new ol.style.Style({
+ image: image,
+ text: text
+ });
+ },
+
+ selectMarker: function (marker, center) {
+ if (this.selectedMarker) {
+ this.selectedMarker.setStyle(
+ this.resizeMarker(this.selectedMarker.getStyle(), false));
+ }
+
+ if (marker) {
+ marker.setStyle(
+ this.resizeMarker(marker.getStyle(), true));
+ 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) {
+ if (position instanceof Traccar.model.Position) {
+ 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);
+ }
+ }
+ }
+});
diff --git a/web/app/view/Notifications.js b/web/app/view/Notifications.js
index 900ebe3f..419d9616 100644
--- a/web/app/view/Notifications.js
+++ b/web/app/view/Notifications.js
@@ -24,7 +24,7 @@ Ext.define('Traccar.view.Notifications', {
],
controller: 'notificationsController',
- store: 'AllNotifications',
+ store: 'Notifications',
selModel: {
selType: 'cellmodel'
@@ -44,32 +44,21 @@ Ext.define('Traccar.view.Notifications', {
text: Strings.notificationType,
dataIndex: 'type',
renderer: function (value) {
- var typeKey = 'event' + value.charAt(0).toUpperCase() + value.slice(1);
- return Strings[typeKey];
+ return Traccar.app.getEventString(value);
}
}, {
text: Strings.notificationWeb,
- dataIndex: 'attributes.web',
+ dataIndex: 'web',
xtype: 'checkcolumn',
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',
+ dataIndex: 'mail',
xtype: 'checkcolumn',
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
index 70b99f1b..4c83b145 100644
--- a/web/app/view/NotificationsController.js
+++ b/web/app/view/NotificationsController.js
@@ -24,57 +24,19 @@ Ext.define('Traccar.view.NotificationsController', {
],
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();
- }
- }
- }
- });
+ params: {
+ userId: this.getView().user.getId()
}
});
},
- 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')
- },
+ jsonData: record.data,
callback: function (options, success, response) {
if (!success) {
Traccar.app.showError(response);
diff --git a/web/app/view/ReportConfigController.js b/web/app/view/ReportConfigController.js
index c121c654..0ae7c0a4 100644
--- a/web/app/view/ReportConfigController.js
+++ b/web/app/view/ReportConfigController.js
@@ -35,13 +35,11 @@ Ext.define('Traccar.view.ReportConfigController', {
Ext.create('Traccar.store.AllNotifications').load({
scope: this,
callback: function (records, operation, success) {
- var i, value, name, typeKey;
+ var i, value;
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});
+ store.add({type: value, name: Traccar.app.getEventString(value)});
}
}
}
diff --git a/web/app/view/ReportController.js b/web/app/view/ReportController.js
index 775394d0..1f3f3a2a 100644
--- a/web/app/view/ReportController.js
+++ b/web/app/view/ReportController.js
@@ -32,11 +32,18 @@ Ext.define('Traccar.view.ReportController', {
listen: {
controller: {
'*': {
- selectdevice: 'selectDevice'
+ selectdevice: 'selectDevice',
+ showsingleevent: 'showSingleEvent'
},
'map': {
selectreport: 'selectReport'
}
+ },
+ store: {
+ '#ReportEvents': {
+ add: 'loadEvents',
+ load: 'loadEvents'
+ }
}
}
},
@@ -112,8 +119,8 @@ Ext.define('Traccar.view.ReportController', {
deviceId: this.deviceId,
groupId: this.groupId,
type: this.eventType,
- from: from.toISOString(),
- to: to.toISOString()
+ from: Ext.Date.format(from, 'c'),
+ to: Ext.Date.format(to, 'c')
});
}
}
@@ -126,7 +133,7 @@ Ext.define('Traccar.view.ReportController', {
clearReport: function (reportType) {
this.getView().getStore().removeAll();
- if (reportType === 'trips') {
+ if (reportType === 'trips' || reportType === 'events') {
Ext.getStore('ReportRoute').removeAll();
}
},
@@ -139,6 +146,9 @@ Ext.define('Traccar.view.ReportController', {
if (report instanceof Traccar.model.ReportTrip) {
this.selectTrip(report);
}
+ if (report instanceof Traccar.model.Event) {
+ this.selectEvent(report);
+ }
}
},
@@ -149,10 +159,16 @@ Ext.define('Traccar.view.ReportController', {
},
selectReport: function (object, center) {
- var reportType = this.lookupReference('reportTypeField').getValue();
- if (object instanceof Traccar.model.Position && reportType === 'route') {
- this.getView().getSelectionModel().select([object], false, true);
- this.getView().getView().focusRow(object);
+ var positionEvent, reportType = this.lookupReference('reportTypeField').getValue();
+ if (object instanceof Traccar.model.Position) {
+ if (reportType === 'route') {
+ this.getView().getSelectionModel().select([object], false, true);
+ this.getView().getView().focusRow(object);
+ } else if (reportType === 'events') {
+ positionEvent = this.getView().getStore().findRecord('positionId', object.get('id'), 0, false, true, true);
+ this.getView().getSelectionModel().select([positionEvent], false, true);
+ this.getView().getView().focusRow(positionEvent);
+ }
}
},
@@ -170,6 +186,67 @@ Ext.define('Traccar.view.ReportController', {
});
},
+ selectEvent: function (event) {
+ var position;
+ if (event.get('positionId')) {
+ position = Ext.getStore('ReportRoute').getById(event.get('positionId'));
+ if (position) {
+ this.fireEvent('selectreport', position, true);
+ }
+ }
+ },
+
+ loadEvents: function (store, data) {
+ var i, eventObject, positionIds = [];
+ Ext.getStore('ReportRoute').removeAll();
+ for (i = 0; i < data.length; i++) {
+ eventObject = data[i];
+ if (eventObject.get('positionId')) {
+ positionIds.push(eventObject.get('positionId'));
+ }
+ }
+ if (positionIds.length > 0) {
+ Ext.getStore('Positions').load({
+ params: {
+ id: positionIds
+ },
+ scope: this,
+ callback: function (records, operation, success) {
+ if (success) {
+ Ext.getStore('ReportRoute').add(records);
+ if (records.length === 1) {
+ this.fireEvent('selectreport', records[0], false);
+ }
+ }
+ }
+ });
+ }
+ },
+
+ showSingleEvent: function (eventId) {
+ this.lookupReference('reportTypeField').setValue('events');
+ Ext.getStore('Events').load({
+ id: eventId,
+ scope: this,
+ callback: function (records, operation, success) {
+ if (success) {
+ Ext.getStore('ReportEvents').add(records);
+ if (records.length > 0) {
+ if (!records[0].get('positionId')) {
+ if (Traccar.app.isMobile()) {
+ Traccar.app.showReports(true);
+ } else {
+ this.getView().expand();
+ }
+ }
+ this.getView().getSelectionModel().select([records[0]], false, true);
+ this.getView().getView().focusRow(records[0]);
+ }
+ }
+ }
+ });
+ },
+
downloadFile: function (requestUrl, requestParams) {
Ext.Ajax.request({
url: requestUrl,
@@ -272,8 +349,7 @@ Ext.define('Traccar.view.ReportController', {
text: Strings.sharedType,
dataIndex: 'type',
renderer: function (value) {
- var typeKey = 'event' + value.charAt(0).toUpperCase() + value.slice(1);
- return Strings[typeKey];
+ return Traccar.app.getEventString(value);
}
}, {
text: Strings.sharedGeofence,
diff --git a/web/app/view/ServerDialog.js b/web/app/view/ServerDialog.js
index 0f965885..0a05876e 100644
--- a/web/app/view/ServerDialog.js
+++ b/web/app/view/ServerDialog.js
@@ -29,11 +29,15 @@ Ext.define('Traccar.view.ServerDialog', {
xtype: 'form',
items: [{
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'registration',
fieldLabel: Strings.serverRegistration,
allowBlank: false
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'readonly',
fieldLabel: Strings.serverReadonly,
allowBlank: false
@@ -88,11 +92,15 @@ Ext.define('Traccar.view.ServerDialog', {
fieldLabel: Strings.serverZoom
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'twelveHourFormat',
fieldLabel: Strings.settingsTwelveHourFormat,
allowBlank: false
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'forceSettings',
fieldLabel: Strings.serverForceSettings,
allowBlank: false
diff --git a/web/app/view/SettingsMenu.js b/web/app/view/SettingsMenu.js
index db436b33..c71c8372 100644
--- a/web/app/view/SettingsMenu.js
+++ b/web/app/view/SettingsMenu.js
@@ -82,6 +82,12 @@ Ext.define('Traccar.view.SettingsMenu', {
handler: 'onStatisticsClick',
reference: 'settingsStatisticsButton'
}, {
+ hidden: true,
+ text: Strings.sharedCalendars,
+ glyph: 'xf073@FontAwesome',
+ handler: 'onCalendarsClick',
+ reference: 'settingsCalendarsButton'
+ }, {
text: Strings.loginLogout,
glyph: 'xf08b@FontAwesome',
handler: 'onLogoutClick'
diff --git a/web/app/view/SettingsMenuController.js b/web/app/view/SettingsMenuController.js
index 0ec2a781..2f1685f0 100644
--- a/web/app/view/SettingsMenuController.js
+++ b/web/app/view/SettingsMenuController.js
@@ -30,6 +30,7 @@ Ext.define('Traccar.view.SettingsMenuController', {
'Traccar.view.AttributeAliases',
'Traccar.view.Statistics',
'Traccar.view.DeviceDistanceDialog',
+ 'Traccar.view.Calendars',
'Traccar.view.BaseWindow'
],
@@ -47,12 +48,14 @@ Ext.define('Traccar.view.SettingsMenuController', {
this.lookupReference('settingsGroupsButton').setHidden(false);
this.lookupReference('settingsGeofencesButton').setHidden(false);
this.lookupReference('settingsAttributeAliasesButton').setHidden(false);
+ this.lookupReference('settingsCalendarsButton').setHidden(false);
}
},
onUserClick: function () {
var dialog = Ext.create('Traccar.view.UserDialog');
dialog.down('form').loadRecord(Traccar.app.getUser());
+ dialog.lookupReference('testMailButton').setHidden(false);
dialog.show();
},
@@ -129,6 +132,16 @@ Ext.define('Traccar.view.SettingsMenuController', {
dialog.show();
},
+ onCalendarsClick: function () {
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedCalendars,
+ modal: false,
+ items: {
+ xtype: 'calendarsView'
+ }
+ }).show();
+ },
+
onLogoutClick: function () {
Ext.create('Traccar.view.LoginController').logout();
}
diff --git a/web/app/view/State.js b/web/app/view/State.js
index 3356fd72..2dc466f1 100644
--- a/web/app/view/State.js
+++ b/web/app/view/State.js
@@ -49,11 +49,10 @@ Ext.define('Traccar.view.State', {
selectionchange: 'onSelectionChange'
},
- forceFit: true,
-
columns: {
defaults: {
- minWidth: Traccar.Style.columnWidthNormal
+ minWidth: Traccar.Style.columnWidthNormal,
+ flex: 1
},
items: [{
text: Strings.stateName,
diff --git a/web/app/view/UserCalendars.js b/web/app/view/UserCalendars.js
new file mode 100644
index 00000000..29bb99cb
--- /dev/null
+++ b/web/app/view/UserCalendars.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+Ext.define('Traccar.view.UserCalendars', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'userCalendarsView',
+
+ requires: [
+ 'Traccar.view.BasePermissionsController'
+ ],
+
+ controller: 'basePermissionsController',
+
+ selModel: {
+ selType: 'checkboxmodel',
+ checkOnly: true,
+ showHeaderCheckbox: false
+ },
+
+ listeners: {
+ beforedeselect: 'onBeforeDeselect',
+ beforeselect: 'onBeforeSelect'
+ },
+
+ forceFit: true,
+
+ columns: {
+ items: [{
+ text: Strings.sharedName,
+ minWidth: Traccar.Style.columnWidthNormal,
+ dataIndex: 'name'
+ }]
+ }
+});
diff --git a/web/app/view/UserDialog.js b/web/app/view/UserDialog.js
index 52ec933c..8ee12437 100644
--- a/web/app/view/UserDialog.js
+++ b/web/app/view/UserDialog.js
@@ -44,6 +44,8 @@ Ext.define('Traccar.view.UserDialog', {
allowBlank: false
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'readonly',
fieldLabel: Strings.serverReadonly,
allowBlank: false,
@@ -51,6 +53,8 @@ Ext.define('Traccar.view.UserDialog', {
reference: 'readonlyField'
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'admin',
fieldLabel: Strings.userAdmin,
allowBlank: false,
@@ -99,6 +103,8 @@ Ext.define('Traccar.view.UserDialog', {
fieldLabel: Strings.serverZoom
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'twelveHourFormat',
fieldLabel: Strings.settingsTwelveHourFormat,
allowBlank: false
@@ -112,6 +118,8 @@ Ext.define('Traccar.view.UserDialog', {
editable: false
}, {
xtype: 'checkboxfield',
+ inputValue: true,
+ uncheckedValue: false,
name: 'disabled',
fieldLabel: Strings.userDisabled,
hidden: true,
@@ -155,6 +163,14 @@ Ext.define('Traccar.view.UserDialog', {
tooltip: Strings.sharedGetMapState,
tooltipType: 'title'
}, {
+ glyph: 'xf003@FontAwesome',
+ minWidth: 0,
+ handler: 'testMail',
+ hidden: true,
+ reference: 'testMailButton',
+ tooltip: Strings.sharedTestMail,
+ tooltipType: 'title'
+ }, {
xtype: 'tbfill'
}, {
glyph: 'xf00c@FontAwesome',
diff --git a/web/app/view/UserDialogController.js b/web/app/view/UserDialogController.js
index 0f1c022b..f07031e3 100644
--- a/web/app/view/UserDialogController.js
+++ b/web/app/view/UserDialogController.js
@@ -42,6 +42,16 @@ Ext.define('Traccar.view.UserDialogController', {
this.lookupReference('tokenField').setValue(newToken);
},
+ testMail: function () {
+ Ext.Ajax.request({
+ url: 'api/users/notifications/test',
+ method: 'POST',
+ failure: function (response) {
+ Traccar.app.showError(response);
+ }
+ });
+ },
+
onSaveClick: function (button) {
var dialog, record, store;
dialog = button.up('window').down('form');
diff --git a/web/app/view/Users.js b/web/app/view/Users.js
index 4259c4c1..09a03cc2 100644
--- a/web/app/view/Users.js
+++ b/web/app/view/Users.js
@@ -1,5 +1,6 @@
/*
- * Copyright 2015 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -59,6 +60,13 @@ Ext.define('Traccar.view.Users', {
glyph: 'xf003@FontAwesome',
tooltip: Strings.sharedNotifications,
tooltipType: 'title'
+ }, {
+ disabled: true,
+ handler: 'onCalendarsClick',
+ reference: 'userCalendarsButton',
+ glyph: 'xf073@FontAwesome',
+ tooltip: Strings.sharedCalendars,
+ tooltipType: 'title'
}]
},
diff --git a/web/app/view/UsersController.js b/web/app/view/UsersController.js
index 9b7076e6..af9d47b2 100644
--- a/web/app/view/UsersController.js
+++ b/web/app/view/UsersController.js
@@ -1,5 +1,6 @@
/*
- * Copyright 2015 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2016 Anton Tananaev (anton@traccar.org)
+ * Copyright 2016 Andrey Kunitsyn (andrey@traccar.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,6 +25,7 @@ Ext.define('Traccar.view.UsersController', {
'Traccar.view.UserDevices',
'Traccar.view.UserGroups',
'Traccar.view.UserGeofences',
+ 'Traccar.view.UserCalendars',
'Traccar.view.Notifications',
'Traccar.view.BaseWindow',
'Traccar.model.User'
@@ -129,6 +131,22 @@ Ext.define('Traccar.view.UsersController', {
}).show();
},
+ onCalendarsClick: function () {
+ var user = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.sharedCalendars,
+ items: {
+ xtype: 'userCalendarsView',
+ baseObjectName: 'userId',
+ linkObjectName: 'calendarId',
+ storeName: 'AllCalendars',
+ linkStoreName: 'Calendars',
+ urlApi: 'api/permissions/calendars',
+ baseObject: user.getId()
+ }
+ }).show();
+ },
+
onSelectionChange: function (selected) {
var disabled = selected.length > 0;
this.lookupReference('toolbarEditButton').setDisabled(disabled);
@@ -137,5 +155,6 @@ Ext.define('Traccar.view.UsersController', {
this.lookupReference('userGroupsButton').setDisabled(disabled);
this.lookupReference('userGeofencesButton').setDisabled(disabled);
this.lookupReference('userNotificationsButton').setDisabled(disabled);
+ this.lookupReference('userCalendarsButton').setDisabled(disabled);
}
});