aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/app.css4
-rw-r--r--web/app/Application.js3
-rw-r--r--web/app/controller/Root.js3
-rw-r--r--web/app/model/Device.js5
-rw-r--r--web/app/model/Group.js31
-rw-r--r--web/app/model/Position.js9
-rw-r--r--web/app/store/AllGroups.js28
-rw-r--r--web/app/store/Groups.js28
-rw-r--r--web/app/view/DeviceDialog.js9
-rw-r--r--web/app/view/Devices.js47
-rw-r--r--web/app/view/DevicesController.js66
-rw-r--r--web/app/view/GroupDialog.js43
-rw-r--r--web/app/view/Groups.js44
-rw-r--r--web/app/view/GroupsController.js63
-rw-r--r--web/app/view/MapController.js44
-rw-r--r--web/app/view/Register.js2
-rw-r--r--web/app/view/SettingsMenu.js3
-rw-r--r--web/app/view/SettingsMenuController.js11
-rw-r--r--web/app/view/UserDevices.js8
-rw-r--r--web/app/view/UserDevicesController.js4
-rw-r--r--web/app/view/UserDialog.js2
-rw-r--r--web/app/view/UserGroups.js44
-rw-r--r--web/app/view/UserGroupsController.js79
-rw-r--r--web/app/view/Users.js7
-rw-r--r--web/app/view/UsersController.js13
-rw-r--r--web/arrowstyle.js41
-rw-r--r--web/debug.html7
-rw-r--r--web/l10n/en.json6
-rw-r--r--web/l10n/es.json14
-rw-r--r--web/l10n/fa.json83
-rw-r--r--web/l10n/fi.json83
-rw-r--r--web/l10n/lt.json6
-rw-r--r--web/l10n/ml.json83
-rw-r--r--web/l10n/ms.json83
-rw-r--r--web/l10n/no.json2
-rw-r--r--web/l10n/tr.json83
-rw-r--r--web/locale.js8
-rw-r--r--web/release.html4
38 files changed, 1003 insertions, 100 deletions
diff --git a/web/app.css b/web/app.css
index eb0fdf136..0dbf94ca6 100644
--- a/web/app.css
+++ b/web/app.css
@@ -8,6 +8,10 @@
background-color: rgba(255, 84, 104, 0.3);
}
+.x-tree-icon {
+ display: none !important;
+}
+
.state-indicator {
position: absolute;
top: -999em;
diff --git a/web/app/Application.js b/web/app/Application.js
index fc4344a08..69ce8f891 100644
--- a/web/app/Application.js
+++ b/web/app/Application.js
@@ -26,6 +26,7 @@ Ext.define('Traccar.Application', {
models: [
'Server',
'User',
+ 'Group',
'Device',
'Position',
'Attribute',
@@ -33,7 +34,9 @@ Ext.define('Traccar.Application', {
],
stores: [
+ 'Groups',
'Devices',
+ 'AllGroups',
'AllDevices',
'Positions',
'LatestPositions',
diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js
index 23ca94972..5bd567619 100644
--- a/web/app/controller/Root.js
+++ b/web/app/controller/Root.js
@@ -73,6 +73,7 @@ Ext.define('Traccar.controller.Root', {
},
loadApp: function () {
+ Ext.getStore('Groups').load();
Ext.getStore('Devices').load();
Ext.get('attribution').remove();
if (this.isPhone) {
@@ -86,7 +87,7 @@ Ext.define('Traccar.controller.Root', {
asyncUpdate: function (first) {
var protocol, socket, self = this;
protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
- socket = new WebSocket(protocol + window.location.host + '/api/socket');
+ socket = new WebSocket(protocol + '//' + window.location.host + '/api/socket');
socket.onclose = function (event) {
self.asyncUpdate(false);
diff --git a/web/app/model/Device.js b/web/app/model/Device.js
index b8434d6ad..588d53c1f 100644
--- a/web/app/model/Device.js
+++ b/web/app/model/Device.js
@@ -33,6 +33,9 @@ Ext.define('Traccar.model.Device', {
}, {
name: 'lastUpdate',
type: 'date',
- dateWriteFormat: 'c'
+ dateFormat: 'c'
+ }, {
+ name: 'groupId',
+ type: 'int'
}]
});
diff --git a/web/app/model/Group.js b/web/app/model/Group.js
new file mode 100644
index 000000000..a28897feb
--- /dev/null
+++ b/web/app/model/Group.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.model.Group', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'groupId',
+ type: 'int'
+ }]
+});
diff --git a/web/app/model/Position.js b/web/app/model/Position.js
index 365b06115..e559a7eab 100644
--- a/web/app/model/Position.js
+++ b/web/app/model/Position.js
@@ -29,13 +29,16 @@ Ext.define('Traccar.model.Position', {
type: 'int'
}, {
name: 'serverTime',
- type: 'date'
+ type: 'date',
+ dateFormat: 'c'
}, {
name: 'deviceTime',
- type: 'date'
+ type: 'date',
+ dateFormat: 'c'
}, {
name: 'fixTime',
- type: 'date'
+ type: 'date',
+ dateFormat: 'c'
}, {
name: 'valid',
type: 'boolean'
diff --git a/web/app/store/AllGroups.js b/web/app/store/AllGroups.js
new file mode 100644
index 000000000..880ccc8f5
--- /dev/null
+++ b/web/app/store/AllGroups.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.AllGroups', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Group',
+
+ proxy: {
+ type: 'rest',
+ url: '/api/groups',
+ extraParams: {
+ all: true
+ }
+ }
+});
diff --git a/web/app/store/Groups.js b/web/app/store/Groups.js
new file mode 100644
index 000000000..938abed64
--- /dev/null
+++ b/web/app/store/Groups.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.store.Groups', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Group',
+
+ proxy: {
+ type: 'rest',
+ url: '/api/groups',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/view/DeviceDialog.js b/web/app/view/DeviceDialog.js
index c42af95d0..4a22ca008 100644
--- a/web/app/view/DeviceDialog.js
+++ b/web/app/view/DeviceDialog.js
@@ -29,13 +29,20 @@ Ext.define('Traccar.view.DeviceDialog', {
items: [{
xtype: 'textfield',
name: 'name',
- fieldLabel: Strings.deviceName,
+ fieldLabel: Strings.sharedName,
allowBlank: false
}, {
xtype: 'textfield',
name: 'uniqueId',
fieldLabel: Strings.deviceIdentifier,
allowBlank: false
+ }, {
+ xtype: 'combobox',
+ name: 'groupId',
+ fieldLabel: Strings.groupParent,
+ store: 'Groups',
+ displayField: 'name',
+ valueField: 'id'
}]
}
});
diff --git a/web/app/view/Devices.js b/web/app/view/Devices.js
index 66c4e813b..ebe3ca195 100644
--- a/web/app/view/Devices.js
+++ b/web/app/view/Devices.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * 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.
@@ -15,7 +15,7 @@
*/
Ext.define('Traccar.view.Devices', {
- extend: 'Ext.grid.Panel',
+ extend: 'Ext.tree.Panel',
xtype: 'devicesView',
requires: [
@@ -25,7 +25,17 @@ Ext.define('Traccar.view.Devices', {
],
controller: 'devices',
- store: 'Devices',
+ rootVisible: false,
+ store: {
+ type: 'tree',
+ parentIdProperty: 'groupId',
+ proxy: {
+ type: 'memory',
+ reader: {
+ type: 'json'
+ }
+ }
+ },
title: Strings.deviceTitle,
selType: 'rowmodel',
@@ -53,11 +63,13 @@ Ext.define('Traccar.view.Devices', {
},
listeners: {
- selectionchange: 'onSelectionChange'
+ selectionchange: 'onSelectionChange',
+ beforeselect: 'onBeforeSelect'
},
columns: [{
- text: Strings.deviceName,
+ xtype: 'treecolumn',
+ text: Strings.sharedName,
dataIndex: 'name',
flex: 1
}, {
@@ -65,19 +77,20 @@ Ext.define('Traccar.view.Devices', {
dataIndex: 'lastUpdate',
flex: 1,
renderer: function (value, metaData, record) {
- var status = record.get('status');
- switch (status) {
- case 'online':
- metaData.tdCls = 'status-color-online';
- break;
- case 'offline':
- metaData.tdCls = 'status-color-offline';
- break;
- default:
- metaData.tdCls = 'status-color-unknown';
- break;
+ if (record.get('leaf')) {
+ switch (record.get('status')) {
+ case 'online':
+ metaData.tdCls = 'status-color-online';
+ break;
+ case 'offline':
+ metaData.tdCls = 'status-color-offline';
+ break;
+ default:
+ metaData.tdCls = 'status-color-unknown';
+ break;
+ }
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat);
}
- return Ext.Date.format(value, Traccar.Style.dateTimeFormat);
}
}]
diff --git a/web/app/view/DevicesController.js b/web/app/view/DevicesController.js
index 8c6c72725..4d8231d94 100644
--- a/web/app/view/DevicesController.js
+++ b/web/app/view/DevicesController.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
+ * 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.
@@ -30,9 +30,54 @@ Ext.define('Traccar.view.DevicesController', {
selectDevice: 'selectDevice',
selectReport: 'selectReport'
}
+ },
+ store: {
+ '#Groups': {
+ datachanged: 'storeUpdate',
+ update: 'storeUpdate'
+ },
+ '#Devices': {
+ datachanged: 'storeUpdate',
+ update: 'storeUpdate'
+ }
}
}
},
+
+ storeUpdate: function () {
+ var nodes = [];
+ Ext.getStore('Groups').each(function (record) {
+ var groupId, node = {
+ id: 'g' + record.get('id'),
+ original: record,
+ name: record.get('name')
+ };
+ groupId = record.get('groupId');
+ if (groupId !== 0) {
+ node.groupId = 'g' + groupId;
+ }
+ nodes.push(node);
+ }, this);
+ Ext.getStore('Devices').each(function (record) {
+ var groupId, node = {
+ id: 'd' + record.get('id'),
+ original: record,
+ name: record.get('name'),
+ status: record.get('status'),
+ lastUpdate: record.get('lastUpdate'),
+ leaf: true
+ };
+ groupId = record.get('groupId');
+ if (groupId !== 0) {
+ node.groupId = 'g' + groupId;
+ }
+ nodes.push(node);
+ }, this);
+ this.getView().getStore().getProxy().setData(nodes);
+ this.getView().getStore().load();
+ this.getView().expandAll();
+ },
+
init: function () {
var readonly = Traccar.app.getServer().get('readonly') && !Traccar.app.getUser().get('admin');
this.lookupReference('toolbarAddButton').setVisible(!readonly);
@@ -43,7 +88,7 @@ Ext.define('Traccar.view.DevicesController', {
onAddClick: function () {
var device, dialog;
device = Ext.create('Traccar.model.Device');
- device.store = this.getView().getStore();
+ device.store = Ext.getStore('Devices');
dialog = Ext.create('Traccar.view.DeviceDialog');
dialog.down('form').loadRecord(device);
dialog.show();
@@ -51,14 +96,14 @@ Ext.define('Traccar.view.DevicesController', {
onEditClick: function () {
var device, dialog;
- device = this.getView().getSelectionModel().getSelection()[0];
+ device = this.getView().getSelectionModel().getSelection()[0].get('original');
dialog = Ext.create('Traccar.view.DeviceDialog');
dialog.down('form').loadRecord(device);
dialog.show();
},
onRemoveClick: function () {
- var device = this.getView().getSelectionModel().getSelection()[0];
+ var device = this.getView().getSelectionModel().getSelection()[0].get('original');
Ext.Msg.show({
title: Strings.deviceDialog,
message: Strings.sharedRemoveConfirm,
@@ -80,7 +125,7 @@ Ext.define('Traccar.view.DevicesController', {
onCommandClick: function () {
var device, command, dialog;
- device = this.getView().getSelectionModel().getSelection()[0];
+ device = this.getView().getSelectionModel().getSelection()[0].get('original');
command = Ext.create('Traccar.model.Command');
command.set('deviceId', device.get('id'));
dialog = Ext.create('Traccar.view.CommandDialog');
@@ -89,17 +134,22 @@ Ext.define('Traccar.view.DevicesController', {
},
onSelectionChange: function (selected) {
- var empty = selected.getCount() === 0;
+ var empty = selected.getCount() === 0 || !this.getView().getSelectionModel().getSelection()[0].get('leaf');
this.lookupReference('toolbarEditButton').setDisabled(empty);
this.lookupReference('toolbarRemoveButton').setDisabled(empty);
this.lookupReference('deviceCommandButton').setDisabled(empty);
if (!empty) {
- this.fireEvent('selectDevice', selected.getLastSelected(), true);
+ this.fireEvent('selectDevice', selected.getLastSelected().get('original'), true);
}
},
+ onBeforeSelect: function (row, record) {
+ return record.get('leaf');
+ },
+
selectDevice: function (device, center) {
- this.getView().getSelectionModel().select([device], false, true);
+ var node = this.getView().getStore().getNodeById('d' + device.get('id'));
+ this.getView().getSelectionModel().select([node], false, true);
},
selectReport: function (position) {
diff --git a/web/app/view/GroupDialog.js b/web/app/view/GroupDialog.js
new file mode 100644
index 000000000..2cca61ef5
--- /dev/null
+++ b/web/app/view/GroupDialog.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.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',
+ displayField: 'name',
+ valueField: 'id'
+ }]
+ }
+});
diff --git a/web/app/view/Groups.js b/web/app/view/Groups.js
new file mode 100644
index 000000000..8404c59a9
--- /dev/null
+++ b/web/app/view/Groups.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.Groups', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'groupsView',
+
+ requires: [
+ 'Traccar.view.GroupsController',
+ 'Traccar.view.EditToolbar'
+ ],
+
+ controller: 'groups',
+ store: 'Groups',
+
+ selType: 'rowmodel',
+
+ tbar: {
+ xtype: 'editToolbar'
+ },
+
+ 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 000000000..6cc568ea2
--- /dev/null
+++ b/web/app/view/GroupsController.js
@@ -0,0 +1,63 @@
+/*
+ * 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',
+
+ 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();
+ }
+ }
+ });
+ },
+
+ onSelectionChange: function (selected) {
+ var disabled = selected.length > 0;
+ this.lookupReference('toolbarEditButton').setDisabled(disabled);
+ this.lookupReference('toolbarRemoveButton').setDisabled(disabled);
+ }
+});
diff --git a/web/app/view/MapController.js b/web/app/view/MapController.js
index ce420c2ec..918f81390 100644
--- a/web/app/view/MapController.js
+++ b/web/app/view/MapController.js
@@ -113,29 +113,31 @@ Ext.define('Traccar.view.MapController', {
deviceId = position.get('deviceId');
device = Ext.getStore('Devices').findRecord('id', deviceId, 0, false, false, true);
- 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);
- }
+ 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);
+ marker.getStyle().getImage().setRotation(position.get('course') * Math.PI / 180);
- if (marker === this.selectedMarker && this.followSelected()) {
- this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates());
+ if (marker === this.selectedMarker && this.followSelected()) {
+ this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates());
+ }
}
}
},
diff --git a/web/app/view/Register.js b/web/app/view/Register.js
index 7c2881d62..198e10b8f 100644
--- a/web/app/view/Register.js
+++ b/web/app/view/Register.js
@@ -33,7 +33,7 @@ Ext.define('Traccar.view.Register', {
items: [{
xtype: 'textfield',
name: 'name',
- fieldLabel: Strings.userName,
+ fieldLabel: Strings.sharedName,
allowBlank: false
}, {
xtype: 'textfield',
diff --git a/web/app/view/SettingsMenu.js b/web/app/view/SettingsMenu.js
index c828b37af..1ada94359 100644
--- a/web/app/view/SettingsMenu.js
+++ b/web/app/view/SettingsMenu.js
@@ -32,6 +32,9 @@ Ext.define('Traccar.view.SettingsMenu', {
text: Strings.settingsUser,
handler: 'onUserClick'
}, {
+ text: Strings.settingsGroups,
+ handler: 'onGroupsClick'
+ }, {
text: Strings.settingsServer,
hidden: true,
handler: 'onServerClick',
diff --git a/web/app/view/SettingsMenuController.js b/web/app/view/SettingsMenuController.js
index c52f0a75e..6d767e3a9 100644
--- a/web/app/view/SettingsMenuController.js
+++ b/web/app/view/SettingsMenuController.js
@@ -23,6 +23,7 @@ Ext.define('Traccar.view.SettingsMenuController', {
'Traccar.view.UserDialog',
'Traccar.view.ServerDialog',
'Traccar.view.Users',
+ 'Traccar.view.Groups',
'Traccar.view.BaseWindow'
],
@@ -39,6 +40,16 @@ Ext.define('Traccar.view.SettingsMenuController', {
dialog.show();
},
+ onGroupsClick: function () {
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.settingsGroups,
+ modal: false,
+ items: {
+ xtype: 'groupsView'
+ }
+ }).show();
+ },
+
onServerClick: function () {
var dialog = Ext.create('Traccar.view.ServerDialog');
dialog.down('form').loadRecord(Traccar.app.getServer());
diff --git a/web/app/view/UserDevices.js b/web/app/view/UserDevices.js
index f9ab48266..fe16dd93a 100644
--- a/web/app/view/UserDevices.js
+++ b/web/app/view/UserDevices.js
@@ -37,10 +37,12 @@ Ext.define('Traccar.view.UserDevices', {
},
columns: [{
- text: Strings.deviceName,
- dataIndex: 'name', flex: 1
+ text: Strings.sharedName,
+ dataIndex: 'name',
+ flex: 1
}, {
text: Strings.deviceIdentifier,
- dataIndex: 'uniqueId', flex: 1
+ dataIndex: 'uniqueId',
+ flex: 1
}]
});
diff --git a/web/app/view/UserDevicesController.js b/web/app/view/UserDevicesController.js
index 4f013fd64..e5dbbdbb8 100644
--- a/web/app/view/UserDevicesController.js
+++ b/web/app/view/UserDevicesController.js
@@ -47,7 +47,7 @@ Ext.define('Traccar.view.UserDevicesController', {
onBeforeSelect: function (object, record, index) {
Ext.Ajax.request({
scope: this,
- url: '/api/permissions',
+ url: '/api/permissions/devices',
jsonData: {
userId: this.userId,
deviceId: record.getData().id
@@ -64,7 +64,7 @@ Ext.define('Traccar.view.UserDevicesController', {
Ext.Ajax.request({
scope: this,
method: 'DELETE',
- url: '/api/permissions',
+ url: '/api/permissions/devices',
jsonData: {
userId: this.userId,
deviceId: record.getData().id
diff --git a/web/app/view/UserDialog.js b/web/app/view/UserDialog.js
index 783ddd159..c1ed2fece 100644
--- a/web/app/view/UserDialog.js
+++ b/web/app/view/UserDialog.js
@@ -30,7 +30,7 @@ Ext.define('Traccar.view.UserDialog', {
items: [{
xtype: 'textfield',
name: 'name',
- fieldLabel: Strings.userName
+ fieldLabel: Strings.sharedName
}, {
xtype: 'textfield',
name: 'email',
diff --git a/web/app/view/UserGroups.js b/web/app/view/UserGroups.js
new file mode 100644
index 000000000..cb0f0bd5d
--- /dev/null
+++ b/web/app/view/UserGroups.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.UserGroups', {
+ extend: 'Ext.grid.Panel',
+ xtype: 'userGroupsView',
+
+ requires: [
+ 'Traccar.view.UserGroupsController'
+ ],
+
+ controller: 'userGroups',
+ store: 'AllGroups',
+
+ 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/UserGroupsController.js b/web/app/view/UserGroupsController.js
new file mode 100644
index 000000000..662508f0a
--- /dev/null
+++ b/web/app/view/UserGroupsController.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Ext.define('Traccar.view.UserGroupsController', {
+ extend: 'Ext.app.ViewController',
+ alias: 'controller.userGroups',
+
+ init: function () {
+ this.userId = this.getView().user.getData().id;
+ this.getView().getStore().load({
+ scope: this,
+ callback: function (records, operation, success) {
+ var userStore = Ext.create('Traccar.store.Groups');
+
+ userStore.load({
+ params: {
+ userId: this.userId
+ },
+ scope: this,
+ callback: function (records, operation, success) {
+ var i, index;
+ if (success) {
+ for (i = 0; i < records.length; i++) {
+ index = this.getView().getStore().find('id', records[i].getData().id);
+ this.getView().getSelectionModel().select(index, true, true);
+ }
+ }
+ }
+ });
+ }
+ });
+ },
+
+ onBeforeSelect: function (object, record, index) {
+ Ext.Ajax.request({
+ scope: this,
+ url: '/api/permissions/groups',
+ jsonData: {
+ userId: this.userId,
+ groupId: record.getData().id
+ },
+ callback: function (options, success, response) {
+ if (!success) {
+ Traccar.app.showError(response);
+ }
+ }
+ });
+ },
+
+ onBeforeDeselect: function (object, record, index) {
+ Ext.Ajax.request({
+ scope: this,
+ method: 'DELETE',
+ url: '/api/permissions/groups',
+ jsonData: {
+ userId: this.userId,
+ groupId: record.getData().id
+ },
+ callback: function (options, success, response) {
+ if (!success) {
+ Traccar.app.showError(response);
+ }
+ }
+ });
+ }
+});
diff --git a/web/app/view/Users.js b/web/app/view/Users.js
index f4ef332b4..408a70885 100644
--- a/web/app/view/Users.js
+++ b/web/app/view/Users.js
@@ -35,6 +35,11 @@ Ext.define('Traccar.view.Users', {
disabled: true,
handler: 'onDevicesClick',
reference: 'userDevicesButton'
+ }, {
+ text: Strings.settingsGroups,
+ disabled: true,
+ handler: 'onGroupsClick',
+ reference: 'userGroupsButton'
}]
},
@@ -43,7 +48,7 @@ Ext.define('Traccar.view.Users', {
},
columns: [{
- text: Strings.userName,
+ text: Strings.sharedName,
dataIndex: 'name',
flex: 1
}, {
diff --git a/web/app/view/UsersController.js b/web/app/view/UsersController.js
index 3d0e813e8..c48f57cf4 100644
--- a/web/app/view/UsersController.js
+++ b/web/app/view/UsersController.js
@@ -21,6 +21,7 @@ Ext.define('Traccar.view.UsersController', {
requires: [
'Traccar.view.UserDialog',
'Traccar.view.UserDevices',
+ 'Traccar.view.UserGroups',
'Traccar.view.BaseWindow'
],
@@ -75,10 +76,22 @@ Ext.define('Traccar.view.UsersController', {
}).show();
},
+ onGroupsClick: function () {
+ var user = this.getView().getSelectionModel().getSelection()[0];
+ Ext.create('Traccar.view.BaseWindow', {
+ title: Strings.settingsGroups,
+ items: {
+ xtype: 'userGroupsView',
+ 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);
}
});
diff --git a/web/arrowstyle.js b/web/arrowstyle.js
index c1eb88909..ad0ba08ee 100644
--- a/web/arrowstyle.js
+++ b/web/arrowstyle.js
@@ -1,12 +1,10 @@
goog.provide('ol.style.Arrow');
goog.require('goog.asserts');
-goog.require('goog.dom');
goog.require('ol');
goog.require('ol.color');
goog.require('ol.has');
goog.require('ol.render.canvas');
-goog.require('ol.structs.IHasChecksum');
goog.require('ol.style.AtlasManager');
goog.require('ol.style.Fill');
goog.require('ol.style.Image');
@@ -22,7 +20,6 @@ goog.require('ol.style.Stroke');
* @constructor
* @param {olx.style.ArrowOptions} options Options.
* @extends {ol.style.Image}
- * @implements {ol.structs.IHasChecksum}
* @api
*/
ol.style.Arrow = function(options) {
@@ -119,9 +116,15 @@ ol.style.Arrow = function(options) {
var snapToPixel = options.snapToPixel !== undefined ?
options.snapToPixel : true;
+ /**
+ * @type {boolean}
+ */
+ var rotateWithView = options.rotateWithView !== undefined ?
+ options.rotateWithView : false;
+
goog.base(this, {
opacity: 1,
- rotateWithView: false,
+ rotateWithView: rotateWithView,
rotation: options.rotation !== undefined ? options.rotation : 0,
scale: 1,
snapToPixel: snapToPixel
@@ -283,7 +286,7 @@ ol.style.Arrow.RenderOptions;
/**
* @private
- * @param {ol.style.AtlasManager|undefined} atlasManager
+ * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
*/
ol.style.Arrow.prototype.render_ = function(atlasManager) {
var imageSize;
@@ -334,7 +337,7 @@ ol.style.Arrow.prototype.render_ = function(atlasManager) {
if (atlasManager === undefined) {
// no atlas manager is used, create a new canvas
this.canvas_ = /** @type {HTMLCanvasElement} */
- (goog.dom.createElement('CANVAS'));
+ (document.createElement('CANVAS'));
this.canvas_.height = size;
this.canvas_.width = size;
@@ -357,12 +360,12 @@ ol.style.Arrow.prototype.render_ = function(atlasManager) {
if (hasCustomHitDetectionImage) {
// render the hit-detection image into a separate atlas image
renderHitDetectionCallback =
- goog.bind(this.drawHitDetectionCanvas_, this, renderOptions);
+ this.drawHitDetectionCanvas_.bind(this, renderOptions);
}
var id = this.getChecksum();
var info = atlasManager.add(
- id, size, size, goog.bind(this.draw_, this, renderOptions),
+ id, size, size, this.draw_.bind(this, renderOptions),
renderHitDetectionCallback);
goog.asserts.assert(info, 'arrow size is too large');
@@ -388,8 +391,8 @@ ol.style.Arrow.prototype.render_ = function(atlasManager) {
/**
* @private
- * @param {ol.style.Arrow.RenderOptions} renderOptions
- * @param {CanvasRenderingContext2D} context
+ * @param {ol.style.Arrow.RenderOptions} renderOptions Render options.
+ * @param {CanvasRenderingContext2D} context The rendering context.
* @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y).
*/
@@ -419,7 +422,7 @@ ol.style.Arrow.prototype.draw_ = function(renderOptions, context, x, y) {
lineTo(this.radius_, 0);
if (this.fill_) {
- context.fillStyle = ol.color.asString(this.fill_.getColor());
+ context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor());
context.fill();
}
if (this.stroke_) {
@@ -439,10 +442,9 @@ ol.style.Arrow.prototype.draw_ = function(renderOptions, context, x, y) {
/**
* @private
- * @param {ol.style.Arrow.RenderOptions} renderOptions
+ * @param {ol.style.Arrow.RenderOptions} renderOptions Render options.
*/
-ol.style.Arrow.prototype.createHitDetectionCanvas_ =
- function(renderOptions) {
+ol.style.Arrow.prototype.createHitDetectionCanvas_ = function(renderOptions) {
this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
if (this.fill_) {
this.hitDetectionCanvas_ = this.canvas_;
@@ -452,7 +454,7 @@ ol.style.Arrow.prototype.createHitDetectionCanvas_ =
// if no fill style is set, create an extra hit-detection image with a
// default fill style
this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */
- (goog.dom.createElement('CANVAS'));
+ (document.createElement('CANVAS'));
var canvas = this.hitDetectionCanvas_;
canvas.height = renderOptions.size;
@@ -466,13 +468,12 @@ ol.style.Arrow.prototype.createHitDetectionCanvas_ =
/**
* @private
- * @param {ol.style.Arrow.RenderOptions} renderOptions
- * @param {CanvasRenderingContext2D} context
+ * @param {ol.style.Arrow.RenderOptions} renderOptions Render options.
+ * @param {CanvasRenderingContext2D} context The context.
* @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y).
*/
-ol.style.Arrow.prototype.drawHitDetectionCanvas_ =
- function(renderOptions, context, x, y) {
+ol.style.Arrow.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) {
var innerRadius = this.radius_ / Math.sin(Math.PI - this.backAngle_ / 2) *
Math.sin(this.backAngle_ / 2 - this.frontAngle_);
@@ -511,7 +512,7 @@ ol.style.Arrow.prototype.drawHitDetectionCanvas_ =
/**
- * @inheritDoc
+ * @return {string} The checksum.
*/
ol.style.Arrow.prototype.getChecksum = function() {
var strokeChecksum = this.stroke_ ?
diff --git a/web/debug.html b/web/debug.html
index 3608930c0..351bb9f9f 100644
--- a/web/debug.html
+++ b/web/debug.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Traccar</title>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/resources/theme-neptune-all.css">
-<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="app.css">
</head>
@@ -14,8 +14,11 @@
<div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all-debug.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/theme-neptune.js"></script>
-<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol-debug.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol-debug.js"></script>
<script src="arrowstyle.js"></script>
+<script type="text/javascript">
+Ext.Loader.setConfig({ disableCaching: false });
+</script>
<script src="locale.js"></script>
<script src="app.js"></script>
</body>
diff --git a/web/l10n/en.json b/web/l10n/en.json
index ec8018863..cabd76362 100644
--- a/web/l10n/en.json
+++ b/web/l10n/en.json
@@ -13,10 +13,10 @@
"sharedHour": "Hour",
"sharedMinute": "Minute",
"sharedSecond": "Second",
+ "sharedName": "Name",
"errorTitle": "Error",
"errorUnknown": "Unknown error",
"errorConnection": "Connection error",
- "userName": "Name",
"userEmail": "Email",
"userPassword": "Password",
"userAdmin": "Admin",
@@ -30,13 +30,15 @@
"devicesAndState": "Devices and State",
"deviceDialog": "Device",
"deviceTitle": "Devices",
- "deviceName": "Name",
"deviceIdentifier": "Identifier",
"deviceLastUpdate": "Last Update",
"deviceCommand": "Command",
"deviceFollow": "Follow",
+ "groupDialog": "Group",
+ "groupParent": "Group",
"settingsTitle": "Settings",
"settingsUser": "Account",
+ "settingsGroups": "Groups",
"settingsServer": "Server",
"settingsUsers": "Users",
"settingsDistanceUnit": "Distance",
diff --git a/web/l10n/es.json b/web/l10n/es.json
index 2ace91caf..7cca1c8f2 100644
--- a/web/l10n/es.json
+++ b/web/l10n/es.json
@@ -1,6 +1,6 @@
{
"sharedLoading": "Cargando...",
- "sharedSave": "Grabar",
+ "sharedSave": "Guardar",
"sharedCancel": "Cancelar",
"sharedAdd": "Agregar",
"sharedEdit": "Editar",
@@ -12,14 +12,14 @@
"sharedMph": "MPH",
"sharedHour": "Hora",
"sharedMinute": "Minuto",
- "sharedSecond": "Segundos",
+ "sharedSecond": "Segundo",
"errorTitle": "Error",
"errorUnknown": "Error Desconocido",
"errorConnection": "Error de Conexión",
"userName": "Nombre",
"userEmail": "Email",
"userPassword": "Contraseña",
- "userAdmin": "Admin",
+ "userAdmin": "Administrador",
"loginTitle": "Ingresar",
"loginLanguage": "Idioma",
"loginRegister": "Registrar",
@@ -27,14 +27,14 @@
"loginFailed": "Dirección de Correo o Contraseña Incorrecta",
"loginCreated": "Nuevo Usuario ha sido registrado",
"loginLogout": "Salir",
- "devicesAndState": "Devices and State",
+ "devicesAndState": "Dispositivos y Estado",
"deviceDialog": "Dispositivo",
"deviceTitle": "Dispositivos",
"deviceName": "Nombre",
"deviceIdentifier": "Identificador",
- "deviceLastUpdate": "Last Update",
+ "deviceLastUpdate": "Última Actualización",
"deviceCommand": "Comando",
- "deviceFollow": "Follow",
+ "deviceFollow": "Seguir",
"settingsTitle": "Preferencias",
"settingsUser": "Cuenta",
"settingsServer": "Servidor",
@@ -59,7 +59,7 @@
"serverTitle": "Preferencias Servidor",
"serverZoom": "Zoom",
"serverRegistration": "Registrar",
- "serverReadonly": "Readonly",
+ "serverReadonly": "Sólo Lectura",
"mapTitle": "Mapa",
"mapLayer": "Capa de Mapa",
"mapCustom": "Mapa Personalizado",
diff --git a/web/l10n/fa.json b/web/l10n/fa.json
new file mode 100644
index 000000000..3497dff23
--- /dev/null
+++ b/web/l10n/fa.json
@@ -0,0 +1,83 @@
+{
+ "sharedLoading": "در حال بارگزارى ...",
+ "sharedSave": "ذخيره",
+ "sharedCancel": "Cancel",
+ "sharedAdd": "اضافه كردن",
+ "sharedEdit": "Edit",
+ "sharedRemove": "Remove",
+ "sharedRemoveConfirm": "Remove item?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Hour",
+ "sharedMinute": "دقيقه",
+ "sharedSecond": "ثانيه",
+ "errorTitle": "Error",
+ "errorUnknown": "Unknown error",
+ "errorConnection": "Connection error",
+ "userName": "نام",
+ "userEmail": "نام كاربرى ( ايميل )",
+ "userPassword": "گذرواژه",
+ "userAdmin": "Admin",
+ "loginTitle": "ورود",
+ "loginLanguage": "انتخاب زبان",
+ "loginRegister": "ثبت نام",
+ "loginLogin": "ورود",
+ "loginFailed": "نام كاربرى ( ايميل ) يا گذرواژه اشتباه است",
+ "loginCreated": "ثبت نام با موفقيت انجام شد",
+ "loginLogout": "خروج",
+ "devicesAndState": "Devices and State",
+ "deviceDialog": "دستگاه",
+ "deviceTitle": "دستگاه ها",
+ "deviceName": "نام خودرو",
+ "deviceIdentifier": "سريال دستگاه",
+ "deviceLastUpdate": "آخرين بروزرسانى",
+ "deviceCommand": "Command",
+ "deviceFollow": "Follow",
+ "settingsTitle": "تنظيمات",
+ "settingsUser": "حساب كاربرى",
+ "settingsServer": "Server",
+ "settingsUsers": "Users",
+ "settingsDistanceUnit": "Distance",
+ "settingsSpeedUnit": "سرعت",
+ "reportTitle": "گزارشات ",
+ "reportDevice": "دستگاه",
+ "reportFrom": "از",
+ "reportTo": "To",
+ "reportShow": "Show",
+ "reportClear": "Clear",
+ "positionFixTime": "Time",
+ "positionValid": "Valid",
+ "positionLatitude": "عرض جغرافيايى",
+ "positionLongitude": "طول جغرافيايى",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "سرعت",
+ "positionCourse": "Course",
+ "positionAddress": "Address",
+ "positionProtocol": "Protocol",
+ "serverTitle": "Server Settings",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registration",
+ "serverReadonly": "Readonly",
+ "mapTitle": "Map",
+ "mapLayer": "Map Layer",
+ "mapCustom": "Custom Map",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "stateTitle": "State",
+ "stateName": "Attribute",
+ "stateValue": "Value",
+ "commandTitle": "Command",
+ "commandSend": "ارسال",
+ "commandType": "Type",
+ "commandSent": "Command has been sent",
+ "commandPositionPeriodic": "Periodic Reporting",
+ "commandPositionStop": "Stop Reporting",
+ "commandEngineStop": "Engine Stop",
+ "commandEngineResume": "Engine Resume",
+ "commandFrequency": "Frequency",
+ "commandUnit": "Unit"
+} \ No newline at end of file
diff --git a/web/l10n/fi.json b/web/l10n/fi.json
new file mode 100644
index 000000000..dc44cb03a
--- /dev/null
+++ b/web/l10n/fi.json
@@ -0,0 +1,83 @@
+{
+ "sharedLoading": "Ladataan...",
+ "sharedSave": "Tallenna",
+ "sharedCancel": "Peruuta",
+ "sharedAdd": "Lisää",
+ "sharedEdit": "Muokkaa",
+ "sharedRemove": "Poista",
+ "sharedRemoveConfirm": "Poista kohde?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Tunti",
+ "sharedMinute": "Minuutti",
+ "sharedSecond": "Sekunti",
+ "errorTitle": "Virhe",
+ "errorUnknown": "Tuntematon virhe",
+ "errorConnection": "Yhteysvirhe",
+ "userName": "Nimi",
+ "userEmail": "Email",
+ "userPassword": "Salasana",
+ "userAdmin": "Ylläpito",
+ "loginTitle": "Kirjaudu",
+ "loginLanguage": "Kieli",
+ "loginRegister": "Rekisteröidy",
+ "loginLogin": "Kirjaudu",
+ "loginFailed": "Virheellinen email tai salasana",
+ "loginCreated": "Uusi käyttäjä on rekisteröitynyt",
+ "loginLogout": "Kirjaudu ulos",
+ "devicesAndState": "Laitteet ja Tilat",
+ "deviceDialog": "Laite",
+ "deviceTitle": "Laitteet",
+ "deviceName": "Nimi",
+ "deviceIdentifier": "Tunniste",
+ "deviceLastUpdate": "Viimeisin päivitys",
+ "deviceCommand": "Komento",
+ "deviceFollow": "Seuraa",
+ "settingsTitle": "Asetukset",
+ "settingsUser": "Tili",
+ "settingsServer": "Palvelin",
+ "settingsUsers": "Käyttäjät",
+ "settingsDistanceUnit": "Etäisyys",
+ "settingsSpeedUnit": "Nopeus",
+ "reportTitle": "Raportit",
+ "reportDevice": "Laite",
+ "reportFrom": "Mistä",
+ "reportTo": "Mihin",
+ "reportShow": "Näytä",
+ "reportClear": "Tyhjennä",
+ "positionFixTime": "Aika",
+ "positionValid": "Kelvollinen",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Korkeus",
+ "positionSpeed": "Nopeus",
+ "positionCourse": "Suunta",
+ "positionAddress": "Osoite",
+ "positionProtocol": "Protokolla",
+ "serverTitle": "Palvelinasetukset",
+ "serverZoom": "Lähennä",
+ "serverRegistration": "Rekisteröinti",
+ "serverReadonly": "Vain luku",
+ "mapTitle": "Kartta",
+ "mapLayer": "Karttataso",
+ "mapCustom": "Oma kartta",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps avain",
+ "mapBingRoad": "Bign Maps tiet",
+ "mapBingAerial": "Bing Maps ilmakuva",
+ "stateTitle": "Tila",
+ "stateName": "Ominaisuus",
+ "stateValue": "Arvo",
+ "commandTitle": "Komento",
+ "commandSend": "Lähetä",
+ "commandType": "Tyyppi",
+ "commandSent": "Komento on lähetetty",
+ "commandPositionPeriodic": "Määräaikaisraportointi",
+ "commandPositionStop": "Lopeta raportointi",
+ "commandEngineStop": "Sammuta moottori",
+ "commandEngineResume": "Palauta moottori",
+ "commandFrequency": "Taajuus",
+ "commandUnit": "Yksikkö"
+} \ No newline at end of file
diff --git a/web/l10n/lt.json b/web/l10n/lt.json
index 3b32a9c3d..77e7b95a5 100644
--- a/web/l10n/lt.json
+++ b/web/l10n/lt.json
@@ -27,14 +27,14 @@
"loginFailed": "Neteisingas el.paštas ir/ar slaptažodis",
"loginCreated": "Registracija sėmkinga",
"loginLogout": "Atsijungti",
- "devicesAndState": "Devices and State",
+ "devicesAndState": "Prietaisai ir Statusas",
"deviceDialog": "Prietaisas",
"deviceTitle": "Prietaisai",
"deviceName": "Pavadinimas",
"deviceIdentifier": "Identifikacinis kodas",
"deviceLastUpdate": "Naujausias atnaujinimas",
"deviceCommand": "Komanda",
- "deviceFollow": "Follow",
+ "deviceFollow": "Sekti",
"settingsTitle": "Nustatymai",
"settingsUser": "Paskyra",
"settingsServer": "Serveris",
@@ -59,7 +59,7 @@
"serverTitle": "Serverio nustatymai",
"serverZoom": "Priartinimas",
"serverRegistration": "Registracija",
- "serverReadonly": "Readonly",
+ "serverReadonly": "Tik skaityti",
"mapTitle": "Žemėlapis",
"mapLayer": "Žemėlapio sluoksnis",
"mapCustom": "Pasirinktinis Žemėlapis",
diff --git a/web/l10n/ml.json b/web/l10n/ml.json
new file mode 100644
index 000000000..02bd39b00
--- /dev/null
+++ b/web/l10n/ml.json
@@ -0,0 +1,83 @@
+{
+ "sharedLoading": "ലോഡുചെയ്യുന്നു ..",
+ "sharedSave": "Save",
+ "sharedCancel": "റദ്ദാക്കുക",
+ "sharedAdd": "ചേര്‍ക്കുക",
+ "sharedEdit": "തിരുത്തുക",
+ "sharedRemove": "നീക്കം ചെയ്യുക",
+ "sharedRemoveConfirm": "വിഷയം നീക്കം ചെയ്യുക",
+ "sharedKm": "കിലോമീറ്റർ",
+ "sharedMi": "mi",
+ "sharedKmh": "കിലോമീറ്റർ / മണിക്കൂർ",
+ "sharedMph": "mph",
+ "sharedHour": "മണിക്കൂര്",
+ "sharedMinute": "മിനിറ്റ്",
+ "sharedSecond": "സെക്കന്റ്",
+ "errorTitle": "പിശക്‌",
+ "errorUnknown": "അജ്ഞാത പിശക്",
+ "errorConnection": "കണക്ഷൻ പിശക്",
+ "userName": "പേര്",
+ "userEmail": "ഇമെയിൽ",
+ "userPassword": "രഹസ്യ കോഡ്‌",
+ "userAdmin": "Admin",
+ "loginTitle": "അകത്തു പ്രവേശിക്കുക",
+ "loginLanguage": "ഭാഷ",
+ "loginRegister": "Register",
+ "loginLogin": "അകത്തു പ്രവേശിക്കുക",
+ "loginFailed": "തെറ്റായ ഇമെയിൽ വിലാസവും പാസ്വേഡും",
+ "loginCreated": "പുതിയ ഉപയോക്താവ് രജിസ്റ്റർ ചെയ്തു",
+ "loginLogout": "പുറത്തുകടക്കുക",
+ "devicesAndState": "Devices and State",
+ "deviceDialog": "ഉപകരണം",
+ "deviceTitle": "Devices",
+ "deviceName": "പേര്",
+ "deviceIdentifier": "Identifier",
+ "deviceLastUpdate": "Last Update",
+ "deviceCommand": "Command",
+ "deviceFollow": "Follow",
+ "settingsTitle": "Settings",
+ "settingsUser": "Account",
+ "settingsServer": "Server",
+ "settingsUsers": "Users",
+ "settingsDistanceUnit": "Distance",
+ "settingsSpeedUnit": "വേഗം",
+ "reportTitle": "Reports",
+ "reportDevice": "ഉപകരണം",
+ "reportFrom": "From",
+ "reportTo": "To",
+ "reportShow": "Show",
+ "reportClear": "Clear",
+ "positionFixTime": "സമയം",
+ "positionValid": "Valid",
+ "positionLatitude": "അക്ഷാംശം",
+ "positionLongitude": "രേഖാംശം",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "വേഗം",
+ "positionCourse": "Course",
+ "positionAddress": "Address",
+ "positionProtocol": "Protocol",
+ "serverTitle": "Server Settings",
+ "serverZoom": "വലുതാക്കിയോ ചെറുതാക്കിയോ കാണിക്കുക",
+ "serverRegistration": "രജിസ്ട്രേഷൻ",
+ "serverReadonly": "Readonly",
+ "mapTitle": "ഭൂപടം",
+ "mapLayer": "Map Layer",
+ "mapCustom": "Custom Map",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "stateTitle": "State",
+ "stateName": "Attribute",
+ "stateValue": "Value",
+ "commandTitle": "Command",
+ "commandSend": "Send",
+ "commandType": "Type",
+ "commandSent": "Command has been sent",
+ "commandPositionPeriodic": "Periodic Reporting",
+ "commandPositionStop": "Stop Reporting",
+ "commandEngineStop": "Engine Stop",
+ "commandEngineResume": "Engine Resume",
+ "commandFrequency": "Frequency",
+ "commandUnit": "Unit"
+} \ No newline at end of file
diff --git a/web/l10n/ms.json b/web/l10n/ms.json
new file mode 100644
index 000000000..ff5f02f01
--- /dev/null
+++ b/web/l10n/ms.json
@@ -0,0 +1,83 @@
+{
+ "sharedLoading": "Memuatkan...",
+ "sharedSave": "Simpan",
+ "sharedCancel": "Batal",
+ "sharedAdd": "Tambah",
+ "sharedEdit": "Ubah",
+ "sharedRemove": "Hapus",
+ "sharedRemoveConfirm": "Hapuskan item?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Jam",
+ "sharedMinute": "Minit",
+ "sharedSecond": "Saat",
+ "errorTitle": "Ralat",
+ "errorUnknown": "Ralat tidak diketahui",
+ "errorConnection": "Ralat penyambungan",
+ "userName": "Nama",
+ "userEmail": "Emel",
+ "userPassword": "Katalaluan",
+ "userAdmin": "Admin",
+ "loginTitle": "Log masuk",
+ "loginLanguage": "Bahasa",
+ "loginRegister": "Daftar",
+ "loginLogin": "Log masuk",
+ "loginFailed": "Kesalahan emel atau katalaluan",
+ "loginCreated": "Pengguna baru telah didaftarkan",
+ "loginLogout": "Keluar",
+ "devicesAndState": "Peranti dan State",
+ "deviceDialog": "Peranti",
+ "deviceTitle": "Peranti",
+ "deviceName": "Nama",
+ "deviceIdentifier": "IMEI/ID",
+ "deviceLastUpdate": "Kemaskini Terakhir",
+ "deviceCommand": "Arahan",
+ "deviceFollow": "Ikut",
+ "settingsTitle": "Tetapan",
+ "settingsUser": "Akaun",
+ "settingsServer": "Server",
+ "settingsUsers": "Pengguna",
+ "settingsDistanceUnit": "Jarak",
+ "settingsSpeedUnit": "Kelajuan",
+ "reportTitle": "Laporan",
+ "reportDevice": "Peranti",
+ "reportFrom": "Daripada",
+ "reportTo": "Ke",
+ "reportShow": "Papar",
+ "reportClear": "Kosongkan",
+ "positionFixTime": "Masa",
+ "positionValid": "Sah",
+ "positionLatitude": "Latitud",
+ "positionLongitude": "Longitud",
+ "positionAltitude": "Altitud",
+ "positionSpeed": "Kelajuan",
+ "positionCourse": "Course",
+ "positionAddress": "Alamat",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Tetapan Server",
+ "serverZoom": "Besarkan",
+ "serverRegistration": "Pendaftaran",
+ "serverReadonly": "Baca Sahaja",
+ "mapTitle": "Peta",
+ "mapLayer": "Map Layer",
+ "mapCustom": "Peta Lain",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "stateTitle": "Negeri",
+ "stateName": "Atribut",
+ "stateValue": "Nilai",
+ "commandTitle": "Arahan",
+ "commandSend": "Hantar",
+ "commandType": "Jenis",
+ "commandSent": "Arahan telah dihantar",
+ "commandPositionPeriodic": "Laporan Berkala",
+ "commandPositionStop": "Hentikan Laporan",
+ "commandEngineStop": "Matikan Enjin",
+ "commandEngineResume": "Hidupkan Enjin",
+ "commandFrequency": "Frekuensi",
+ "commandUnit": "Unit"
+} \ No newline at end of file
diff --git a/web/l10n/no.json b/web/l10n/no.json
index dcfef54ce..d3dbb003e 100644
--- a/web/l10n/no.json
+++ b/web/l10n/no.json
@@ -67,7 +67,7 @@
"mapBingKey": "Bing Maps Nøkkel",
"mapBingRoad": "Bing Maps Vei",
"mapBingAerial": "Bing Maps Fly",
- "stateTitle": "Stat",
+ "stateTitle": "Status",
"stateName": "Egenskap",
"stateValue": "Verdi",
"commandTitle": "Kommando",
diff --git a/web/l10n/tr.json b/web/l10n/tr.json
new file mode 100644
index 000000000..6eacb12d0
--- /dev/null
+++ b/web/l10n/tr.json
@@ -0,0 +1,83 @@
+{
+ "sharedLoading": "Yükleniyor...",
+ "sharedSave": "Kaydet",
+ "sharedCancel": "İptal",
+ "sharedAdd": "Ekle",
+ "sharedEdit": "Düzenle",
+ "sharedRemove": "Kaldır",
+ "sharedRemoveConfirm": "Öğeyi kaldır",
+ "sharedKm": "km",
+ "sharedMi": "mil",
+ "sharedKmh": "km/s",
+ "sharedMph": "mil/s",
+ "sharedHour": "Saat",
+ "sharedMinute": "Dakika",
+ "sharedSecond": "Saniye",
+ "errorTitle": "Hata",
+ "errorUnknown": "Bilinmeyen hata ",
+ "errorConnection": "Bağlantı Hatası",
+ "userName": "Ad",
+ "userEmail": "Eposta",
+ "userPassword": "Şifre",
+ "userAdmin": "Yönetici",
+ "loginTitle": "Oturum aç",
+ "loginLanguage": "Lisan",
+ "loginRegister": "Kayıt",
+ "loginLogin": "Oturumu aç",
+ "loginFailed": "Geçersiz eposta veya şifre",
+ "loginCreated": "Yeni kullanıcı kaydedildi",
+ "loginLogout": "Oturumu sonlandır",
+ "devicesAndState": "Cihazlar ve Bölge",
+ "deviceDialog": "Cihaz",
+ "deviceTitle": "Cihazlar",
+ "deviceName": "İsim",
+ "deviceIdentifier": "Kimlik",
+ "deviceLastUpdate": "Son Güncelleme",
+ "deviceCommand": "Komut",
+ "deviceFollow": "Takip",
+ "settingsTitle": "Ayarlar",
+ "settingsUser": "Hesap",
+ "settingsServer": "Sunucu",
+ "settingsUsers": "Kullanıcı",
+ "settingsDistanceUnit": "Mesafe",
+ "settingsSpeedUnit": "Hız",
+ "reportTitle": "Raporlar",
+ "reportDevice": "Aygıt",
+ "reportFrom": "Başlangıç",
+ "reportTo": "Varış",
+ "reportShow": "Göster",
+ "reportClear": "Temizle",
+ "positionFixTime": "Süre",
+ "positionValid": "Geçerli",
+ "positionLatitude": "Enlem",
+ "positionLongitude": "Boylam",
+ "positionAltitude": "Rakım",
+ "positionSpeed": "Sürat",
+ "positionCourse": "Yön",
+ "positionAddress": "Adres",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Sunucu Ayarları",
+ "serverZoom": "Yakınlaştırma",
+ "serverRegistration": "Kayıt",
+ "serverReadonly": "Saltokunur",
+ "mapTitle": "Harita",
+ "mapLayer": "Harita Katmanı",
+ "mapCustom": "Özelleştirilmiş Harita",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "stateTitle": "Bölge",
+ "stateName": "Özellik",
+ "stateValue": "Değer",
+ "commandTitle": "Komut",
+ "commandSend": "Gönder",
+ "commandType": "Tip",
+ "commandSent": "Komut gönderildi",
+ "commandPositionPeriodic": "Periyodik Rapor",
+ "commandPositionStop": "Raporlamayı Durdur",
+ "commandEngineStop": "Motoru Durdur",
+ "commandEngineResume": "Motoru Çalıştır",
+ "commandFrequency": "Frekans",
+ "commandUnit": "Ünite"
+} \ No newline at end of file
diff --git a/web/locale.js b/web/locale.js
index 7dc94ec45..c0b3af90b 100644
--- a/web/locale.js
+++ b/web/locale.js
@@ -16,9 +16,6 @@
var Locale = {};
-Ext.Ajax.disableCaching = false;
-Ext.Loader.disableCaching = false;
-
Locale.languages = {
'ar': { name: 'العربية', code: 'en' },
'bg': { name: 'Български', code: 'bg' },
@@ -28,12 +25,16 @@ Locale.languages = {
'el': { name: 'Ελληνικά', code: 'el' },
'en': { name: 'English', code: 'en' },
'es': { name: 'Español', code: 'es' },
+ 'fa': { name: 'فارسی', code: 'fa' },
+ 'fi': { name: 'Suomi', code: 'fi' },
'fr': { name: 'Français', code: 'fr' },
'hu': { name: 'Magyar', code: 'hu' },
'id': { name: 'Bahasa Indonesia', code: 'id' },
'it': { name: 'Italiano', code: 'it' },
'ka': { name: 'ქართული', code: 'en' },
'lt': { name: 'Lietuvių', code: 'lt' },
+ 'ml': { name: 'മലയാളം', code: 'en' },
+ 'ms': { name: 'بهاس ملايو', code: 'en' },
'nl': { name: 'Nederlands', code: 'nl' },
'no': { name: 'Norsk', code: 'no_NB' },
'pl': { name: 'Polski', code: 'pl' },
@@ -47,6 +48,7 @@ Locale.languages = {
'sr': { name: 'Srpski', code: 'sr' },
'ta': { name: 'தமிழ்', code: 'en' },
'th': { name: 'ไทย', code: 'th' },
+ 'tr': { name: 'Türkçe', code: 'tr' },
'uk': { name: 'Українська', code: 'ukr' },
'vi': { name: 'Tiếng Việt', code: 'en' },
'zh': { name: '中文', code: 'zh_CN' }
diff --git a/web/release.html b/web/release.html
index d5fc60e2b..155e13f1a 100644
--- a/web/release.html
+++ b/web/release.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Traccar</title>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/resources/theme-neptune-all.css">
-<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" href="app.css">
</head>
@@ -14,7 +14,7 @@
<div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/theme-neptune.js"></script>
-<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol-debug.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol-debug.min.js"></script>
<script src="arrowstyle.js"></script>
<script src="locale.js"></script>
<script type="text/javascript">