diff options
author | Anton Tananaev <anton.tananaev@gmail.com> | 2016-09-18 11:40:46 +1200 |
---|---|---|
committer | Anton Tananaev <anton.tananaev@gmail.com> | 2016-09-18 11:40:46 +1200 |
commit | 2608adeff46f1286deff11a5320cee5647f6d41b (patch) | |
tree | a16014c04c755ec701b3237b0cccc7d8cc406960 /web/app/view | |
parent | d5ca160059220a07c71c95cc85696e0d66bf9c6e (diff) | |
download | trackermap-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')
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); + } +}); |