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