diff options
Diffstat (limited to 'web/app')
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); } }); |