aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2016-09-18 11:40:46 +1200
committerAnton Tananaev <anton.tananaev@gmail.com>2016-09-18 11:40:46 +1200
commit2608adeff46f1286deff11a5320cee5647f6d41b (patch)
treea16014c04c755ec701b3237b0cccc7d8cc406960
parentd5ca160059220a07c71c95cc85696e0d66bf9c6e (diff)
downloadetbsa-traccar-web-2608adeff46f1286deff11a5320cee5647f6d41b.tar.gz
etbsa-traccar-web-2608adeff46f1286deff11a5320cee5647f6d41b.tar.bz2
etbsa-traccar-web-2608adeff46f1286deff11a5320cee5647f6d41b.zip
Move web interface from main repo
-rw-r--r--tools/minify.bat5
-rwxr-xr-xtools/minify.sh14
-rwxr-xr-xtools/translate.py35
-rw-r--r--web/.jscsrc5
-rw-r--r--web/.jshintignore4
-rw-r--r--web/.jshintrc75
-rw-r--r--web/app.css69
-rw-r--r--web/app.js20
-rw-r--r--web/app/Application.js117
-rw-r--r--web/app/AttributeFormatter.js81
-rw-r--r--web/app/GeofenceConverter.js79
-rw-r--r--web/app/Style.js79
-rw-r--r--web/app/controller/Root.js207
-rw-r--r--web/app/model/Attribute.js30
-rw-r--r--web/app/model/Command.js30
-rw-r--r--web/app/model/Device.js45
-rw-r--r--web/app/model/Event.js43
-rw-r--r--web/app/model/Geofence.js36
-rw-r--r--web/app/model/Group.js33
-rw-r--r--web/app/model/Notification.js33
-rw-r--r--web/app/model/Position.js66
-rw-r--r--web/app/model/ReportSummary.js41
-rw-r--r--web/app/model/ReportTrip.js55
-rw-r--r--web/app/model/Server.js72
-rw-r--r--web/app/model/User.js69
-rw-r--r--web/app/store/AllDevices.js28
-rw-r--r--web/app/store/AllGeofences.js28
-rw-r--r--web/app/store/AllGroups.js28
-rw-r--r--web/app/store/AllNotifications.js30
-rw-r--r--web/app/store/Attributes.js24
-rw-r--r--web/app/store/CommandTypes.js50
-rw-r--r--web/app/store/Devices.js28
-rw-r--r--web/app/store/DistanceUnits.js39
-rw-r--r--web/app/store/Events.js25
-rw-r--r--web/app/store/GeofenceTypes.js28
-rw-r--r--web/app/store/Geofences.js28
-rw-r--r--web/app/store/Groups.js28
-rw-r--r--web/app/store/Languages.js33
-rw-r--r--web/app/store/LatestPositions.js20
-rw-r--r--web/app/store/MapTypes.js34
-rw-r--r--web/app/store/Notifications.js25
-rw-r--r--web/app/store/Positions.js25
-rw-r--r--web/app/store/ReportEventTypes.js25
-rw-r--r--web/app/store/ReportEvents.js28
-rw-r--r--web/app/store/ReportRoute.js28
-rw-r--r--web/app/store/ReportSummary.js29
-rw-r--r--web/app/store/ReportTrips.js29
-rw-r--r--web/app/store/ReportTypes.js34
-rw-r--r--web/app/store/SpeedUnits.js43
-rw-r--r--web/app/store/TimeUnits.js31
-rw-r--r--web/app/store/Users.js28
-rw-r--r--web/app/view/AttributeController.js42
-rw-r--r--web/app/view/AttributeDialog.js47
-rw-r--r--web/app/view/Attributes.js47
-rw-r--r--web/app/view/AttributesController.js114
-rw-r--r--web/app/view/BaseDialog.js23
-rw-r--r--web/app/view/BaseEditDialog.js32
-rw-r--r--web/app/view/BaseEditDialogController.js60
-rw-r--r--web/app/view/BaseMap.js117
-rw-r--r--web/app/view/BasePermissionsController.js81
-rw-r--r--web/app/view/BaseWindow.js24
-rw-r--r--web/app/view/CommandDialog.js107
-rw-r--r--web/app/view/CommandDialogController.js103
-rw-r--r--web/app/view/CustomTimeField.js29
-rw-r--r--web/app/view/DeviceDialog.js49
-rw-r--r--web/app/view/DeviceGeofences.js43
-rw-r--r--web/app/view/Devices.js176
-rw-r--r--web/app/view/DevicesController.js156
-rw-r--r--web/app/view/EditToolbar.js48
-rw-r--r--web/app/view/GeofenceDialog.js58
-rw-r--r--web/app/view/GeofenceDialogController.js51
-rw-r--r--web/app/view/GeofenceMap.js116
-rw-r--r--web/app/view/GeofenceMapController.js43
-rw-r--r--web/app/view/Geofences.js48
-rw-r--r--web/app/view/GeofencesController.js72
-rw-r--r--web/app/view/GroupDialog.js44
-rw-r--r--web/app/view/GroupGeofences.js43
-rw-r--r--web/app/view/Groups.js53
-rw-r--r--web/app/view/GroupsController.js88
-rw-r--r--web/app/view/Login.js99
-rw-r--r--web/app/view/LoginController.js109
-rw-r--r--web/app/view/Main.js67
-rw-r--r--web/app/view/MainMobile.js52
-rw-r--r--web/app/view/Map.js59
-rw-r--r--web/app/view/MapController.js317
-rw-r--r--web/app/view/MapPickerDialogController.js41
-rw-r--r--web/app/view/Notifications.js71
-rw-r--r--web/app/view/NotificationsController.js84
-rw-r--r--web/app/view/Register.js60
-rw-r--r--web/app/view/RegisterController.js43
-rw-r--r--web/app/view/Report.js63
-rw-r--r--web/app/view/ReportConfigController.js69
-rw-r--r--web/app/view/ReportConfigDialog.js98
-rw-r--r--web/app/view/ReportController.js343
-rw-r--r--web/app/view/ServerDialog.js111
-rw-r--r--web/app/view/SettingsMenu.js64
-rw-r--r--web/app/view/SettingsMenuController.js104
-rw-r--r--web/app/view/State.js45
-rw-r--r--web/app/view/StateController.js131
-rw-r--r--web/app/view/UserDevices.js47
-rw-r--r--web/app/view/UserDialog.js115
-rw-r--r--web/app/view/UserDialogController.js48
-rw-r--r--web/app/view/UserGeofences.js43
-rw-r--r--web/app/view/UserGroups.js43
-rw-r--r--web/app/view/Users.js73
-rw-r--r--web/app/view/UsersController.js140
-rw-r--r--web/arrow.js526
-rw-r--r--web/beep.wavbin0 -> 3047 bytes
-rw-r--r--web/debug.html25
-rw-r--r--web/favicon.icobin0 -> 1150 bytes
-rw-r--r--web/l10n/ar.json164
-rw-r--r--web/l10n/bg.json164
-rw-r--r--web/l10n/cs.json164
-rw-r--r--web/l10n/da.json164
-rw-r--r--web/l10n/de.json164
-rw-r--r--web/l10n/el.json164
-rw-r--r--web/l10n/en.json164
-rw-r--r--web/l10n/es.json164
-rw-r--r--web/l10n/fa.json164
-rw-r--r--web/l10n/fi.json164
-rw-r--r--web/l10n/fr.json164
-rw-r--r--web/l10n/he.json164
-rw-r--r--web/l10n/hi.json164
-rw-r--r--web/l10n/hu.json164
-rw-r--r--web/l10n/id.json164
-rw-r--r--web/l10n/it.json164
-rw-r--r--web/l10n/ka.json164
-rw-r--r--web/l10n/lo.json164
-rw-r--r--web/l10n/lt.json164
-rw-r--r--web/l10n/ml.json164
-rw-r--r--web/l10n/ms.json164
-rw-r--r--web/l10n/nb.json164
-rw-r--r--web/l10n/ne.json164
-rw-r--r--web/l10n/nl.json164
-rw-r--r--web/l10n/nn.json164
-rw-r--r--web/l10n/pl.json164
-rw-r--r--web/l10n/pt.json164
-rw-r--r--web/l10n/pt_BR.json164
-rw-r--r--web/l10n/ro.json164
-rw-r--r--web/l10n/ru.json164
-rw-r--r--web/l10n/si.json164
-rw-r--r--web/l10n/sk.json164
-rw-r--r--web/l10n/sl.json164
-rw-r--r--web/l10n/sq.json164
-rw-r--r--web/l10n/sr.json164
-rw-r--r--web/l10n/ta.json164
-rw-r--r--web/l10n/th.json164
-rw-r--r--web/l10n/tr.json164
-rw-r--r--web/l10n/uk.json164
-rw-r--r--web/l10n/vi.json164
-rw-r--r--web/l10n/zh.json164
-rw-r--r--web/locale.js80
-rw-r--r--web/release.html24
153 files changed, 13951 insertions, 0 deletions
diff --git a/tools/minify.bat b/tools/minify.bat
new file mode 100644
index 0000000..6ab8fd9
--- /dev/null
+++ b/tools/minify.bat
@@ -0,0 +1,5 @@
+@echo off
+cd C:\[traccar path]\traccar\web
+set SDK=C:\[sencha path]\ext-6.0.0
+
+sencha -sdk %SDK% compile -classpath=app.js,app,%SDK%\packages\core\src,%SDK%\packages\core\overrides,%SDK%\classic\classic\src,%SDK%\classic\classic\overrides exclude -all and include -recursive -file app.js and exclude -namespace=Ext and concatenate -closure app.min.js
diff --git a/tools/minify.sh b/tools/minify.sh
new file mode 100755
index 0000000..4a5c47f
--- /dev/null
+++ b/tools/minify.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+cd $(dirname $0)/../web
+
+SDK="../../ext-6.0.1"
+
+sencha compile --classpath=app.js,app,$SDK/packages/core/src,$SDK/packages/core/overrides,$SDK/classic/classic/src,$SDK/classic/classic/overrides \
+ exclude -all \
+ and \
+ include -recursive -file app.js \
+ and \
+ exclude -namespace=Ext \
+ and \
+ concatenate -closure app.min.js
diff --git a/tools/translate.py b/tools/translate.py
new file mode 100755
index 0000000..e8324a6
--- /dev/null
+++ b/tools/translate.py
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+import os
+import optparse
+import urllib2
+import json
+import base64
+
+parser = optparse.OptionParser()
+parser.add_option("-u", "--user", dest="username", help="transifex user login")
+parser.add_option("-p", "--password", dest="password", help="transifex user password")
+
+(options, args) = parser.parse_args()
+
+if not options.username or not options.password:
+ parser.error('User name and password are required')
+
+os.chdir(os.path.dirname(os.path.abspath(__file__)))
+
+path = "../web/l10n/"
+
+def request(url):
+ req = urllib2.Request(url)
+ auth = base64.encodestring("%s:%s" % (options.username, options.password)).replace("\n", "")
+ req.add_header("Authorization", "Basic %s" % auth)
+ return urllib2.urlopen(req)
+
+resource = json.load(request("https://www.transifex.com/api/2/project/traccar/resource/web/?details"))
+
+for language in resource["available_languages"]:
+ code = language["code"]
+ data = request("https://www.transifex.com/api/2/project/traccar/resource/web/translation/" + code + "?file")
+ file = open(path + code + ".json", "wb")
+ file.write(data.read())
+ file.close()
diff --git a/web/.jscsrc b/web/.jscsrc
new file mode 100644
index 0000000..1dd27dc
--- /dev/null
+++ b/web/.jscsrc
@@ -0,0 +1,5 @@
+{
+ "preset": "crockford",
+ "maxErrors": 100,
+ "excludeFiles": ["arrow.js"]
+}
diff --git a/web/.jshintignore b/web/.jshintignore
new file mode 100644
index 0000000..6a7e8b2
--- /dev/null
+++ b/web/.jshintignore
@@ -0,0 +1,4 @@
+l10n/**
+tests/**
+locale.js
+arrow.js
diff --git a/web/.jshintrc b/web/.jshintrc
new file mode 100644
index 0000000..0d2e29d
--- /dev/null
+++ b/web/.jshintrc
@@ -0,0 +1,75 @@
+{
+ // Enforcing
+ "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
+ "camelcase" : true, // true: Identifiers must be in camelCase
+ "curly" : true, // true: Require {} for every new block or scope
+ "eqeqeq" : true, // true: Require triple equals (===) for comparison
+ "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
+ "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
+ "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
+ "indent" : 4, // {int} Number of spaces to use for indentation
+ "latedef" : true, // true: Require variables/functions to be defined before being used
+ "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
+ "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
+ "noempty" : true, // true: Prohibit use of empty blocks
+ "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
+ "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
+ "plusplus" : false, // true: Prohibit use of `++` and `--`
+ "quotmark" : "single", // Quotation mark consistency:
+ // false : do nothing (default)
+ // true : ensure whatever is used is consistent
+ // "single" : require single quotes
+ // "double" : require double quotes
+ "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
+ "unused" : "vars", // Unused variables:
+ // true : all variables, last function parameter
+ // "vars" : all variables only
+ // "strict" : all variables, all function parameters
+ "strict" : false, // true: Requires all functions run in ES5 Strict Mode
+ "maxparams" : false, // {int} Max number of formal params allowed per function
+ "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
+ "maxstatements" : false, // {int} Max number statements per function
+ "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
+ "maxlen" : false, // {int} Max number of characters per line
+ "varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed.
+
+ // Relaxing
+ "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
+ "boss" : false, // true: Tolerate assignments where comparisons would be expected
+ "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
+ "eqnull" : false, // true: Tolerate use of `== null`
+ "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
+ "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
+ "evil" : false, // true: Tolerate use of `eval` and `new Function()`
+ "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
+ "funcscope" : false, // true: Tolerate defining variables inside control statements
+ "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
+ "iterator" : false, // true: Tolerate using the `__iterator__` property
+ "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
+ "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
+ "laxcomma" : false, // true: Tolerate comma-first style coding
+ "loopfunc" : false, // true: Tolerate functions being defined in loops
+ "multistr" : false, // true: Tolerate multi-line strings
+ "noyield" : false, // true: Tolerate generator functions with no yield statement in them.
+ "notypeof" : false, // true: Tolerate invalid typeof operator values
+ "proto" : false, // true: Tolerate using the `__proto__` property
+ "scripturl" : false, // true: Tolerate script-targeted URLs
+ "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
+ "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
+ "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
+ "validthis" : false, // true: Tolerate using this in a non-constructor function
+
+ // Environments
+ "browser" : true, // Web Browser (window, document, etc)
+ "devel" : true, // Development/debugging (alert, confirm, etc)
+ "node" : true, // Node.js
+
+ // Custom Globals
+ "globals" : {
+ "Ext" : false,
+ "ol" : false,
+ "Traccar" : false,
+ "Strings" : false,
+ "Locale" : false
+ }
+}
diff --git a/web/app.css b/web/app.css
new file mode 100644
index 0000000..9541760
--- /dev/null
+++ b/web/app.css
@@ -0,0 +1,69 @@
+.view-color-green {
+ background-color: rgba(77, 250, 144, 0.3);
+}
+.view-color-yellow {
+ background-color: rgba(250, 190, 77, 0.3);
+}
+.view-color-red {
+ background-color: rgba(255, 84, 104, 0.3);
+}
+
+.x-tree-icon {
+ display: none !important;
+}
+
+.state-indicator {
+ position: absolute;
+ top: -999em;
+ left: -999em;
+ z-index: 0;
+}
+
+@media all and (max-device-width: 768px) {
+ .state-indicator {
+ z-index: 1;
+ }
+}
+
+#attribution {
+ position: absolute;
+ bottom: 10px;
+ right: 15px;
+ font-size: x-small;
+}
+
+#spinner {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ height: 60px;
+ width: 60px;
+ margin-top: -30px;
+ margin-left: -30px;
+ -webkit-animation: rotation .8s infinite linear;
+ -moz-animation: rotation .8s infinite linear;
+ -o-animation: rotation .8s infinite linear;
+ animation: rotation .8s infinite linear;
+ border-left: 6px solid rgba(56, 146, 212, .15);
+ border-right: 6px solid rgba(56, 146, 212, .15);
+ border-bottom: 6px solid rgba(56, 146, 212, .15);
+ border-top: 6px solid rgba(56, 146, 212, .8);
+ border-radius: 100%;
+}
+
+@-webkit-keyframes rotation {
+ from { -webkit-transform: rotate(0deg); }
+ to { -webkit-transform: rotate(359deg); }
+}
+@-moz-keyframes rotation {
+ from { -moz-transform: rotate(0deg); }
+ to { -moz-transform: rotate(359deg); }
+}
+@-o-keyframes rotation {
+ from { -o-transform: rotate(0deg); }
+ to { -o-transform: rotate(359deg); }
+}
+@keyframes rotation {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(359deg); }
+}
diff --git a/web/app.js b/web/app.js
new file mode 100644
index 0000000..597cce4
--- /dev/null
+++ b/web/app.js
@@ -0,0 +1,20 @@
+/*
+ * 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.application({
+ name: 'Traccar',
+ extend: 'Traccar.Application'
+});
diff --git a/web/app/Application.js b/web/app/Application.js
new file mode 100644
index 0000000..2d80653
--- /dev/null
+++ b/web/app/Application.js
@@ -0,0 +1,117 @@
+/*
+ * 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.Application', {
+ extend: 'Ext.app.Application',
+ name: 'Traccar',
+
+ requires: [
+ 'Traccar.Style',
+ 'Traccar.AttributeFormatter'
+ ],
+
+ models: [
+ 'Server',
+ 'User',
+ 'Group',
+ 'Device',
+ 'Position',
+ 'Attribute',
+ 'Command',
+ 'Event',
+ 'Geofence',
+ 'Notification',
+ 'ReportSummary',
+ 'ReportTrip'
+ ],
+
+ stores: [
+ 'Groups',
+ 'Devices',
+ 'AllGroups',
+ 'AllDevices',
+ 'Positions',
+ 'LatestPositions',
+ 'Users',
+ 'Attributes',
+ 'MapTypes',
+ 'DistanceUnits',
+ 'SpeedUnits',
+ 'CommandTypes',
+ 'TimeUnits',
+ 'Languages',
+ 'Events',
+ 'Geofences',
+ 'AllGeofences',
+ 'Notifications',
+ 'AllNotifications',
+ 'GeofenceTypes',
+ 'ReportRoute',
+ 'ReportEvents',
+ 'ReportTrips',
+ 'ReportSummary',
+ 'ReportTypes',
+ 'ReportEventTypes'
+ ],
+
+ controllers: [
+ 'Root'
+ ],
+
+ setUser: function (data) {
+ var reader = Ext.create('Ext.data.reader.Json', {
+ model: 'Traccar.model.User'
+ });
+ this.user = reader.readRecords(data).getRecords()[0];
+ },
+
+ getUser: function () {
+ return this.user;
+ },
+
+ setServer: function (data) {
+ var reader = Ext.create('Ext.data.reader.Json', {
+ model: 'Traccar.model.Server'
+ });
+ this.server = reader.readRecords(data).getRecords()[0];
+ },
+
+ getServer: function () {
+ return this.server;
+ },
+
+ getPreference: function (key, defaultValue) {
+ return this.getUser().get(key) || this.getServer().get(key) || defaultValue;
+ },
+
+ showError: function (response) {
+ var data;
+ if (Ext.isString(response)) {
+ Ext.Msg.alert(Strings.errorTitle, response);
+ } else if (response.responseText) {
+ data = Ext.decode(response.responseText);
+ if (data.details) {
+ Ext.Msg.alert(Strings.errorTitle, data.details);
+ } else {
+ Ext.Msg.alert(Strings.errorTitle, data.message);
+ }
+ } else if (response.statusText) {
+ Ext.Msg.alert(Strings.errorTitle, response.statusText);
+ } else {
+ Ext.Msg.alert(Strings.errorTitle, Strings.errorConnection);
+ }
+ }
+});
diff --git a/web/app/AttributeFormatter.js b/web/app/AttributeFormatter.js
new file mode 100644
index 0000000..1fff07b
--- /dev/null
+++ b/web/app/AttributeFormatter.js
@@ -0,0 +1,81 @@
+/*
+ * 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.AttributeFormatter', {
+ singleton: true,
+
+ coordinateFormatter: function (value) {
+ return value.toFixed(Traccar.Style.coordinatePrecision);
+ },
+
+ speedFormatter: function (value) {
+ return Ext.getStore('SpeedUnits').formatValue(value, Traccar.app.getPreference('speedUnit'));
+ },
+
+ courseFormatter: function (value) {
+ var courseValues = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'];
+ return courseValues[Math.floor(value / 45)];
+ },
+
+ distanceFormatter: function (value) {
+ return Ext.getStore('DistanceUnits').formatValue(value, Traccar.app.getPreference('distanceUnit'));
+ },
+
+ hoursFormatter: function (value) {
+ var hours = Math.round(value / 3600000);
+ return (hours + ' ' + Strings.sharedHourAbbreviation);
+ },
+
+ durationFormatter: function (value) {
+ var hours, minutes;
+ hours = Math.floor(value / 3600000);
+ minutes = Math.round((value % 3600000) / 60000);
+ return (hours + ' ' + Strings.sharedHourAbbreviation + ' ' + minutes + ' ' + Strings.sharedMinuteAbbreviation);
+ },
+
+ defaultFormatter: function (value) {
+ if (typeof value === 'number') {
+ return Number(value.toFixed(Traccar.Style.numberPrecision));
+ } else if (typeof value === 'boolean') {
+ return value ? Ext.Msg.buttonText.yes : Ext.Msg.buttonText.no;
+ } else if (value instanceof Date) {
+ if (Traccar.app.getPreference('twelveHourFormat', false)) {
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat12);
+ } else {
+ return Ext.Date.format(value, Traccar.Style.dateTimeFormat24);
+ }
+ }
+ return value;
+ },
+
+ getFormatter: function (key) {
+ if (key === 'latitude' || key === 'longitude') {
+ return this.coordinateFormatter;
+ } else if (key === 'speed') {
+ return this.speedFormatter;
+ } else if (key === 'course') {
+ return this.courseFormatter;
+ } else if (key === 'distance' || key === 'odometer' || key === 'totalDistance') {
+ return this.distanceFormatter;
+ } else if (key === 'hours') {
+ return this.hoursFormatter;
+ } else if (key === 'duration') {
+ return this.durationFormatter;
+ } else {
+ return this.defaultFormatter;
+ }
+ }
+});
diff --git a/web/app/GeofenceConverter.js b/web/app/GeofenceConverter.js
new file mode 100644
index 0000000..339f096
--- /dev/null
+++ b/web/app/GeofenceConverter.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.GeofenceConverter', {
+ singleton: true,
+
+ wktToGeometry: function (mapView, wkt) {
+ var geometry, projection, resolutionAtEquator, pointResolution, resolutionFactor, points = [], center, radius,
+ content, i, lat, lon, coordinates;
+ if (wkt.lastIndexOf('POLYGON', 0) === 0) {
+ content = wkt.match(/\([^\(\)]+\)/);
+ if (content !== null) {
+ coordinates = content[0].match(/-?\d+\.?\d*/g);
+ if (coordinates !== null) {
+ projection = mapView.getProjection();
+ for (i = 0; i < coordinates.length; i += 2) {
+ lat = Number(coordinates[i]);
+ lon = Number(coordinates[i + 1]);
+ points.push(ol.proj.transform([lon, lat], 'EPSG:4326', projection));
+ }
+ geometry = new ol.geom.Polygon([points]);
+ }
+ }
+ } else if (wkt.lastIndexOf('CIRCLE', 0) === 0) {
+ content = wkt.match(/\([^\(\)]+\)/);
+ if (content !== null) {
+ coordinates = content[0].match(/-?\d+\.?\d*/g);
+ if (coordinates !== null) {
+ projection = mapView.getProjection();
+ center = ol.proj.transform([Number(coordinates[1]), Number(coordinates[0])], 'EPSG:4326', projection);
+ resolutionAtEquator = mapView.getResolution();
+ pointResolution = projection.getPointResolution(resolutionAtEquator, center);
+ resolutionFactor = resolutionAtEquator / pointResolution;
+ radius = (Number(coordinates[2]) / ol.proj.METERS_PER_UNIT.m) * resolutionFactor;
+ geometry = new ol.geom.Circle(center, radius);
+ }
+ }
+ }
+ return geometry;
+ },
+
+ geometryToWkt: function (projection, geometry) {
+ var result, i, center, radius, edgeCoordinate, earthSphere, groundRadius, points;
+ if (geometry instanceof ol.geom.Circle) {
+ center = geometry.getCenter();
+ radius = geometry.getRadius();
+ edgeCoordinate = [center[0] + radius, center[1]];
+ center = ol.proj.transform(center, projection, 'EPSG:4326');
+ earthSphere = new ol.Sphere(6378137);
+ groundRadius = earthSphere.haversineDistance(center,
+ ol.proj.transform(edgeCoordinate, projection, 'EPSG:4326'));
+ result = 'CIRCLE (';
+ result += center[1] + ' ' + center[0] + ', ';
+ result += Number((groundRadius).toFixed(1)) + ')';
+ } else if (geometry instanceof ol.geom.Polygon) {
+ geometry.transform(projection, 'EPSG:4326');
+ points = geometry.getCoordinates();
+ result = 'POLYGON((';
+ for (i = 0; i < points[0].length; i += 1) {
+ result += points[0][i][1] + ' ' + points[0][i][0] + ', ';
+ }
+ result = result.substring(0, result.length - 2) + '))';
+ }
+ return result;
+ }
+});
diff --git a/web/app/Style.js b/web/app/Style.js
new file mode 100644
index 0000000..b3b296b
--- /dev/null
+++ b/web/app/Style.js
@@ -0,0 +1,79 @@
+/*
+ * 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.Style', {
+ singleton: true,
+
+ panelPadding: 10,
+
+ windowWidth: 640,
+ windowHeight: 480,
+
+ dateTimeFormat24: 'Y-m-d H:i:s',
+ dateTimeFormat12: 'Y-m-d g:i:s a',
+ timeFormat24: 'H:i',
+ timeFormat12: 'g:i a',
+ dateFormat: 'Y-m-d',
+ weekStartDay: 1,
+
+ deviceWidth: 350,
+
+ reportHeight: 250,
+ reportTime: 100,
+
+ mapDefaultLat: 51.507222,
+ mapDefaultLon: -0.1275,
+ mapDefaultZoom: 6,
+
+ mapRouteColor: [
+ 'rgba(21, 127, 204, 1.0)',
+ 'rgba(109, 46, 204, 1.0)',
+ 'rgba(204, 46, 162, 1.0)',
+ 'rgba(204, 46, 38, 1.0)',
+ 'rgba(128, 204, 46, 1.0)',
+ 'rgba(46, 204, 155, 1.0)'
+ ],
+ mapRouteWidth: 5,
+
+ mapArrowStrokeColor: 'rgba(50, 50, 50, 1.0)',
+ mapArrowStrokeWidth: 2,
+
+ mapTextColor: 'rgba(50, 50, 50, 1.0)',
+ mapTextStrokeColor: 'rgba(255, 255, 255, 1.0)',
+ mapTextStrokeWidth: 2,
+ mapTextOffset: 10,
+ mapTextFont: 'bold 12px sans-serif',
+
+ mapColorOnline: 'rgba(77, 250, 144, 1.0)',
+ mapColorUnknown: 'rgba(250, 190, 77, 1.0)',
+ mapColorOffline: 'rgba(255, 84, 104, 1.0)',
+
+ mapRadiusNormal: 9,
+ mapRadiusSelected: 14,
+
+ mapMaxZoom: 19,
+ mapDelay: 500,
+
+ mapGeofenceColor: 'rgba(21, 127, 204, 1.0)',
+ mapGeofenceOverlay: 'rgba(21, 127, 204, 0.2)',
+ mapGeofenceWidth: 5,
+ mapGeofenceRadius: 9,
+
+ coordinatePrecision: 6,
+ numberPrecision: 2,
+
+ reportTagfieldWidth: 375
+});
diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js
new file mode 100644
index 0000000..26f2f6f
--- /dev/null
+++ b/web/app/controller/Root.js
@@ -0,0 +1,207 @@
+/*
+ * 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.controller.Root', {
+ extend: 'Ext.app.Controller',
+
+ requires: [
+ 'Traccar.view.Login',
+ 'Traccar.view.Main',
+ 'Traccar.view.MainMobile',
+ 'Traccar.model.Position'
+ ],
+
+ init: function () {
+ var indicator = document.createElement('div');
+ indicator.className = 'state-indicator';
+ document.body.appendChild(indicator);
+ this.isPhone = parseInt(window.getComputedStyle(indicator).getPropertyValue('z-index'), 10) !== 0;
+ },
+
+ onLaunch: function () {
+ Ext.Ajax.request({
+ scope: this,
+ url: 'api/server',
+ callback: this.onServerReturn
+ });
+ },
+
+ onServerReturn: function (options, success, response) {
+ Ext.get('spinner').remove();
+ if (success) {
+ Traccar.app.setServer(Ext.decode(response.responseText));
+ Ext.Ajax.request({
+ scope: this,
+ url: 'api/session',
+ callback: this.onSessionReturn
+ });
+ } else {
+ Traccar.app.showError(response);
+ }
+ },
+
+ onSessionReturn: function (options, success, response) {
+ if (success) {
+ Traccar.app.setUser(Ext.decode(response.responseText));
+ this.loadApp();
+ } else {
+ this.login = Ext.create('widget.login', {
+ listeners: {
+ scope: this,
+ login: this.onLogin
+ }
+ });
+ this.login.show();
+ }
+ },
+
+ onLogin: function () {
+ this.login.close();
+ this.loadApp();
+ },
+
+ loadApp: function () {
+ var attribution;
+ Ext.getStore('Groups').load();
+ Ext.getStore('Geofences').load();
+ Ext.getStore('Devices').load({
+ scope: this,
+ callback: function () {
+ this.asyncUpdate(true);
+ }
+ });
+ attribution = Ext.get('attribution');
+ if (attribution) {
+ attribution.remove();
+ }
+ if (this.isPhone) {
+ Ext.create('widget.mainMobile');
+ } else {
+ Ext.create('widget.main');
+ }
+ },
+
+ beep: function () {
+ if (!this.beepSound) {
+ this.beepSound = new Audio('beep.wav');
+ }
+ this.beepSound.play();
+ },
+
+ mutePressed: function () {
+ var muteButton = Ext.getCmp('muteButton');
+ return muteButton && !muteButton.pressed;
+ },
+
+ asyncUpdate: function (first) {
+ var protocol, socket, self = this;
+ protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ socket = new WebSocket(protocol + '//' + window.location.host + window.location.pathname + 'api/socket');
+
+ socket.onclose = function (event) {
+ self.asyncUpdate(false);
+ };
+
+ socket.onmessage = function (event) {
+ var i, j, store, data, array, entity, device, typeKey, alarmKey, text, geofence;
+
+ data = Ext.decode(event.data);
+
+ if (data.devices) {
+ array = data.devices;
+ store = Ext.getStore('Devices');
+ for (i = 0; i < array.length; i++) {
+ entity = store.getById(array[i].id);
+ if (entity) {
+ entity.set({
+ status: array[i].status,
+ lastUpdate: array[i].lastUpdate
+ }, {
+ dirty: false
+ });
+ }
+ }
+ }
+
+ if (data.positions && !data.events) {
+ array = data.positions;
+ store = Ext.getStore('LatestPositions');
+ for (i = 0; i < array.length; i++) {
+ entity = store.findRecord('deviceId', array[i].deviceId, 0, false, false, true);
+ if (entity) {
+ entity.set(array[i]);
+ } else {
+ store.add(Ext.create('Traccar.model.Position', array[i]));
+ }
+ }
+ }
+
+ if (data.events) {
+ array = data.events;
+ store = Ext.getStore('Events');
+ for (i = 0; i < array.length; i++) {
+ store.add(array[i]);
+ if (array[i].type === 'commandResult' && data.positions) {
+ for (j = 0; j < data.positions.length; j++) {
+ if (data.positions[j].id === array[i].positionId) {
+ text = data.positions[j].attributes.result;
+ break;
+ }
+ }
+ text = Strings.eventCommandResult + ': ' + text;
+ } else if (array[i].type === 'alarm' && data.positions) {
+ alarmKey = 'alarm';
+ text = Strings[alarmKey];
+ if (!text) {
+ text = alarmKey;
+ }
+ for (j = 0; j < data.positions.length; j++) {
+ if (data.positions[j].id === array[i].positionId && data.positions[j].attributes.alarm !== null) {
+ if (typeof data.positions[j].attributes.alarm === 'string' && data.positions[j].attributes.alarm.length >= 2) {
+ alarmKey = 'alarm' + data.positions[j].attributes.alarm.charAt(0).toUpperCase() + data.positions[j].attributes.alarm.slice(1);
+ text = Strings[alarmKey];
+ if (!text) {
+ text = alarmKey;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ typeKey = 'event' + array[i].type.charAt(0).toUpperCase() + array[i].type.slice(1);
+ text = Strings[typeKey];
+ if (!text) {
+ text = typeKey;
+ }
+ }
+ if (array[i].geofenceId !== 0) {
+ geofence = Ext.getStore('Geofences').getById(array[i].geofenceId);
+ if (typeof geofence !== 'undefined') {
+ text += ' \"' + geofence.get('name') + '"';
+ }
+ }
+ device = Ext.getStore('Devices').getById(array[i].deviceId);
+ if (typeof device !== 'undefined') {
+ if (self.mutePressed()) {
+ self.beep();
+ }
+ Ext.toast(text, device.get('name'));
+ }
+ }
+ }
+ };
+ }
+});
diff --git a/web/app/model/Attribute.js b/web/app/model/Attribute.js
new file mode 100644
index 0000000..78acdb1
--- /dev/null
+++ b/web/app/model/Attribute.js
@@ -0,0 +1,30 @@
+/*
+ * 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.model.Attribute', {
+ extend: 'Ext.data.Model',
+
+ fields: [{
+ name: 'priority',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'value',
+ type: 'string'
+ }]
+});
diff --git a/web/app/model/Command.js b/web/app/model/Command.js
new file mode 100644
index 0000000..3e848b5
--- /dev/null
+++ b/web/app/model/Command.js
@@ -0,0 +1,30 @@
+/*
+ * 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.model.Command', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'type',
+ type: 'string'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Device.js b/web/app/model/Device.js
new file mode 100644
index 0000000..100f50f
--- /dev/null
+++ b/web/app/model/Device.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.model.Device', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'uniqueId',
+ type: 'string'
+ }, {
+ name: 'status',
+ type: 'string'
+ }, {
+ name: 'lastUpdate',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'groupId',
+ type: 'int'
+ }, {
+ name: 'geofenceIds'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Event.js b/web/app/model/Event.js
new file mode 100644
index 0000000..698ebb5
--- /dev/null
+++ b/web/app/model/Event.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.model.Event', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'type',
+ type: 'string'
+ }, {
+ name: 'serverTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'positionId',
+ type: 'int'
+ }, {
+ name: 'geofenceId',
+ type: 'int'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Geofence.js b/web/app/model/Geofence.js
new file mode 100644
index 0000000..a832455
--- /dev/null
+++ b/web/app/model/Geofence.js
@@ -0,0 +1,36 @@
+/*
+ * 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.Geofence', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'description',
+ type: 'string'
+ }, {
+ name: 'area',
+ type: 'string'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Group.js b/web/app/model/Group.js
new file mode 100644
index 0000000..bb18b5b
--- /dev/null
+++ b/web/app/model/Group.js
@@ -0,0 +1,33 @@
+/*
+ * 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'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Notification.js b/web/app/model/Notification.js
new file mode 100644
index 0000000..9b4d61e
--- /dev/null
+++ b/web/app/model/Notification.js
@@ -0,0 +1,33 @@
+/*
+ * 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.Notification', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'type',
+ type: 'string'
+ }, {
+ name: 'userId',
+ type: 'int'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/Position.js b/web/app/model/Position.js
new file mode 100644
index 0000000..e559a7e
--- /dev/null
+++ b/web/app/model/Position.js
@@ -0,0 +1,66 @@
+/*
+ * 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.model.Position', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'protocol',
+ type: 'string'
+ }, {
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'serverTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'deviceTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'fixTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'valid',
+ type: 'boolean'
+ }, {
+ name: 'latitude',
+ type: 'float'
+ }, {
+ name: 'longitude',
+ type: 'float'
+ }, {
+ name: 'altitude',
+ type: 'float'
+ }, {
+ name: 'speed',
+ type: 'float'
+ }, {
+ name: 'course',
+ type: 'float'
+ }, {
+ name: 'address',
+ type: 'string'
+ }, {
+ name: 'attributes'
+ }]
+});
diff --git a/web/app/model/ReportSummary.js b/web/app/model/ReportSummary.js
new file mode 100644
index 0000000..430d00b
--- /dev/null
+++ b/web/app/model/ReportSummary.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.model.ReportSummary', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'deviceName',
+ type: 'string'
+ }, {
+ name: 'maxSpeed',
+ type: 'float'
+ }, {
+ name: 'averageSpeed',
+ type: 'float'
+ }, {
+ name: 'distance',
+ type: 'float'
+ }, {
+ name: 'engineHours',
+ type: 'int'
+ }]
+});
diff --git a/web/app/model/ReportTrip.js b/web/app/model/ReportTrip.js
new file mode 100644
index 0000000..cbd03d7
--- /dev/null
+++ b/web/app/model/ReportTrip.js
@@ -0,0 +1,55 @@
+/*
+ * 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.model.ReportTrip', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'deviceId',
+ type: 'int'
+ }, {
+ name: 'deviceName',
+ type: 'string'
+ }, {
+ name: 'maxSpeed',
+ type: 'float'
+ }, {
+ name: 'averageSpeed',
+ type: 'float'
+ }, {
+ name: 'distance',
+ type: 'float'
+ }, {
+ name: 'duration',
+ type: 'int'
+ }, {
+ name: 'startTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'startAddress',
+ type: 'string'
+ }, {
+ name: 'endTime',
+ type: 'date',
+ dateFormat: 'c'
+ }, {
+ name: 'endAddress',
+ type: 'string'
+ }]
+});
diff --git a/web/app/model/Server.js b/web/app/model/Server.js
new file mode 100644
index 0000000..2ed8f12
--- /dev/null
+++ b/web/app/model/Server.js
@@ -0,0 +1,72 @@
+/*
+ * 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.model.Server', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'registration',
+ type: 'boolean'
+ }, {
+ name: 'readonly',
+ type: 'boolean'
+ }, {
+ name: 'map',
+ type: 'string'
+ }, {
+ name: 'bingKey',
+ type: 'string'
+ }, {
+ name: 'mapUrl',
+ type: 'string'
+ }, {
+ name: 'distanceUnit',
+ type: 'string'
+ }, {
+ name: 'speedUnit',
+ type: 'string'
+ }, {
+ name: 'latitude',
+ type: 'float'
+ }, {
+ name: 'longitude',
+ type: 'float'
+ }, {
+ name: 'zoom',
+ type: 'int'
+ }, {
+ name: 'twelveHourFormat',
+ type: 'boolean'
+ }, {
+ name: 'attributes'
+ }],
+
+ proxy: {
+ type: 'ajax',
+ url: 'api/server',
+ actionMethods: {
+ update: 'PUT'
+ },
+ writer: {
+ type: 'json',
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/model/User.js b/web/app/model/User.js
new file mode 100644
index 0000000..b162bad
--- /dev/null
+++ b/web/app/model/User.js
@@ -0,0 +1,69 @@
+/*
+ * 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.model.User', {
+ extend: 'Ext.data.Model',
+ identifier: 'negative',
+
+ fields: [{
+ name: 'id',
+ type: 'int'
+ }, {
+ name: 'name',
+ type: 'string'
+ }, {
+ name: 'email',
+ type: 'string'
+ }, {
+ name: 'password',
+ type: 'string'
+ }, {
+ name: 'admin',
+ type: 'boolean'
+ }, {
+ name: 'map',
+ type: 'string'
+ }, {
+ name: 'distanceUnit',
+ type: 'string'
+ }, {
+ name: 'speedUnit',
+ type: 'string'
+ }, {
+ name: 'latitude',
+ type: 'float'
+ }, {
+ name: 'longitude',
+ type: 'float'
+ }, {
+ name: 'zoom',
+ type: 'int'
+ }, {
+ name: 'twelveHourFormat',
+ type: 'boolean'
+ }, {
+ name: 'attributes'
+ }],
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users',
+ writer: {
+ type: 'json',
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/AllDevices.js b/web/app/store/AllDevices.js
new file mode 100644
index 0000000..3f51926
--- /dev/null
+++ b/web/app/store/AllDevices.js
@@ -0,0 +1,28 @@
+/*
+ * 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.store.AllDevices', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Device',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/devices',
+ extraParams: {
+ all: true
+ }
+ }
+});
diff --git a/web/app/store/AllGeofences.js b/web/app/store/AllGeofences.js
new file mode 100644
index 0000000..3520996
--- /dev/null
+++ b/web/app/store/AllGeofences.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.AllGeofences', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Geofence',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/geofences',
+ extraParams: {
+ all: true
+ }
+ }
+});
diff --git a/web/app/store/AllGroups.js b/web/app/store/AllGroups.js
new file mode 100644
index 0000000..8ce0cc2
--- /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/AllNotifications.js b/web/app/store/AllNotifications.js
new file mode 100644
index 0000000..9e9cb79
--- /dev/null
+++ b/web/app/store/AllNotifications.js
@@ -0,0 +1,30 @@
+/*
+ * 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.store.AllNotifications', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Notification',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users/notifications',
+ extraParams: {
+ all: true
+ }
+ },
+ sortOnLoad: true,
+ sorters: { property: 'type', direction : 'ASC' }
+});
diff --git a/web/app/store/Attributes.js b/web/app/store/Attributes.js
new file mode 100644
index 0000000..2019582
--- /dev/null
+++ b/web/app/store/Attributes.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.store.Attributes', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Attribute',
+
+ sorters: [{
+ property: 'priority'
+ }]
+});
diff --git a/web/app/store/CommandTypes.js b/web/app/store/CommandTypes.js
new file mode 100644
index 0000000..48405db
--- /dev/null
+++ b/web/app/store/CommandTypes.js
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Gabor Somogyi (gabor.g.somogyi@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.CommandTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['type', 'name'],
+
+ listeners: {
+ 'beforeload' : function (store) {
+ var proxy;
+ proxy = store.getProxy();
+ proxy.setUrl('api/commandtypes?deviceId' + proxy.extraParams.deviceId);
+ }
+ },
+
+ proxy: {
+ type: 'rest',
+ url: '',
+ reader: {
+ type: 'json',
+ getData: function (data) {
+ Ext.each(data, function (entry) {
+ var nameKey, name;
+ entry.name = entry.type;
+ if (typeof entry.type !== 'undefined') {
+ nameKey = 'command' + entry.type.charAt(0).toUpperCase() + entry.type.slice(1);
+ name = Strings[nameKey];
+ if (typeof name !== 'undefined') {
+ entry.name = name;
+ }
+ }
+ });
+ return data;
+ }
+ }
+ }
+});
diff --git a/web/app/store/Devices.js b/web/app/store/Devices.js
new file mode 100644
index 0000000..c3c3733
--- /dev/null
+++ b/web/app/store/Devices.js
@@ -0,0 +1,28 @@
+/*
+ * 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.store.Devices', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Device',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/devices',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/DistanceUnits.js b/web/app/store/DistanceUnits.js
new file mode 100644
index 0000000..2805d52
--- /dev/null
+++ b/web/app/store/DistanceUnits.js
@@ -0,0 +1,39 @@
+/*
+ * 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.store.DistanceUnits', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name', 'factor'],
+
+ data: [{
+ key: 'km',
+ name: Strings.sharedKm,
+ factor: 0.001
+ }, {
+ key: 'mi',
+ name: Strings.sharedMi,
+ factor: 0.000621371
+ }],
+
+ formatValue: function (value, unit) {
+ var model;
+ if (!unit) {
+ unit = 'km';
+ }
+ model = this.findRecord('key', unit);
+ return (value * model.get('factor')).toFixed(2) + ' ' + model.get('name');
+ }
+});
diff --git a/web/app/store/Events.js b/web/app/store/Events.js
new file mode 100644
index 0000000..2698933
--- /dev/null
+++ b/web/app/store/Events.js
@@ -0,0 +1,25 @@
+/*
+ * 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.store.Events', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Event',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/events'
+ }
+});
diff --git a/web/app/store/GeofenceTypes.js b/web/app/store/GeofenceTypes.js
new file mode 100644
index 0000000..68c76be
--- /dev/null
+++ b/web/app/store/GeofenceTypes.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.GeofenceTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name'],
+
+ data: [{
+ key: 'Polygon',
+ name: Strings.mapShapePolygon
+ }, {
+ key: 'Circle',
+ name: Strings.mapShapeCircle
+ }]
+});
diff --git a/web/app/store/Geofences.js b/web/app/store/Geofences.js
new file mode 100644
index 0000000..a0b01ae
--- /dev/null
+++ b/web/app/store/Geofences.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.Geofences', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Geofence',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/geofences',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/store/Groups.js b/web/app/store/Groups.js
new file mode 100644
index 0000000..8740b25
--- /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/store/Languages.js b/web/app/store/Languages.js
new file mode 100644
index 0000000..027c96b
--- /dev/null
+++ b/web/app/store/Languages.js
@@ -0,0 +1,33 @@
+/*
+ * 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.store.Languages', {
+ extend: 'Ext.data.Store',
+ fields: ['code', 'name'],
+
+ data: (function () {
+ var code, data = [];
+ for (code in Locale.languages) {
+ if (Locale.languages.hasOwnProperty(code)) {
+ data.push({
+ code: code,
+ name: Locale.languages[code].name
+ });
+ }
+ }
+ return data;
+ })()
+});
diff --git a/web/app/store/LatestPositions.js b/web/app/store/LatestPositions.js
new file mode 100644
index 0000000..c656bdc
--- /dev/null
+++ b/web/app/store/LatestPositions.js
@@ -0,0 +1,20 @@
+/*
+ * 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.store.LatestPositions', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Position'
+});
diff --git a/web/app/store/MapTypes.js b/web/app/store/MapTypes.js
new file mode 100644
index 0000000..4c26ad4
--- /dev/null
+++ b/web/app/store/MapTypes.js
@@ -0,0 +1,34 @@
+/*
+ * 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.store.MapTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name'],
+
+ data: [{
+ key: 'osm',
+ name: Strings.mapOsm
+ }, {
+ key: 'bingRoad',
+ name: Strings.mapBingRoad
+ }, {
+ key: 'bingAerial',
+ name: Strings.mapBingAerial
+ }, {
+ key: 'custom',
+ name: Strings.mapCustom
+ }]
+});
diff --git a/web/app/store/Notifications.js b/web/app/store/Notifications.js
new file mode 100644
index 0000000..04cd9b8
--- /dev/null
+++ b/web/app/store/Notifications.js
@@ -0,0 +1,25 @@
+/*
+ * 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.store.Notifications', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Notification',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users/notifications'
+ }
+});
diff --git a/web/app/store/Positions.js b/web/app/store/Positions.js
new file mode 100644
index 0000000..8675983
--- /dev/null
+++ b/web/app/store/Positions.js
@@ -0,0 +1,25 @@
+/*
+ * 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.store.Positions', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Position',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/positions'
+ }
+});
diff --git a/web/app/store/ReportEventTypes.js b/web/app/store/ReportEventTypes.js
new file mode 100644
index 0000000..27bc1fd
--- /dev/null
+++ b/web/app/store/ReportEventTypes.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 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.store.ReportEventTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['type', 'name'],
+
+ statics: {
+ allEvents: '%'
+ }
+});
diff --git a/web/app/store/ReportEvents.js b/web/app/store/ReportEvents.js
new file mode 100644
index 0000000..1759ffd
--- /dev/null
+++ b/web/app/store/ReportEvents.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.ReportEvents', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Event',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/events',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportRoute.js b/web/app/store/ReportRoute.js
new file mode 100644
index 0000000..ab6da94
--- /dev/null
+++ b/web/app/store/ReportRoute.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.ReportRoute', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.Position',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/route',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportSummary.js b/web/app/store/ReportSummary.js
new file mode 100644
index 0000000..7c9a4fc
--- /dev/null
+++ b/web/app/store/ReportSummary.js
@@ -0,0 +1,29 @@
+/*
+ * 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.store.ReportSummary', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.ReportSummary',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/summary',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportTrips.js b/web/app/store/ReportTrips.js
new file mode 100644
index 0000000..e0d86aa
--- /dev/null
+++ b/web/app/store/ReportTrips.js
@@ -0,0 +1,29 @@
+/*
+ * 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.store.ReportTrips', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.ReportTrip',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/reports/trips',
+ headers: {
+ 'Accept': 'application/json'
+ }
+ }
+});
diff --git a/web/app/store/ReportTypes.js b/web/app/store/ReportTypes.js
new file mode 100644
index 0000000..09ef61d
--- /dev/null
+++ b/web/app/store/ReportTypes.js
@@ -0,0 +1,34 @@
+/*
+ * 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.ReportTypes', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name'],
+
+ data: [{
+ key: 'route',
+ name: Strings.reportRoute
+ }, {
+ key: 'events',
+ name: Strings.reportEvents
+ }, {
+ key: 'trips',
+ name: Strings.reportTrips
+ }, {
+ key: 'summary',
+ name: Strings.reportSummary
+ }]
+});
diff --git a/web/app/store/SpeedUnits.js b/web/app/store/SpeedUnits.js
new file mode 100644
index 0000000..296f586
--- /dev/null
+++ b/web/app/store/SpeedUnits.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.store.SpeedUnits', {
+ extend: 'Ext.data.Store',
+ fields: ['key', 'name', 'factor'],
+
+ data: [{
+ key: 'kn',
+ name: Strings.sharedKn,
+ factor: 1
+ }, {
+ key: 'kmh',
+ name: Strings.sharedKmh,
+ factor: 1.852
+ }, {
+ key: 'mph',
+ name: Strings.sharedMph,
+ factor: 1.15078
+ }],
+
+ formatValue: function (value, unit) {
+ var model;
+ if (!unit) {
+ unit = 'kn';
+ }
+ model = this.findRecord('key', unit);
+ return (value * model.get('factor')).toFixed(1) + ' ' + model.get('name');
+ }
+});
diff --git a/web/app/store/TimeUnits.js b/web/app/store/TimeUnits.js
new file mode 100644
index 0000000..e032638
--- /dev/null
+++ b/web/app/store/TimeUnits.js
@@ -0,0 +1,31 @@
+/*
+ * 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.store.TimeUnits', {
+ extend: 'Ext.data.Store',
+ fields: ['name', 'factor'],
+
+ data: [{
+ name: Strings.sharedSecond,
+ factor: 1
+ }, {
+ name: Strings.sharedMinute,
+ factor: 60
+ }, {
+ name: Strings.sharedHour,
+ factor: 3600
+ }]
+});
diff --git a/web/app/store/Users.js b/web/app/store/Users.js
new file mode 100644
index 0000000..53a49ff
--- /dev/null
+++ b/web/app/store/Users.js
@@ -0,0 +1,28 @@
+/*
+ * 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.store.Users', {
+ extend: 'Ext.data.Store',
+ model: 'Traccar.model.User',
+
+ proxy: {
+ type: 'rest',
+ url: 'api/users',
+ writer: {
+ writeAllFields: true
+ }
+ }
+});
diff --git a/web/app/view/AttributeController.js b/web/app/view/AttributeController.js
new file mode 100644
index 0000000..932a643
--- /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 0000000..213891e
--- /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 0000000..4bc7d55
--- /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 0000000..91d69a8
--- /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 0000000..fb09f12
--- /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 0000000..1af095c
--- /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 0000000..79fd8f2
--- /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 0000000..62b2c57
--- /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 0000000..ea0efa9
--- /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 0000000..b6c777d
--- /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 0000000..a374ab0
--- /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 0000000..4020065
--- /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 0000000..1bd8c73
--- /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 0000000..e88618f
--- /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 0000000..9e2c12a
--- /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 0000000..ab6436e
--- /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 0000000..68dd160
--- /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 0000000..523d27e
--- /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 0000000..febef23
--- /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 0000000..5638db3
--- /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 0000000..933df23
--- /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 0000000..c508127
--- /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 0000000..4a5fc9e
--- /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 0000000..5faee13
--- /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 0000000..032397a
--- /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 0000000..8ef2984
--- /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 0000000..59d20df
--- /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 0000000..06057fd
--- /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 0000000..db3c552
--- /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 0000000..698cc7f
--- /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 0000000..c15faab
--- /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 0000000..fe26426
--- /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 0000000..8ff57c0
--- /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 0000000..3b0db6b
--- /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 0000000..4f202d1
--- /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 0000000..5ff5f06
--- /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 0000000..4e041eb
--- /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 0000000..198e10b
--- /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 0000000..b79c5f5
--- /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 0000000..7e77ef4
--- /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 0000000..df0c1ce
--- /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 0000000..547bd29
--- /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 0000000..19baec8
--- /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 0000000..46c76ff
--- /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 0000000..bf18442
--- /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 0000000..ebaa700
--- /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 0000000..547fb0c
--- /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 0000000..567a392
--- /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 0000000..6a1a718
--- /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 0000000..f9e704e
--- /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 0000000..52d649b
--- /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 0000000..03a02ff
--- /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 0000000..84032f0
--- /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 0000000..4abfff1
--- /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 0000000..fad5f24
--- /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);
+ }
+});
diff --git a/web/arrow.js b/web/arrow.js
new file mode 100644
index 0000000..5c09e2c
--- /dev/null
+++ b/web/arrow.js
@@ -0,0 +1,526 @@
+goog.provide('ol.style.Arrow');
+
+goog.require('ol');
+goog.require('ol.color');
+goog.require('ol.dom');
+goog.require('ol.has');
+goog.require('ol.render.canvas');
+goog.require('ol.style.Image');
+goog.require('ol.style.ImageState');
+
+
+
+/**
+ * @classdesc
+ * Set arrow style for vector features.
+ *
+ * @constructor
+ * @param {olx.style.ArrowOptions} options Options.
+ * @extends {ol.style.Image}
+ * @api
+ */
+ol.style.Arrow = function(options) {
+
+ goog.DEBUG && console.assert(options.radius !== undefined,
+ 'must provide "radius"');
+
+ /**
+ * @private
+ * @type {Array.<string>}
+ */
+ this.checksums_ = null;
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.canvas_ = null;
+
+ /**
+ * @private
+ * @type {HTMLCanvasElement}
+ */
+ this.hitDetectionCanvas_ = null;
+
+ /**
+ * @private
+ * @type {ol.style.Fill}
+ */
+ this.fill_ = options.fill !== undefined ? options.fill : null;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.origin_ = [0, 0];
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.radius_ = /** @type {number} */ (options.radius !== undefined ?
+ options.radius : options.radius1);
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.frontAngle_ = options.frontAngle !== undefined ?
+ options.frontAngle : Math.PI / 5;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.backAngle_ = options.backAngle !== undefined ?
+ options.backAngle : 4 * Math.PI / 5;
+
+ /**
+ * @private
+ * @type {ol.style.Stroke}
+ */
+ this.stroke_ = options.stroke !== undefined ? options.stroke : null;
+
+ /**
+ * @private
+ * @type {Array.<number>}
+ */
+ this.anchor_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.size_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.imageSize_ = null;
+
+ /**
+ * @private
+ * @type {ol.Size}
+ */
+ this.hitDetectionImageSize_ = null;
+
+ this.render_(options.atlasManager);
+
+ /**
+ * @type {boolean}
+ */
+ var snapToPixel = options.snapToPixel !== undefined ?
+ options.snapToPixel : true;
+
+ /**
+ * @type {boolean}
+ */
+ var rotateWithView = options.rotateWithView !== undefined ?
+ options.rotateWithView : false;
+
+ ol.style.Image.call(this, {
+ opacity: 1,
+ rotateWithView: rotateWithView,
+ rotation: options.rotation !== undefined ? options.rotation : 0,
+ scale: 1,
+ snapToPixel: snapToPixel
+ });
+
+};
+ol.inherits(ol.style.Arrow, ol.style.Image);
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Arrow.prototype.getAnchor = function() {
+ return this.anchor_;
+};
+
+
+/**
+ * Get front angle of the arrow.
+ * @return {number} Angle in radians.
+ * @api
+ */
+ol.style.Arrow.prototype.getFrontAngle = function() {
+ return this.frontAngle_;
+};
+
+
+/**
+ * Get back angle of the arrow.
+ * @return {number} Angle in radians.
+ * @api
+ */
+ol.style.Arrow.prototype.getBackAngle = function() {
+ return this.backAngle_;
+};
+
+
+/**
+ * Get the fill style for the arrow.
+ * @return {ol.style.Fill} Fill style.
+ * @api
+ */
+ol.style.Arrow.prototype.getFill = function() {
+ return this.fill_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Arrow.prototype.getHitDetectionImage = function(pixelRatio) {
+ return this.hitDetectionCanvas_;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Arrow.prototype.getImage = function(pixelRatio) {
+ return this.canvas_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Arrow.prototype.getImageSize = function() {
+ return this.imageSize_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Arrow.prototype.getHitDetectionImageSize = function() {
+ return this.hitDetectionImageSize_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Arrow.prototype.getImageState = function() {
+ return ol.style.ImageState.LOADED;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Arrow.prototype.getOrigin = function() {
+ return this.origin_;
+};
+
+
+/**
+ * Get the (primary) radius for the arrow.
+ * @return {number} Radius.
+ * @api
+ */
+ol.style.Arrow.prototype.getRadius = function() {
+ return this.radius_;
+};
+
+
+/**
+ * @inheritDoc
+ * @api
+ */
+ol.style.Arrow.prototype.getSize = function() {
+ return this.size_;
+};
+
+
+/**
+ * Get the stroke style for the arrow.
+ * @return {ol.style.Stroke} Stroke style.
+ * @api
+ */
+ol.style.Arrow.prototype.getStroke = function() {
+ return this.stroke_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Arrow.prototype.listenImageChange = ol.nullFunction;
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Arrow.prototype.load = ol.nullFunction;
+
+
+/**
+ * @inheritDoc
+ */
+ol.style.Arrow.prototype.unlistenImageChange = ol.nullFunction;
+
+
+/**
+ * @typedef {{
+ * strokeStyle: (string|undefined),
+ * strokeWidth: number,
+ * size: number,
+ * lineCap: string,
+ * lineDash: Array.<number>,
+ * lineJoin: string,
+ * miterLimit: number
+ * }}
+ */
+ol.ArrowRenderOptions;
+
+
+/**
+ * @private
+ * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
+ */
+ol.style.Arrow.prototype.render_ = function(atlasManager) {
+ var imageSize;
+ var lineCap = '';
+ var lineJoin = '';
+ var miterLimit = 0;
+ var lineDash = null;
+ var strokeStyle;
+ var strokeWidth = 0;
+
+ if (this.stroke_) {
+ strokeStyle = ol.color.asString(this.stroke_.getColor());
+ strokeWidth = this.stroke_.getWidth();
+ if (strokeWidth === undefined) {
+ strokeWidth = ol.render.canvas.defaultLineWidth;
+ }
+ lineDash = this.stroke_.getLineDash();
+ if (!ol.has.CANVAS_LINE_DASH) {
+ lineDash = null;
+ }
+ lineJoin = this.stroke_.getLineJoin();
+ if (lineJoin === undefined) {
+ lineJoin = ol.render.canvas.defaultLineJoin;
+ }
+ lineCap = this.stroke_.getLineCap();
+ if (lineCap === undefined) {
+ lineCap = ol.render.canvas.defaultLineCap;
+ }
+ miterLimit = this.stroke_.getMiterLimit();
+ if (miterLimit === undefined) {
+ miterLimit = ol.render.canvas.defaultMiterLimit;
+ }
+ }
+
+ var size = 2 * (this.radius_ + strokeWidth) + 1;
+
+ /** @type {ol.ArrowRenderOptions} */
+ var renderOptions = {
+ strokeStyle: strokeStyle,
+ strokeWidth: strokeWidth,
+ size: size,
+ lineCap: lineCap,
+ lineDash: lineDash,
+ lineJoin: lineJoin,
+ miterLimit: miterLimit
+ };
+
+ if (atlasManager === undefined) {
+ // no atlas manager is used, create a new canvas
+ var context = ol.dom.createCanvasContext2D(size, size);
+ this.canvas_ = context.canvas;
+
+ // canvas.width and height are rounded to the closest integer
+ size = this.canvas_.width;
+ imageSize = size;
+
+ this.draw_(renderOptions, context, 0, 0);
+
+ this.createHitDetectionCanvas_(renderOptions);
+ } else {
+ // an atlas manager is used, add the symbol to an atlas
+ size = Math.round(size);
+
+ var hasCustomHitDetectionImage = !this.fill_;
+ var renderHitDetectionCallback;
+ if (hasCustomHitDetectionImage) {
+ // render the hit-detection image into a separate atlas image
+ renderHitDetectionCallback =
+ this.drawHitDetectionCanvas_.bind(this, renderOptions);
+ }
+
+ var id = this.getChecksum();
+ var info = atlasManager.add(
+ id, size, size, this.draw_.bind(this, renderOptions),
+ renderHitDetectionCallback);
+ goog.DEBUG && console.assert(info, 'arrow size is too large');
+
+ this.canvas_ = info.image;
+ this.origin_ = [info.offsetX, info.offsetY];
+ imageSize = info.image.width;
+
+ if (hasCustomHitDetectionImage) {
+ this.hitDetectionCanvas_ = info.hitImage;
+ this.hitDetectionImageSize_ =
+ [info.hitImage.width, info.hitImage.height];
+ } else {
+ this.hitDetectionCanvas_ = this.canvas_;
+ this.hitDetectionImageSize_ = [imageSize, imageSize];
+ }
+ }
+
+ this.anchor_ = [size / 2, size / 2];
+ this.size_ = [size, size];
+ this.imageSize_ = [imageSize, imageSize];
+};
+
+
+/**
+ * @private
+ * @param {ol.ArrowRenderOptions} 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).
+ */
+ol.style.Arrow.prototype.draw_ = function(renderOptions, context, x, y) {
+ var innerRadius = this.radius_ / Math.sin(Math.PI - this.backAngle_ / 2) *
+ Math.sin(this.backAngle_ / 2 - this.frontAngle_);
+
+ // reset transform
+ context.setTransform(1, 0, 0, 1, 0, 0);
+
+ // then move to (x, y)
+ context.translate(x, y);
+
+
+ context.beginPath();
+
+ function lineTo(radius, angle) {
+ context.lineTo(
+ renderOptions.size / 2 + radius * Math.cos(angle + Math.PI / 2),
+ renderOptions.size / 2 - radius * Math.sin(angle + Math.PI / 2));
+ }
+
+ lineTo(this.radius_, 0);
+ lineTo(this.radius_, Math.PI - this.frontAngle_);
+ lineTo(innerRadius, Math.PI);
+ lineTo(this.radius_, this.frontAngle_ - Math.PI);
+ lineTo(this.radius_, 0);
+
+ if (this.fill_) {
+ context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor());
+ context.fill();
+ }
+ if (this.stroke_) {
+ context.strokeStyle = renderOptions.strokeStyle;
+ context.lineWidth = renderOptions.strokeWidth;
+ if (renderOptions.lineDash) {
+ context.setLineDash(renderOptions.lineDash);
+ }
+ context.lineCap = renderOptions.lineCap;
+ context.lineJoin = renderOptions.lineJoin;
+ context.miterLimit = renderOptions.miterLimit;
+ context.stroke();
+ }
+ context.closePath();
+};
+
+
+/**
+ * @private
+ * @param {ol.ArrowRenderOptions} renderOptions Render options.
+ */
+ol.style.Arrow.prototype.createHitDetectionCanvas_ = function(renderOptions) {
+ this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
+ if (this.fill_) {
+ this.hitDetectionCanvas_ = this.canvas_;
+ return;
+ }
+
+ // if no fill style is set, create an extra hit-detection image with a
+ // default fill style
+ var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size);
+ this.hitDetectionCanvas_ = context.canvas;
+
+ this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
+};
+
+
+/**
+ * @private
+ * @param {ol.ArrowRenderOptions} 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) {
+ var innerRadius = this.radius_ / Math.sin(Math.PI - this.backAngle_ / 2) *
+ Math.sin(this.backAngle_ / 2 - this.frontAngle_);
+
+ // reset transform
+ context.setTransform(1, 0, 0, 1, 0, 0);
+
+ // then move to (x, y)
+ context.translate(x, y);
+
+ context.beginPath();
+
+ function lineTo(radius, angle) {
+ context.lineTo(
+ renderOptions.size / 2 + radius * Math.cos(angle + Math.PI / 2),
+ renderOptions.size / 2 - radius * Math.sin(angle + Math.PI / 2));
+ }
+
+ lineTo(this.radius_, 0);
+ lineTo(this.radius_, Math.PI - this.frontAngle_);
+ lineTo(innerRadius / 2, Math.PI);
+ lineTo(this.radius_, this.frontAngle_ - Math.PI);
+ lineTo(this.radius_, 0);
+
+ context.fillStyle = ol.render.canvas.defaultFillStyle;
+ context.fill();
+ if (this.stroke_) {
+ context.strokeStyle = renderOptions.strokeStyle;
+ context.lineWidth = renderOptions.strokeWidth;
+ if (renderOptions.lineDash) {
+ context.setLineDash(renderOptions.lineDash);
+ }
+ context.stroke();
+ }
+ context.closePath();
+};
+
+
+/**
+ * @return {string} The checksum.
+ */
+ol.style.Arrow.prototype.getChecksum = function() {
+ var strokeChecksum = this.stroke_ ?
+ this.stroke_.getChecksum() : '-';
+ var fillChecksum = this.fill_ ?
+ this.fill_.getChecksum() : '-';
+
+ var recalculate = !this.checksums_ ||
+ (strokeChecksum != this.checksums_[1] ||
+ fillChecksum != this.checksums_[2] ||
+ this.radius_ != this.checksums_[3] ||
+ this.frontAngle_ != this.checksums_[4] ||
+ this.backAngle_ != this.checksums_[5]);
+
+ if (recalculate) {
+ var checksum = 'r' + strokeChecksum + fillChecksum +
+ (this.radius_ !== undefined ? this.radius_.toString() : '-') +
+ (this.frontAngle_ !== undefined ? this.frontAngle_.toString() : '-') +
+ (this.backAngle_ !== undefined ? this.backAngle_.toString() : '-');
+ this.checksums_ = [checksum, strokeChecksum, fillChecksum,
+ this.radius_, this.frontAngle_, this.backAngle_];
+ }
+
+ return this.checksums_[0];
+};
diff --git a/web/beep.wav b/web/beep.wav
new file mode 100644
index 0000000..c2364f1
--- /dev/null
+++ b/web/beep.wav
Binary files differ
diff --git a/web/debug.html b/web/debug.html
new file mode 100644
index 0000000..ff42ac8
--- /dev/null
+++ b/web/debug.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<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.1/classic/theme-neptune/resources/theme-neptune-all.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.18.2/ol.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css">
+<link rel="stylesheet" href="app.css">
+</head>
+<body>
+<div id="spinner"></div>
+<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.1/ext-all-debug.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.1/classic/theme-neptune/theme-neptune.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.18.2/ol-debug.js"></script>
+<script src="arrow.js"></script>
+<script type="text/javascript">
+Ext.Loader.setConfig({ disableCaching: false });
+</script>
+<script src="locale.js"></script>
+<script src="app.js"></script>
+</body>
+</html>
diff --git a/web/favicon.ico b/web/favicon.ico
new file mode 100644
index 0000000..1918dff
--- /dev/null
+++ b/web/favicon.ico
Binary files differ
diff --git a/web/l10n/ar.json b/web/l10n/ar.json
new file mode 100644
index 0000000..c22bdbc
--- /dev/null
+++ b/web/l10n/ar.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "جاري التحميل...",
+ "sharedSave": "حفظ",
+ "sharedCancel": "إلغاء",
+ "sharedAdd": "إضافة",
+ "sharedEdit": "تعديل",
+ "sharedRemove": "حذف",
+ "sharedRemoveConfirm": "حذف العنصر؟",
+ "sharedKm": "كم",
+ "sharedMi": "ميل",
+ "sharedKn": "عقدة",
+ "sharedKmh": "كم/ساعه",
+ "sharedMph": "ميل/ساعة",
+ "sharedHour": "ساعه",
+ "sharedMinute": "دقيقة",
+ "sharedSecond": "ثانية",
+ "sharedName": "الاسم",
+ "sharedDescription": "الوصف",
+ "sharedSearch": "بحث",
+ "sharedGeofence": "السياج الجغرافي",
+ "sharedGeofences": "السياجات الجغرافية",
+ "sharedNotifications": "التنبيهات",
+ "sharedAttributes": "الخصائص",
+ "sharedAttribute": "خاصية",
+ "sharedArea": "منطقة",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "خطأ",
+ "errorUnknown": "خطأ غير معروف",
+ "errorConnection": "خطأ في الاتصال",
+ "userEmail": "بريد إلكتروني",
+ "userPassword": "كلمة المرور",
+ "userAdmin": "مدير النظام",
+ "userRemember": "Remember",
+ "loginTitle": "تسجيل الدخول",
+ "loginLanguage": "اللغة",
+ "loginRegister": "تسجيل جديد",
+ "loginLogin": "تسجيل الدخول",
+ "loginFailed": "كلمة مرور او بريد خاطئ",
+ "loginCreated": "تم تسجيل مستخدم جديد",
+ "loginLogout": "خروج",
+ "devicesAndState": "الأجهزة والحالة",
+ "deviceDialog": "جهاز",
+ "deviceTitle": "أجهزة",
+ "deviceIdentifier": "المعرف",
+ "deviceLastUpdate": "آخر تحديث",
+ "deviceCommand": "أمر ",
+ "deviceFollow": "متابعة",
+ "groupDialog": "مجموعة",
+ "groupParent": "مجموعة",
+ "groupNoGroup": "لا توجد مجموعة",
+ "settingsTitle": "إعدادات",
+ "settingsUser": "حساب",
+ "settingsGroups": "المجموعات",
+ "settingsServer": "خادم",
+ "settingsUsers": "المستخدمون",
+ "settingsSpeedUnit": "سرعة",
+ "settingsTwelveHourFormat": "صيغة 12-ساعة",
+ "reportTitle": "تقارير",
+ "reportDevice": "جهاز",
+ "reportGroup": "Group",
+ "reportFrom": "من",
+ "reportTo": "الي",
+ "reportShow": "اظهار",
+ "reportClear": "تفريغ الحقول",
+ "positionFixTime": "وقت",
+ "positionValid": "صالح",
+ "positionLatitude": "خط العرض",
+ "positionLongitude": "خط الطول",
+ "positionAltitude": "ارتفاع عن سطح البحر",
+ "positionSpeed": "السرعة",
+ "positionCourse": "دورة",
+ "positionAddress": "العنوان",
+ "positionProtocol": "بروتوكول",
+ "serverTitle": "اعدادت الخادم",
+ "serverZoom": "تقريب",
+ "serverRegistration": "تسجيل",
+ "serverReadonly": "قراءة فقط",
+ "mapTitle": "خريطة",
+ "mapLayer": "طبقة الخريطة",
+ "mapCustom": "خريطة محددة",
+ "mapOsm": "خرائط اوبن ستريت",
+ "mapBingKey": "مفتاح خرائط Bing",
+ "mapBingRoad": " خرائط الطرق Bing",
+ "mapBingAerial": "خرائط جوية Bing",
+ "mapShapePolygon": "مضلع",
+ "mapShapeCircle": "دائرة",
+ "stateTitle": "حالة",
+ "stateName": "عنصر",
+ "stateValue": "قيمة",
+ "commandTitle": "أمر",
+ "commandSend": "ارسال",
+ "commandSent": "تم ارسال الأمر",
+ "commandPositionPeriodic": "تقارير دورية",
+ "commandPositionStop": "ايقاف الارسال",
+ "commandEngineStop": "ايقاف المحرك",
+ "commandEngineResume": "تشغيل المحرك",
+ "commandFrequency": "تردد",
+ "commandUnit": "وحدة",
+ "commandCustom": "أمر خاص",
+ "commandPositionSingle": "تقرير مفرد",
+ "commandAlarmArm": "بدء تشغيل المنبه",
+ "commandAlarmDisarm": "تعطيل المنبه",
+ "commandSetTimezone": "حدد التوقيت الزمني",
+ "commandRequestPhoto": "اطلب صورة",
+ "commandRebootDevice": "أعد تشغيل الجهاز",
+ "commandSendSms": "إرسال رسالة قصيرة",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "ظبط رقم الطوارئ",
+ "commandSilenceTime": "حدد توقيت الصامت",
+ "commandSetPhonebook": "ضبط سجل الهاتف",
+ "commandVoiceMessage": "رسالة صوتية",
+ "commandOutputControl": "التحكم بالإخراج",
+ "commandAlarmSpeed": "منبه تعدي السرعة",
+ "commandDeviceIdentification": "تعريف الجهاز",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "الجهاز متصل",
+ "eventDeviceOffline": "الجهاز غير متصل",
+ "eventDeviceMoving": "الجهاز يتحرك",
+ "eventDeviceStopped": "الجهاز متوقف",
+ "eventDeviceOverspeed": "الجهاز متعدٍّ للسرعة",
+ "eventCommandResult": "نتيجة الأمر",
+ "eventGeofenceEnter": "الجهاز قد دخل السياج الجغرافي",
+ "eventGeofenceExit": "الجهاز قد خرج من السياج الجغرافي",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "نوع الملاحظة",
+ "notificationWeb": "أرسل عن طريق صفحة الويب",
+ "notificationMail": "أرسل عن طريق البريد الإلكتروني",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/bg.json b/web/l10n/bg.json
new file mode 100644
index 0000000..0dc67dc
--- /dev/null
+++ b/web/l10n/bg.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Зареждане...",
+ "sharedSave": "Запази",
+ "sharedCancel": "Отказ",
+ "sharedAdd": "Добави",
+ "sharedEdit": "Редактирай",
+ "sharedRemove": "Премахни",
+ "sharedRemoveConfirm": "Премахване на Устройство?",
+ "sharedKm": "км",
+ "sharedMi": "мл",
+ "sharedKn": "kn",
+ "sharedKmh": "км/ч",
+ "sharedMph": "мл/ч",
+ "sharedHour": "Час",
+ "sharedMinute": "Минута",
+ "sharedSecond": "Секунда",
+ "sharedName": "Име",
+ "sharedDescription": "Описание",
+ "sharedSearch": "Търси",
+ "sharedGeofence": "Зона",
+ "sharedGeofences": "Зони",
+ "sharedNotifications": "Известия",
+ "sharedAttributes": "Атрибути",
+ "sharedAttribute": "Атрибут",
+ "sharedArea": "Район",
+ "sharedMute": "Изкл. звук",
+ "sharedType": "Тип",
+ "sharedDistance": "Разстояние",
+ "sharedHourAbbreviation": "час",
+ "sharedMinuteAbbreviation": "м",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Грешка",
+ "errorUnknown": "Непозната Грешка",
+ "errorConnection": "Грешка във връзката",
+ "userEmail": "Пощенска кутия",
+ "userPassword": "Парола",
+ "userAdmin": "Admin",
+ "userRemember": "Запомни",
+ "loginTitle": "Вход",
+ "loginLanguage": "Език",
+ "loginRegister": "Регистрация",
+ "loginLogin": "Вход",
+ "loginFailed": "Грешен потребител или парола",
+ "loginCreated": "Регистриран Нов Потребител",
+ "loginLogout": "Изход",
+ "devicesAndState": "Устройства и състояние",
+ "deviceDialog": "Обекти",
+ "deviceTitle": "Устройства",
+ "deviceIdentifier": "Идентификатор",
+ "deviceLastUpdate": "Последно обновяване",
+ "deviceCommand": "Команда",
+ "deviceFollow": "Следвай",
+ "groupDialog": "Група",
+ "groupParent": "Група",
+ "groupNoGroup": "Без група",
+ "settingsTitle": "Настройки",
+ "settingsUser": "Профил",
+ "settingsGroups": "Групи",
+ "settingsServer": "Сървър",
+ "settingsUsers": "Потребител",
+ "settingsSpeedUnit": "Скорост",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Доклад",
+ "reportDevice": "Устройство",
+ "reportGroup": "Група",
+ "reportFrom": "От",
+ "reportTo": "До",
+ "reportShow": "Покажи",
+ "reportClear": "Изчисти",
+ "positionFixTime": "Време",
+ "positionValid": "Валидност",
+ "positionLatitude": "Географска Ширина",
+ "positionLongitude": "Географска Дължина",
+ "positionAltitude": "Надморска височина",
+ "positionSpeed": "Скорост",
+ "positionCourse": "Посока",
+ "positionAddress": "Адрес",
+ "positionProtocol": "Протокол",
+ "serverTitle": "Настройки на Сървъра",
+ "serverZoom": "Приближение",
+ "serverRegistration": "Регистрация",
+ "serverReadonly": "Readonly",
+ "mapTitle": "Карта",
+ "mapLayer": "Слой",
+ "mapCustom": "Потребителска Карта",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Многоъгълник",
+ "mapShapeCircle": "Кръг",
+ "stateTitle": "Състояние",
+ "stateName": "Параметър",
+ "stateValue": "Стойност",
+ "commandTitle": "Команда",
+ "commandSend": "Изпрати",
+ "commandSent": "Съобщението е изпратено",
+ "commandPositionPeriodic": "Периодичен Доклад",
+ "commandPositionStop": "Спри Доклада",
+ "commandEngineStop": "Спри Двигател",
+ "commandEngineResume": "Стартирай Двигател",
+ "commandFrequency": "Честота",
+ "commandUnit": "Обект",
+ "commandCustom": "Персонализирана Команда",
+ "commandPositionSingle": "Единичен доклад",
+ "commandAlarmArm": "Активирай Аларма",
+ "commandAlarmDisarm": "Деактивирай Аларма",
+ "commandSetTimezone": "Задайте Часова Зона",
+ "commandRequestPhoto": "Изпрати Снимка",
+ "commandRebootDevice": "Рестартирай Устройство",
+ "commandSendSms": "Изпрати СМС",
+ "commandSendUssd": "Изпрати USSD",
+ "commandSosNumber": "Задай SOS номер",
+ "commandSilenceTime": "Задай Тих Час",
+ "commandSetPhonebook": "Задай Тел. Указател",
+ "commandVoiceMessage": "Гласово Съобщение",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Аларма за Превишена Скорост",
+ "commandDeviceIdentification": "Идентификация на Устройство",
+ "commandIndex": "Индекс",
+ "commandData": "Данни",
+ "commandPhone": "Телефонен номер",
+ "commandMessage": "Съобщение",
+ "eventAll": "Всички събития",
+ "eventDeviceOnline": "Устройството е онлайн",
+ "eventDeviceOffline": "Устройството е офлайн",
+ "eventDeviceMoving": "Устройството е в движение",
+ "eventDeviceStopped": "Устройството е спряло",
+ "eventDeviceOverspeed": "Устройството превишава скоростта",
+ "eventCommandResult": "Резултат от командата",
+ "eventGeofenceEnter": "Устройството влезе в зоната",
+ "eventGeofenceExit": "Устройството излезе от зоната",
+ "eventAlarm": "Аларми",
+ "eventIgnitionOn": "Запалването е включено",
+ "eventIgnitionOff": "Запалването е изключено",
+ "alarm": "Аларма",
+ "alarmSos": "SOS Аларма",
+ "alarmVibration": "Аларма Вибрация",
+ "alarmMovement": "Аларма движение",
+ "alarmOverspeed": "Аларма за Превишена Скорост",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "Аларма за слаб заряд",
+ "alarmFault": "Аларма за повреда",
+ "notificationType": "Тип на известието",
+ "notificationWeb": "Изпрати през Web",
+ "notificationMail": "Изпрати през Mail",
+ "reportRoute": "Маршрут",
+ "reportEvents": "Събития",
+ "reportTrips": "Пътувания",
+ "reportSummary": "Общо",
+ "reportConfigure": "Конфигуриране",
+ "reportEventTypes": "Тип Събития",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Име на Обект",
+ "reportAverageSpeed": "Средна скорост",
+ "reportMaximumSpeed": "Максимална скорост",
+ "reportEngineHours": "Машиночас",
+ "reportDuration": "Продължителност",
+ "reportStartTime": "Начален час",
+ "reportStartAddress": "Стартов Адрес",
+ "reportEndTime": "Краен час",
+ "reportEndAddress": "Краен Адрес",
+ "reportSpentFuel": "Отработено Гориво"
+} \ No newline at end of file
diff --git a/web/l10n/cs.json b/web/l10n/cs.json
new file mode 100644
index 0000000..c7b3b79
--- /dev/null
+++ b/web/l10n/cs.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Načítání...",
+ "sharedSave": "Uložit",
+ "sharedCancel": "Zrušit",
+ "sharedAdd": "Přidat",
+ "sharedEdit": "Změnit",
+ "sharedRemove": "Odstranit",
+ "sharedRemoveConfirm": "Odstranit položku?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Hodina",
+ "sharedMinute": "Minuta",
+ "sharedSecond": "Sekunda",
+ "sharedName": "Jméno",
+ "sharedDescription": "Popis",
+ "sharedSearch": "Hledat",
+ "sharedGeofence": "Geografická hranice",
+ "sharedGeofences": "Geografické hranice",
+ "sharedNotifications": "Upozornění",
+ "sharedAttributes": "Atributy",
+ "sharedAttribute": "Atribut",
+ "sharedArea": "Oblast",
+ "sharedMute": "Ztišit",
+ "sharedType": "Typ",
+ "sharedDistance": "Vzdálenost",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Získat stav mapy",
+ "errorTitle": "Chyba",
+ "errorUnknown": "Neznámá chyba",
+ "errorConnection": "Chyba spojení",
+ "userEmail": "Email",
+ "userPassword": "Heslo",
+ "userAdmin": "Admin",
+ "userRemember": "Zapamatovat",
+ "loginTitle": "Přihlášení",
+ "loginLanguage": "Jazyk",
+ "loginRegister": "Registrace",
+ "loginLogin": "Přihlášení",
+ "loginFailed": "Nesprávný email nebo heslo",
+ "loginCreated": "Nový uživatel byl zaregistrován",
+ "loginLogout": "Odhlášení",
+ "devicesAndState": "Zařízení a stav",
+ "deviceDialog": "Zařízení",
+ "deviceTitle": "Zařízení",
+ "deviceIdentifier": "Identifikace",
+ "deviceLastUpdate": "Poslední změna",
+ "deviceCommand": "Příkaz",
+ "deviceFollow": "Sledovat",
+ "groupDialog": "Skupina",
+ "groupParent": "Skupina",
+ "groupNoGroup": "Žádná skupina",
+ "settingsTitle": "Nastavení",
+ "settingsUser": "Účet",
+ "settingsGroups": "Skupiny",
+ "settingsServer": "Server",
+ "settingsUsers": "Uživatelé",
+ "settingsSpeedUnit": "Rychlost",
+ "settingsTwelveHourFormat": "12-hodinový formát",
+ "reportTitle": "Zpráva",
+ "reportDevice": "Zařízení",
+ "reportGroup": "Skupina",
+ "reportFrom": "Od",
+ "reportTo": "Komu",
+ "reportShow": "Zobrazit",
+ "reportClear": "Vyčistit",
+ "positionFixTime": "Čas",
+ "positionValid": "Správný",
+ "positionLatitude": "Šířka",
+ "positionLongitude": "Délka",
+ "positionAltitude": "Výška",
+ "positionSpeed": "Rychlost",
+ "positionCourse": "Směr",
+ "positionAddress": "Adresa",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Nastavení serveru",
+ "serverZoom": "Přiblížení",
+ "serverRegistration": "Registrace",
+ "serverReadonly": "Pouze pro čtení",
+ "mapTitle": "Mapa",
+ "mapLayer": "Vrstva mapy",
+ "mapCustom": "Upravená mapa",
+ "mapOsm": "Open Street mapa",
+ "mapBingKey": "Bing Maps klíč",
+ "mapBingRoad": "Bing Maps cesta",
+ "mapBingAerial": "Bing Maps anténa",
+ "mapShapePolygon": "Mnohoúhelník",
+ "mapShapeCircle": "Kruh",
+ "stateTitle": "Stav",
+ "stateName": "Atribut",
+ "stateValue": "Hodnota",
+ "commandTitle": "Příkaz",
+ "commandSend": "Odeslat",
+ "commandSent": "Příkaz byl odeslán",
+ "commandPositionPeriodic": "Pravidelný report",
+ "commandPositionStop": "Zastavit report",
+ "commandEngineStop": "Zastavit motor",
+ "commandEngineResume": "Nastartovat motor",
+ "commandFrequency": "Frekvence",
+ "commandUnit": "Jednotka",
+ "commandCustom": "Volitelný příkaz",
+ "commandPositionSingle": "Jednotné hlášení",
+ "commandAlarmArm": "Aktivovat alarm",
+ "commandAlarmDisarm": "Deaktivovat alarm",
+ "commandSetTimezone": "Nastavit časovou zónu",
+ "commandRequestPhoto": "Vyžádat fotku",
+ "commandRebootDevice": "Restartovat zařízení",
+ "commandSendSms": "Odeslat SMS",
+ "commandSendUssd": "Odeslat USSD",
+ "commandSosNumber": "Nastavit SOS číslo",
+ "commandSilenceTime": "Nastavit čas tichého módu",
+ "commandSetPhonebook": "Nastavit telefonní seznam",
+ "commandVoiceMessage": "Hlasová zpráva",
+ "commandOutputControl": "Ovládání výstupu",
+ "commandAlarmSpeed": "Překročení rychlosti alarmu",
+ "commandDeviceIdentification": "Identifikace zařízení",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Číslo telefonu",
+ "commandMessage": "Zpráva",
+ "eventAll": "Všechny události",
+ "eventDeviceOnline": "Zařízení je online",
+ "eventDeviceOffline": "Zařízení je offline",
+ "eventDeviceMoving": "Zařízení s pohybuje",
+ "eventDeviceStopped": "Zařízení se zastavilo",
+ "eventDeviceOverspeed": "Zařízení překračuje rychlost",
+ "eventCommandResult": "Výsledek příkazu",
+ "eventGeofenceEnter": "Zařízení vstoupilo do geografické hranice",
+ "eventGeofenceExit": "Zařízení opustilo geografickou hranici",
+ "eventAlarm": "Alarmy",
+ "eventIgnitionOn": "Zažehnutí je ZAPNUTO",
+ "eventIgnitionOff": "Zažehnutí je VYPNUTO",
+ "alarm": "Alarm",
+ "alarmSos": "SOS alarm",
+ "alarmVibration": "Vibrační alarm",
+ "alarmMovement": "Pohybový alarm",
+ "alarmOverspeed": "Alarm překročení rychlosti",
+ "alarmFallDown": "Pádový alarm",
+ "alarmLowBattery": "Alarm vybité baterie",
+ "alarmFault": "Chybový alarm",
+ "notificationType": "Typ oznámení",
+ "notificationWeb": "Odeslat přes web",
+ "notificationMail": "Odeslat přes mail",
+ "reportRoute": "Trasa",
+ "reportEvents": "Události",
+ "reportTrips": "Výlety",
+ "reportSummary": "Souhrn",
+ "reportConfigure": "Nastavit",
+ "reportEventTypes": "Typy událostí",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Jméno zařízení",
+ "reportAverageSpeed": "Průměrná rychlost",
+ "reportMaximumSpeed": "Maximální rychlost",
+ "reportEngineHours": "Hodiny motoru",
+ "reportDuration": "Trvání",
+ "reportStartTime": "Čas startu",
+ "reportStartAddress": "Adresa startu",
+ "reportEndTime": "Čas konce",
+ "reportEndAddress": "Adresa konce",
+ "reportSpentFuel": "Vyčerpané palivo"
+} \ No newline at end of file
diff --git a/web/l10n/da.json b/web/l10n/da.json
new file mode 100644
index 0000000..4d2359c
--- /dev/null
+++ b/web/l10n/da.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Loading... ",
+ "sharedSave": "Gem",
+ "sharedCancel": "Fortryd",
+ "sharedAdd": "Tilføj",
+ "sharedEdit": "Rediger",
+ "sharedRemove": "Fjern",
+ "sharedRemoveConfirm": "Fjern enhed?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "knob",
+ "sharedKmh": "km/t",
+ "sharedMph": "mph",
+ "sharedHour": "Time",
+ "sharedMinute": "Minut",
+ "sharedSecond": "Sekund",
+ "sharedName": "Navn",
+ "sharedDescription": "Beskrivelse",
+ "sharedSearch": "Søg",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifikationer",
+ "sharedAttributes": "Egenskaber",
+ "sharedAttribute": "Egenskab",
+ "sharedArea": "Område",
+ "sharedMute": "Lydløs",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "t",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Kort status",
+ "errorTitle": "Fejl",
+ "errorUnknown": "Ukendt Fejl",
+ "errorConnection": "Tilslutning fejl",
+ "userEmail": "Email",
+ "userPassword": "Kodeord",
+ "userAdmin": "Admin",
+ "userRemember": "Husk",
+ "loginTitle": "Log på",
+ "loginLanguage": "Sprog",
+ "loginRegister": "Registrer",
+ "loginLogin": "Log på",
+ "loginFailed": "Fejl i email adresse eller kodeord",
+ "loginCreated": "Ny bruger er registreret",
+ "loginLogout": "Log af",
+ "devicesAndState": "Enheder og status",
+ "deviceDialog": "Enhed",
+ "deviceTitle": "Enheder",
+ "deviceIdentifier": "Imei nr",
+ "deviceLastUpdate": "Seneste opdatering",
+ "deviceCommand": "Kommando",
+ "deviceFollow": "Følg",
+ "groupDialog": "Gruppe",
+ "groupParent": "Gruppe",
+ "groupNoGroup": "Ingen gruppe",
+ "settingsTitle": "Indstillinger",
+ "settingsUser": "Konto",
+ "settingsGroups": "Grupper",
+ "settingsServer": "Server",
+ "settingsUsers": "Brugere",
+ "settingsSpeedUnit": "Hastighed",
+ "settingsTwelveHourFormat": "12 timers format",
+ "reportTitle": "Rapporter",
+ "reportDevice": "Enhed",
+ "reportGroup": "Gruppe",
+ "reportFrom": "Fra",
+ "reportTo": "Til",
+ "reportShow": "Vis",
+ "reportClear": "Ryd",
+ "positionFixTime": "Tid",
+ "positionValid": "Valid",
+ "positionLatitude": "Breddegrad",
+ "positionLongitude": "Længdegrad",
+ "positionAltitude": "Højde",
+ "positionSpeed": "Hastighed",
+ "positionCourse": "Kurs",
+ "positionAddress": "Adresse",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Server indstillinger",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registrering",
+ "serverReadonly": "Læs",
+ "mapTitle": "Kort",
+ "mapLayer": "Kort opsætning",
+ "mapCustom": "Brugerdefineret Kort",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Cirkel",
+ "stateTitle": "Status",
+ "stateName": "Parameter",
+ "stateValue": "Værdi",
+ "commandTitle": "Kommando",
+ "commandSend": "Send",
+ "commandSent": "Kommando er blevet sendt",
+ "commandPositionPeriodic": "Periodisk Rapportering",
+ "commandPositionStop": "Stop Rapportering",
+ "commandEngineStop": "Stop motor",
+ "commandEngineResume": "Genstart motor",
+ "commandFrequency": "Frekvens",
+ "commandUnit": "Enhed",
+ "commandCustom": "Skræddersyet kommando",
+ "commandPositionSingle": "Enkel rapport",
+ "commandAlarmArm": "Armer alarm",
+ "commandAlarmDisarm": "Slå alarm fra",
+ "commandSetTimezone": "Sæt tidszone",
+ "commandRequestPhoto": "Tag billede",
+ "commandRebootDevice": "Genstart enhed",
+ "commandSendSms": "send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Angiv SOS nummer",
+ "commandSilenceTime": "Angiv lydløs tid",
+ "commandSetPhonebook": "Angiv telefonbog",
+ "commandVoiceMessage": "Tale meddelelse",
+ "commandOutputControl": "Output kontrol",
+ "commandAlarmSpeed": "Hastigheds alarm",
+ "commandDeviceIdentification": "Enheds id",
+ "commandIndex": "Indeks",
+ "commandData": "Data",
+ "commandPhone": "Telefon nummer",
+ "commandMessage": "Meddelelse",
+ "eventAll": "Alle begivenheder",
+ "eventDeviceOnline": "Enhed online",
+ "eventDeviceOffline": "Enhed offline",
+ "eventDeviceMoving": "Enhed i bevægelse",
+ "eventDeviceStopped": "Enhed i stilstand",
+ "eventDeviceOverspeed": "Enhed overskrider hastighed",
+ "eventCommandResult": "Resultat af kommando",
+ "eventGeofenceEnter": "Enhed kom indenfor geofence",
+ "eventGeofenceExit": "Enhed kom udenfor geofence",
+ "eventAlarm": "Alarmer",
+ "eventIgnitionOn": "Tænding slået til",
+ "eventIgnitionOff": "Tænding slået fra",
+ "alarm": "Alarm",
+ "alarmSos": "SOS alarm",
+ "alarmVibration": "Vibrations alarm",
+ "alarmMovement": "Bevægelses alarm",
+ "alarmOverspeed": "Hastigheds alarm",
+ "alarmFallDown": "Fald alarm",
+ "alarmLowBattery": "Lavt batteri alarm",
+ "alarmFault": "Fejl alarm",
+ "notificationType": "Type af notifikation",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via mail",
+ "reportRoute": "Rute",
+ "reportEvents": "Begivenheder",
+ "reportTrips": "Ture",
+ "reportSummary": "Resume",
+ "reportConfigure": "Konfigurer",
+ "reportEventTypes": "Begivenheds typer",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Enheds navn",
+ "reportAverageSpeed": "Gennemsnits hastighed",
+ "reportMaximumSpeed": "Maximum hastighed",
+ "reportEngineHours": "Motor aktiv timer",
+ "reportDuration": "Varighed",
+ "reportStartTime": "Start tidspunkt",
+ "reportStartAddress": "Start adresse",
+ "reportEndTime": "Slut tidspunkt",
+ "reportEndAddress": "Slut adresse",
+ "reportSpentFuel": "Brændstof forbrug"
+} \ No newline at end of file
diff --git a/web/l10n/de.json b/web/l10n/de.json
new file mode 100644
index 0000000..d20e711
--- /dev/null
+++ b/web/l10n/de.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Lade...",
+ "sharedSave": "Speichern",
+ "sharedCancel": "Abbrechen",
+ "sharedAdd": "Hinzufügen",
+ "sharedEdit": "Bearbeiten",
+ "sharedRemove": "Entfernen",
+ "sharedRemoveConfirm": "Objekt entfernen?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Stunde",
+ "sharedMinute": "Minute",
+ "sharedSecond": "Sekunde",
+ "sharedName": "Name",
+ "sharedDescription": "Beschreibung",
+ "sharedSearch": "Suchen",
+ "sharedGeofence": "Geo-Zaun",
+ "sharedGeofences": "Geo-Zäune",
+ "sharedNotifications": "Benachrichtigungen",
+ "sharedAttributes": "Eigenschaften",
+ "sharedAttribute": "Eigenschaft",
+ "sharedArea": "Gebiet",
+ "sharedMute": "Stummschalten",
+ "sharedType": "Typ",
+ "sharedDistance": "Abstand",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Fehler",
+ "errorUnknown": "Unbekannter Fehler",
+ "errorConnection": "Verbindungsfehler",
+ "userEmail": "Email",
+ "userPassword": "Passwort",
+ "userAdmin": "Admin",
+ "userRemember": "Erinnern",
+ "loginTitle": "Anmeldung",
+ "loginLanguage": "Sprache",
+ "loginRegister": "Registrieren",
+ "loginLogin": "Anmelden",
+ "loginFailed": "Falsche Emailadresse oder Passwort",
+ "loginCreated": "Neuer Benutzer wurde registriert",
+ "loginLogout": "Abmelden",
+ "devicesAndState": "Geräte und Status",
+ "deviceDialog": "Gerät",
+ "deviceTitle": "Geräte",
+ "deviceIdentifier": "Kennung",
+ "deviceLastUpdate": "Letzte Aktualisierung",
+ "deviceCommand": "Befehl",
+ "deviceFollow": "Folgen",
+ "groupDialog": "Gruppe",
+ "groupParent": "Gruppe",
+ "groupNoGroup": "Keine Gruppe",
+ "settingsTitle": "Einstellungen",
+ "settingsUser": "Benutzerkonto",
+ "settingsGroups": "Gruppen",
+ "settingsServer": "Server",
+ "settingsUsers": "Benutzer",
+ "settingsSpeedUnit": "Geschwindigkeit",
+ "settingsTwelveHourFormat": "12 Stunden Format",
+ "reportTitle": "Berichte",
+ "reportDevice": "Gerät",
+ "reportGroup": "Gruppe",
+ "reportFrom": "Von",
+ "reportTo": "Bis",
+ "reportShow": "Anzeigen",
+ "reportClear": "Leeren",
+ "positionFixTime": "Zeit",
+ "positionValid": "Gültig",
+ "positionLatitude": "Breitengrad",
+ "positionLongitude": "Längengrad",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "Geschwindigkeit",
+ "positionCourse": "Richtung",
+ "positionAddress": "Adresse",
+ "positionProtocol": "Protokoll",
+ "serverTitle": "Server Einstellungen",
+ "serverZoom": "Zoomen",
+ "serverRegistration": "Registrierung zulassen",
+ "serverReadonly": "Nur Lesen",
+ "mapTitle": "Karte",
+ "mapLayer": "Karten Layer",
+ "mapCustom": "Benutzerspezifische Karte",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Strassenkarte",
+ "mapBingAerial": "Bing Luftbilder",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Kreis",
+ "stateTitle": "Status",
+ "stateName": "Parameter",
+ "stateValue": "Wert",
+ "commandTitle": "Befehl",
+ "commandSend": "Senden",
+ "commandSent": "Befehl wurde gesendet",
+ "commandPositionPeriodic": "Periodische Berichte",
+ "commandPositionStop": "Bericht stoppen",
+ "commandEngineStop": "Motor Stop",
+ "commandEngineResume": "Motor Start",
+ "commandFrequency": "Frequenz",
+ "commandUnit": "Einheit",
+ "commandCustom": "Benutzerdefinierter Befehl",
+ "commandPositionSingle": "Einzelner Bericht",
+ "commandAlarmArm": "Scharf schalten",
+ "commandAlarmDisarm": "Unscharf schalten",
+ "commandSetTimezone": "Zeitzone festlegen",
+ "commandRequestPhoto": "Foto anfordern",
+ "commandRebootDevice": "Gerät neustarten",
+ "commandSendSms": "SMS senden",
+ "commandSendUssd": "USSD senden",
+ "commandSosNumber": "SOS-Nummer festlegen",
+ "commandSilenceTime": "Ruhezeit festlegen",
+ "commandSetPhonebook": "Telefonbuch festlegen",
+ "commandVoiceMessage": "Sprachnachricht",
+ "commandOutputControl": "Berichtsteuerung",
+ "commandAlarmSpeed": "Geschwindigkeitsalarm",
+ "commandDeviceIdentification": "Gerätekennung",
+ "commandIndex": "Index",
+ "commandData": "Daten",
+ "commandPhone": "Rufnummer",
+ "commandMessage": "Nachricht",
+ "eventAll": "Alle Ereignisse",
+ "eventDeviceOnline": "Gerät ist online",
+ "eventDeviceOffline": "Gerät ist offline",
+ "eventDeviceMoving": "Gerät ist in Bewegung",
+ "eventDeviceStopped": "Gerät hat gestoppt",
+ "eventDeviceOverspeed": "Gerät überschreitet Tempolimit",
+ "eventCommandResult": "Ergebnis des Befehls",
+ "eventGeofenceEnter": "Gerät hat Geo-Zaun betreten",
+ "eventGeofenceExit": "Gerät hat Geo-Zaun verlassen",
+ "eventAlarm": "Alarme",
+ "eventIgnitionOn": "Zünding an",
+ "eventIgnitionOff": "Zündung aus",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Erschütterungsalarm",
+ "alarmMovement": "Bewegungsalarm",
+ "alarmOverspeed": "Geschwindigkeitsalarm",
+ "alarmFallDown": "Sturzalarm",
+ "alarmLowBattery": "Batteriealarm",
+ "alarmFault": "Fehleralarm",
+ "notificationType": "Art der Benachrichtigung ",
+ "notificationWeb": "Per Web senden",
+ "notificationMail": "Per E-Mail senden",
+ "reportRoute": "Route",
+ "reportEvents": "Ereignis",
+ "reportTrips": "Trips",
+ "reportSummary": "Zusammenfassung",
+ "reportConfigure": "Konfigurieren",
+ "reportEventTypes": "Ereignisarten",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Gerätename",
+ "reportAverageSpeed": "Durchschnittsgeschwindigkeit",
+ "reportMaximumSpeed": "Höchstgeschwindigkeit",
+ "reportEngineHours": "Betriebsstunden",
+ "reportDuration": "Dauer",
+ "reportStartTime": "Startzeit",
+ "reportStartAddress": "Startort",
+ "reportEndTime": "Zielzeit",
+ "reportEndAddress": "Zielort",
+ "reportSpentFuel": "Kraftstoffverbrauch"
+} \ No newline at end of file
diff --git a/web/l10n/el.json b/web/l10n/el.json
new file mode 100644
index 0000000..49dceda
--- /dev/null
+++ b/web/l10n/el.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Φόρτωση...",
+ "sharedSave": "Αποθήκευση",
+ "sharedCancel": "Άκυρον",
+ "sharedAdd": "Προσθήκη",
+ "sharedEdit": "Επεξεργασία",
+ "sharedRemove": "Διαγραφή",
+ "sharedRemoveConfirm": "Διαγραφη στοιχείου;",
+ "sharedKm": "χλμ",
+ "sharedMi": "μίλια",
+ "sharedKn": "κόμβοι",
+ "sharedKmh": "χλμ/ώρα",
+ "sharedMph": "μίλια/ώρα",
+ "sharedHour": "Ώρα",
+ "sharedMinute": "Λεπτά",
+ "sharedSecond": "Δευτερόλεπτα",
+ "sharedName": "Όνομα",
+ "sharedDescription": "Περιγραφή",
+ "sharedSearch": "Αναζήτηση",
+ "sharedGeofence": "Γεωφράχτης",
+ "sharedGeofences": "Γεωφράχτες",
+ "sharedNotifications": "Ειδοποιήσεις",
+ "sharedAttributes": "Παράμετροι",
+ "sharedAttribute": "Παράμετρος",
+ "sharedArea": "Περιοχή",
+ "sharedMute": "Σίγαση",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Σφάλμα",
+ "errorUnknown": "Άγνωστο σφάλμα",
+ "errorConnection": "Σφάλμα σύνδεσης",
+ "userEmail": "Ηλ. διεύθυνση",
+ "userPassword": "Συνθηματικό",
+ "userAdmin": "Admin",
+ "userRemember": "Απομνημόνευση",
+ "loginTitle": "Σύνδεση",
+ "loginLanguage": "Γλώσσα",
+ "loginRegister": "Εγγραφή",
+ "loginLogin": "Σύνδεση",
+ "loginFailed": "Εσφαλμένη διεύθυνση ή εσφαλμένο συνθηματικό",
+ "loginCreated": "Ο νέος χρήστης καταχωρήθηκε.",
+ "loginLogout": "Αποσύνδεση",
+ "devicesAndState": "Κατάσταση συσκευών",
+ "deviceDialog": "Συσκευή",
+ "deviceTitle": "Συσκευές",
+ "deviceIdentifier": "Αναγνωριστικό",
+ "deviceLastUpdate": "Τελευταία ενημέρωση",
+ "deviceCommand": "Εντολή",
+ "deviceFollow": "Ακολουθώ",
+ "groupDialog": "Ομάδα",
+ "groupParent": "Ομάδα",
+ "groupNoGroup": "Χωρίς Ομάδα",
+ "settingsTitle": "Ρυθμίσεις",
+ "settingsUser": "Λογαριασμός",
+ "settingsGroups": "Ομάδες",
+ "settingsServer": "Εξυπηρετητής",
+ "settingsUsers": "Χρήστες",
+ "settingsSpeedUnit": "Ταχύτητα",
+ "settingsTwelveHourFormat": "12ώρη μορφή",
+ "reportTitle": "Αναφορές",
+ "reportDevice": "Συσκευή",
+ "reportGroup": "Group",
+ "reportFrom": "Από",
+ "reportTo": "Έως",
+ "reportShow": "Προβολή",
+ "reportClear": "Καθαρισμός",
+ "positionFixTime": "Χρόνος",
+ "positionValid": "Έγκυρο",
+ "positionLatitude": "Γ. πλάτος",
+ "positionLongitude": "Γ. μήκος",
+ "positionAltitude": "Υψόμετρο",
+ "positionSpeed": "Ταχύτητα",
+ "positionCourse": "Πορεία",
+ "positionAddress": "Διεύθυνση",
+ "positionProtocol": "Πρωτόκολλο",
+ "serverTitle": "Ρυθμίσεις εξυπηρετητή",
+ "serverZoom": "Εστίαση",
+ "serverRegistration": "Εγγραφή",
+ "serverReadonly": "Μόνο για ανάγνωση",
+ "mapTitle": "Χάρτης",
+ "mapLayer": "Επιλογή χάρτη",
+ "mapCustom": "Προσαρμοσμένος χάρτης",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Κλειδί Bing Maps",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Πολύγωνο",
+ "mapShapeCircle": "Κύκλος",
+ "stateTitle": "Κατάσταση",
+ "stateName": "Παράμετρος",
+ "stateValue": "Τιμή",
+ "commandTitle": "Εντολή",
+ "commandSend": "Αποστολή",
+ "commandSent": "Η εντολή έχει σταλεί.",
+ "commandPositionPeriodic": "Περιοδικές αναφορές",
+ "commandPositionStop": "Λήξη αναφορών",
+ "commandEngineStop": "Κλείσιμο",
+ "commandEngineResume": "Επανεκκίνηση",
+ "commandFrequency": "Συχνότητα",
+ "commandUnit": "Μονάδα",
+ "commandCustom": "Προσαρμοσμένη εντολή",
+ "commandPositionSingle": "Ενιαία αναφορά",
+ "commandAlarmArm": "Ενεργοποίηση συναγερμού",
+ "commandAlarmDisarm": "Απενεργοποίηση συναγερμού",
+ "commandSetTimezone": "Καθορισμός ζώνης ώρας",
+ "commandRequestPhoto": "Αίτημα για φωτογραφία",
+ "commandRebootDevice": "Επανεκκίνηση συσκευής",
+ "commandSendSms": "Αποστολή γραπτού μηνύματος (SMS)",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Καθορισμός αριθμού SOS",
+ "commandSilenceTime": "Καθορισμός χρόνου σιωπής",
+ "commandSetPhonebook": "Καθορισμός τηλεφωνικού καταλόγου",
+ "commandVoiceMessage": "Φωνητικό μήνυμα",
+ "commandOutputControl": "Έλεγχος αποτελεσμάτων",
+ "commandAlarmSpeed": "Υπέρβαση ορίου ταχύτητας",
+ "commandDeviceIdentification": "Αναγνωριστικό συσκευής",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Η συσκευή είναι συνδεδεμένη",
+ "eventDeviceOffline": "Η συσκευή είναι αποσυνδεδεμένη",
+ "eventDeviceMoving": "Η συσκευή βρίσκεται σε κίνηση",
+ "eventDeviceStopped": "Η συσκευή έχει σταματήσει",
+ "eventDeviceOverspeed": "Η συσκευή υπερέβει την ταχύτητα",
+ "eventCommandResult": "Αποτέλεσμα εντολής",
+ "eventGeofenceEnter": "Η συσσκευή εισήλθε του γεωφράχτη",
+ "eventGeofenceExit": "Η συσκευή εξήλθε του γεωφράχτη",
+ "eventAlarm": "Προειδοποιήσεις",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Προειδοποίηση",
+ "alarmSos": "Προειδοποίηση SOS",
+ "alarmVibration": "Προειδοποίηση δόνησης",
+ "alarmMovement": "Προειδοποίηση κίνησης",
+ "alarmOverspeed": "Προειδοποίηση υπέρβασης ορίου ταχύτητας",
+ "alarmFallDown": "Προειδοποίηση πτώσης",
+ "alarmLowBattery": "Προειδοποίηση χαμηλής μπαταρίας",
+ "alarmFault": "Προειδοποίηση σφάλματος",
+ "notificationType": "Τύπος ειδοποίησης",
+ "notificationWeb": "Αποστολή μέσω διαδικτύου",
+ "notificationMail": "Αποστολή μέσω ηλ. ταχυδρομείου",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/en.json b/web/l10n/en.json
new file mode 100644
index 0000000..a1fc97c
--- /dev/null
+++ b/web/l10n/en.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Loading...",
+ "sharedSave": "Save",
+ "sharedCancel": "Cancel",
+ "sharedAdd": "Add",
+ "sharedEdit": "Edit",
+ "sharedRemove": "Remove",
+ "sharedRemoveConfirm": "Remove item?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Hour",
+ "sharedMinute": "Minute",
+ "sharedSecond": "Second",
+ "sharedName": "Name",
+ "sharedDescription": "Description",
+ "sharedSearch": "Search",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Error",
+ "errorUnknown": "Unknown error",
+ "errorConnection": "Connection error",
+ "userEmail": "Email",
+ "userPassword": "Password",
+ "userAdmin": "Admin",
+ "userRemember": "Remember",
+ "loginTitle": "Login",
+ "loginLanguage": "Language",
+ "loginRegister": "Register",
+ "loginLogin": "Login",
+ "loginFailed": "Incorrect email address or password",
+ "loginCreated": "New user has been registered",
+ "loginLogout": "Logout",
+ "devicesAndState": "Devices and State",
+ "deviceDialog": "Device",
+ "deviceTitle": "Devices",
+ "deviceIdentifier": "Identifier",
+ "deviceLastUpdate": "Last Update",
+ "deviceCommand": "Command",
+ "deviceFollow": "Follow",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "Settings",
+ "settingsUser": "Account",
+ "settingsGroups": "Groups",
+ "settingsServer": "Server",
+ "settingsUsers": "Users",
+ "settingsSpeedUnit": "Speed",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Reports",
+ "reportDevice": "Device",
+ "reportGroup": "Group",
+ "reportFrom": "From",
+ "reportTo": "To",
+ "reportShow": "Show",
+ "reportClear": "Clear",
+ "positionFixTime": "Time",
+ "positionValid": "Valid",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "Speed",
+ "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",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "State",
+ "stateName": "Attribute",
+ "stateValue": "Value",
+ "commandTitle": "Command",
+ "commandSend": "Send",
+ "commandSent": "Command has been sent",
+ "commandPositionPeriodic": "Periodic Reporting",
+ "commandPositionStop": "Stop Reporting",
+ "commandEngineStop": "Engine Stop",
+ "commandEngineResume": "Engine Resume",
+ "commandFrequency": "Frequency",
+ "commandUnit": "Unit",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device has stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/es.json b/web/l10n/es.json
new file mode 100644
index 0000000..cadd9b7
--- /dev/null
+++ b/web/l10n/es.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Cargando...",
+ "sharedSave": "Guardar",
+ "sharedCancel": "Cancelar",
+ "sharedAdd": "Agregar",
+ "sharedEdit": "Editar",
+ "sharedRemove": "Borrar",
+ "sharedRemoveConfirm": "Borrar Elemento?",
+ "sharedKm": "KM",
+ "sharedMi": "MI",
+ "sharedKn": "Nudo",
+ "sharedKmh": "KM/H",
+ "sharedMph": "MPH",
+ "sharedHour": "Hora",
+ "sharedMinute": "Minuto",
+ "sharedSecond": "Segundo",
+ "sharedName": "Nombre",
+ "sharedDescription": "Descripción",
+ "sharedSearch": "Buscar",
+ "sharedGeofence": "Geocerca",
+ "sharedGeofences": "Geocercas",
+ "sharedNotifications": "Notificaciones",
+ "sharedAttributes": "Atributos",
+ "sharedAttribute": "Atributo",
+ "sharedArea": "Área",
+ "sharedMute": "Silenciar",
+ "sharedType": "Tipo",
+ "sharedDistance": "Distancia",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Obtener Estado del Mapa",
+ "errorTitle": "Error",
+ "errorUnknown": "Error Desconocido",
+ "errorConnection": "Error de Conexión",
+ "userEmail": "Email",
+ "userPassword": "Contraseña",
+ "userAdmin": "Administrador",
+ "userRemember": "Recordar",
+ "loginTitle": "Ingresar",
+ "loginLanguage": "Idioma",
+ "loginRegister": "Registrar",
+ "loginLogin": "Ingresar",
+ "loginFailed": "Dirección de Correo o Contraseña Incorrecta",
+ "loginCreated": "Nuevo Usuario ha sido registrado",
+ "loginLogout": "Salir",
+ "devicesAndState": "Dispositivos y Estado",
+ "deviceDialog": "Dispositivo",
+ "deviceTitle": "Dispositivos",
+ "deviceIdentifier": "Identificador",
+ "deviceLastUpdate": "Última Actualización",
+ "deviceCommand": "Comando",
+ "deviceFollow": "Seguir",
+ "groupDialog": "Grupo",
+ "groupParent": "Grupo",
+ "groupNoGroup": "Sin grupo",
+ "settingsTitle": "Preferencias",
+ "settingsUser": "Cuenta",
+ "settingsGroups": "Grupos",
+ "settingsServer": "Servidor",
+ "settingsUsers": "Usuarios",
+ "settingsSpeedUnit": "Velocidad",
+ "settingsTwelveHourFormat": "Formato de 12 Hrs",
+ "reportTitle": "Reportes",
+ "reportDevice": "Dispositivos",
+ "reportGroup": "Grupo",
+ "reportFrom": "Desde",
+ "reportTo": "Hasta",
+ "reportShow": "Mostrar",
+ "reportClear": "Limpiar",
+ "positionFixTime": "Hora",
+ "positionValid": "Válida",
+ "positionLatitude": "Latitud",
+ "positionLongitude": "Longitud",
+ "positionAltitude": "Altitud",
+ "positionSpeed": "Velocidad",
+ "positionCourse": "Curso",
+ "positionAddress": "Dirección",
+ "positionProtocol": "Protocolo",
+ "serverTitle": "Preferencias Servidor",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registrar",
+ "serverReadonly": "Sólo Lectura",
+ "mapTitle": "Mapa",
+ "mapLayer": "Capa de Mapa",
+ "mapCustom": "Mapa Personalizado",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps - Carretera",
+ "mapBingAerial": "Bing Maps - Aéreo",
+ "mapShapePolygon": "Polígono",
+ "mapShapeCircle": "Círculo",
+ "stateTitle": "Estado",
+ "stateName": "Parámetro",
+ "stateValue": "Valor",
+ "commandTitle": "Comando",
+ "commandSend": "Enviar",
+ "commandSent": "El Comando ha sido enviado",
+ "commandPositionPeriodic": "Frecuencia de Posiciones",
+ "commandPositionStop": "Detener Reporte de Posiciones",
+ "commandEngineStop": "Apagar motor",
+ "commandEngineResume": "Desbloquear Encendido de Motor",
+ "commandFrequency": "Frequencia",
+ "commandUnit": "Unidad",
+ "commandCustom": "Comando personalizado",
+ "commandPositionSingle": "Un report",
+ "commandAlarmArm": "Armar Alarma",
+ "commandAlarmDisarm": "Desarmar Alarma",
+ "commandSetTimezone": "Establecer zona horaria",
+ "commandRequestPhoto": "Solicitar Foto",
+ "commandRebootDevice": "Reiniciar dispositivo",
+ "commandSendSms": "Enviar SMS",
+ "commandSendUssd": "Enviar USSD",
+ "commandSosNumber": "Establecer el número SOS",
+ "commandSilenceTime": "Setear horario de silencio",
+ "commandSetPhonebook": "Establecer contacto",
+ "commandVoiceMessage": "Mensaje de voz",
+ "commandOutputControl": "Control de Salidas",
+ "commandAlarmSpeed": "Alerta de Velocidad",
+ "commandDeviceIdentification": "Identificación de Dispositivo",
+ "commandIndex": "Índice",
+ "commandData": "Datos",
+ "commandPhone": "Número de Teléfono",
+ "commandMessage": "Mensaje",
+ "eventAll": "Todos los Eventos",
+ "eventDeviceOnline": "El dispositivo está en linea",
+ "eventDeviceOffline": "El dispositivo está fuera de linea",
+ "eventDeviceMoving": "El dispositivo se está moviendo",
+ "eventDeviceStopped": "El dispositivo está parado",
+ "eventDeviceOverspeed": "El dispositivo excedió el limite de velocidad",
+ "eventCommandResult": "Resultado de comando",
+ "eventGeofenceEnter": "El dispositivo ha ingresado a la geocerca",
+ "eventGeofenceExit": "El dispositivo ha salido de la geocerca",
+ "eventAlarm": "Alarmas",
+ "eventIgnitionOn": "Encendido ON",
+ "eventIgnitionOff": "Encendido OFF",
+ "alarm": "Alarma",
+ "alarmSos": "Alarma de SOS",
+ "alarmVibration": "Alarma de vibración",
+ "alarmMovement": "Alarma de movimiento",
+ "alarmOverspeed": "Alarma de exceso de velocidad",
+ "alarmFallDown": "Alarma de caida",
+ "alarmLowBattery": "Alarma de bateria baja",
+ "alarmFault": "Alarma de fallo",
+ "notificationType": "Tipo de Notificación",
+ "notificationWeb": "Envíar vía Web",
+ "notificationMail": "Envíar vía Email",
+ "reportRoute": "Ruta",
+ "reportEvents": "Eventos",
+ "reportTrips": "Viajes",
+ "reportSummary": "Sumario",
+ "reportConfigure": "Configurar",
+ "reportEventTypes": "Tipos de Evento",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Nombre de Dispositivo",
+ "reportAverageSpeed": "Velocidad promedio",
+ "reportMaximumSpeed": "Velocidad Máxima",
+ "reportEngineHours": "Horas Motor",
+ "reportDuration": "Duración",
+ "reportStartTime": "Hora de Inicio",
+ "reportStartAddress": "Dirección de Inicio",
+ "reportEndTime": "Hora de Fin",
+ "reportEndAddress": "Dirección de Fin",
+ "reportSpentFuel": "Combustible utilizado"
+} \ No newline at end of file
diff --git a/web/l10n/fa.json b/web/l10n/fa.json
new file mode 100644
index 0000000..1bb66af
--- /dev/null
+++ b/web/l10n/fa.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "در حال بارگزارى ...",
+ "sharedSave": "ذخيره",
+ "sharedCancel": "انصراف",
+ "sharedAdd": "اضافه كردن",
+ "sharedEdit": "ویرایش",
+ "sharedRemove": "پاک کردن",
+ "sharedRemoveConfirm": "پاک کردن آیتم",
+ "sharedKm": "کیلومتر",
+ "sharedMi": "مایل",
+ "sharedKn": "kn",
+ "sharedKmh": "کیلومتر بر ساعت",
+ "sharedMph": "مایل بر ساعت",
+ "sharedHour": "ساعت",
+ "sharedMinute": "دقيقه",
+ "sharedSecond": "ثانيه",
+ "sharedName": "نام",
+ "sharedDescription": "توضیحات",
+ "sharedSearch": "جستجو",
+ "sharedGeofence": "حصار جغرافیایی",
+ "sharedGeofences": "حصارهای جغرافیایی",
+ "sharedNotifications": "رویدادها",
+ "sharedAttributes": "ویژگی ها",
+ "sharedAttribute": "ویژگی",
+ "sharedArea": "محدوده",
+ "sharedMute": "بی صدا",
+ "sharedType": "نوع خط",
+ "sharedDistance": "طول مسیر",
+ "sharedHourAbbreviation": "ساعت",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "خطا",
+ "errorUnknown": "خطا ناشناخته",
+ "errorConnection": "خطا در اتصال",
+ "userEmail": "نام كاربرى ",
+ "userPassword": "رمز عبور",
+ "userAdmin": "مدیر",
+ "userRemember": "مرا به خاطر داشته باش",
+ "loginTitle": "ورود",
+ "loginLanguage": "انتخاب زبان",
+ "loginRegister": "ثبت نام",
+ "loginLogin": "ورود",
+ "loginFailed": "نام كاربرى يا گذرواژه اشتباه است",
+ "loginCreated": "ثبت نام با موفقيت انجام شد",
+ "loginLogout": "خروج",
+ "devicesAndState": "دستگاه ها و وضعیت",
+ "deviceDialog": "دستگاه",
+ "deviceTitle": "دستگاه ها",
+ "deviceIdentifier": "سريال دستگاه",
+ "deviceLastUpdate": "آخرين بروزرسانى",
+ "deviceCommand": "فرمان",
+ "deviceFollow": "تعقیب",
+ "groupDialog": "گروه",
+ "groupParent": "گروه",
+ "groupNoGroup": "بدون گروه",
+ "settingsTitle": "تنظيمات",
+ "settingsUser": "حساب كاربرى",
+ "settingsGroups": "گروه ها",
+ "settingsServer": "سرور",
+ "settingsUsers": "کاربر",
+ "settingsSpeedUnit": "سرعت",
+ "settingsTwelveHourFormat": "فرمت 12 ساعتی",
+ "reportTitle": "گزارشات ",
+ "reportDevice": "دستگاه",
+ "reportGroup": "Group",
+ "reportFrom": "از",
+ "reportTo": "تا",
+ "reportShow": "نمایش",
+ "reportClear": "خالی کردن",
+ "positionFixTime": "زمان",
+ "positionValid": "معتبر",
+ "positionLatitude": "عرض جغرافيايى",
+ "positionLongitude": "طول جغرافيايى",
+ "positionAltitude": "ارتفاع",
+ "positionSpeed": "سرعت",
+ "positionCourse": "دوره",
+ "positionAddress": "آدرس",
+ "positionProtocol": "پروتوکل",
+ "serverTitle": "تنظیمات سرور",
+ "serverZoom": "بزرگنمایی",
+ "serverRegistration": "ثبت نام",
+ "serverReadonly": "فقط خواندنی",
+ "mapTitle": "نقشه",
+ "mapLayer": "لایه های نقشه",
+ "mapCustom": "Custom Map",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "چند ضلعی",
+ "mapShapeCircle": "دایره ",
+ "stateTitle": "وضعیت",
+ "stateName": "ویژگی",
+ "stateValue": "مقدار",
+ "commandTitle": "ارسال دستور به دستگاه",
+ "commandSend": "ارسال",
+ "commandSent": "دستور ارسال گردید",
+ "commandPositionPeriodic": "Periodic Reporting",
+ "commandPositionStop": "Stop Reporting",
+ "commandEngineStop": "Engine Stop",
+ "commandEngineResume": "Engine Resume",
+ "commandFrequency": "Frequency",
+ "commandUnit": "واحد",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "تنظیم ساعت محلی",
+ "commandRequestPhoto": "درخواست عکس",
+ "commandRebootDevice": "ریست کردن دستگاه",
+ "commandSendSms": "ارسال پیام کوتاه",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "انتخاب شماره مدیر ",
+ "commandSilenceTime": "تنظیم زمان سکوت",
+ "commandSetPhonebook": "تنظیم دفترچه تلفن",
+ "commandVoiceMessage": "پیام صوتی",
+ "commandOutputControl": "تنظیمات خروجی ",
+ "commandAlarmSpeed": "هشدار سرعت غیرمجاز",
+ "commandDeviceIdentification": "شناسایی دستگاه",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "دستگاه آنلاین است ",
+ "eventDeviceOffline": "دستگاه آفلاین است ",
+ "eventDeviceMoving": "خودرو در حال حرکت است",
+ "eventDeviceStopped": "خودرو متوقف است",
+ "eventDeviceOverspeed": "خودرو از سرعت تعیین شده تجاوز کرده است ",
+ "eventCommandResult": "نتیجه ارسال دستور",
+ "eventGeofenceEnter": "خودرو وارد حصار جغرافیایی شد ",
+ "eventGeofenceExit": "خودرو از حصار جغرافیایی خارج شد",
+ "eventAlarm": "هشدار ها",
+ "eventIgnitionOn": "خودرو روشن هست ",
+ "eventIgnitionOff": "خودرو خاموش است ",
+ "alarm": "هشدار",
+ "alarmSos": "هشدار کمک اضطراری",
+ "alarmVibration": "هشدار ضربه",
+ "alarmMovement": "هشدار حرکت",
+ "alarmOverspeed": "هشدار سرعت غیر مجاز",
+ "alarmFallDown": "هشدار سقوط خودرو",
+ "alarmLowBattery": "هشدار کم شدن باتری",
+ "alarmFault": "هشدار خطا در دستگاه",
+ "notificationType": "تعیین نوع رویداد ",
+ "notificationWeb": "ارسال از طریق وب",
+ "notificationMail": "ارسال با ایمیل",
+ "reportRoute": "مسیر های پیموده شده ",
+ "reportEvents": "رویداد ها",
+ "reportTrips": "Trips",
+ "reportSummary": "خلاصه وضعیت ",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "نام دستگاه ",
+ "reportAverageSpeed": "سرعت میانگین",
+ "reportMaximumSpeed": "حداکثر سرعت",
+ "reportEngineHours": "مدت زمان روشن بودن خودرو",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/fi.json b/web/l10n/fi.json
new file mode 100644
index 0000000..8df2d7e
--- /dev/null
+++ b/web/l10n/fi.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Ladataan...",
+ "sharedSave": "Tallenna",
+ "sharedCancel": "Peruuta",
+ "sharedAdd": "Lisää",
+ "sharedEdit": "Muokkaa",
+ "sharedRemove": "Poista",
+ "sharedRemoveConfirm": "Poista kohde?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Tunti",
+ "sharedMinute": "Minuutti",
+ "sharedSecond": "Sekunti",
+ "sharedName": "Name",
+ "sharedDescription": "Description",
+ "sharedSearch": "Search",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Virhe",
+ "errorUnknown": "Tuntematon virhe",
+ "errorConnection": "Yhteysvirhe",
+ "userEmail": "Email",
+ "userPassword": "Salasana",
+ "userAdmin": "Ylläpito",
+ "userRemember": "Remember",
+ "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",
+ "deviceIdentifier": "Tunniste",
+ "deviceLastUpdate": "Viimeisin päivitys",
+ "deviceCommand": "Komento",
+ "deviceFollow": "Seuraa",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "Asetukset",
+ "settingsUser": "Tili",
+ "settingsGroups": "Groups",
+ "settingsServer": "Palvelin",
+ "settingsUsers": "Käyttäjät",
+ "settingsSpeedUnit": "Nopeus",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Raportit",
+ "reportDevice": "Laite",
+ "reportGroup": "Group",
+ "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",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "Tila",
+ "stateName": "Ominaisuus",
+ "stateValue": "Arvo",
+ "commandTitle": "Komento",
+ "commandSend": "Lähetä",
+ "commandSent": "Komento on lähetetty",
+ "commandPositionPeriodic": "Määräaikaisraportointi",
+ "commandPositionStop": "Lopeta raportointi",
+ "commandEngineStop": "Sammuta moottori",
+ "commandEngineResume": "Palauta moottori",
+ "commandFrequency": "Taajuus",
+ "commandUnit": "Yksikkö",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/fr.json b/web/l10n/fr.json
new file mode 100644
index 0000000..afcc873
--- /dev/null
+++ b/web/l10n/fr.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Chargement...",
+ "sharedSave": "Enregistrer",
+ "sharedCancel": "Annuler",
+ "sharedAdd": "Ajouter",
+ "sharedEdit": "Editer",
+ "sharedRemove": "Effacer",
+ "sharedRemoveConfirm": "Effacer objet?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "nd",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Heure",
+ "sharedMinute": "Minute",
+ "sharedSecond": "Seconde",
+ "sharedName": "Nom",
+ "sharedDescription": "Description",
+ "sharedSearch": "Recherche",
+ "sharedGeofence": "Périmètre virtuel",
+ "sharedGeofences": "Périmètres virtuels",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributs",
+ "sharedAttribute": "Attribut",
+ "sharedArea": "Aire",
+ "sharedMute": "Muet",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Etat de la carte",
+ "errorTitle": "Erreur",
+ "errorUnknown": "Erreur inconnue",
+ "errorConnection": "Erreur de connexion",
+ "userEmail": "Email",
+ "userPassword": "Mot de Passe",
+ "userAdmin": "Admin",
+ "userRemember": "Rappel",
+ "loginTitle": "Identification",
+ "loginLanguage": "Langue",
+ "loginRegister": "Inscription",
+ "loginLogin": "Se connecter",
+ "loginFailed": "Adresse email ou mot de passe incorrect",
+ "loginCreated": "Nouvel utilisateur enregistré",
+ "loginLogout": "Déconnexion",
+ "devicesAndState": "Dispositifs et Etat",
+ "deviceDialog": "Dispositif",
+ "deviceTitle": "Dispositifs",
+ "deviceIdentifier": "Identifiant",
+ "deviceLastUpdate": "Dernière mise à jour",
+ "deviceCommand": "Commande",
+ "deviceFollow": "Suivre",
+ "groupDialog": "Groupe",
+ "groupParent": "Groupe",
+ "groupNoGroup": "Sans groupe",
+ "settingsTitle": "Paramètres",
+ "settingsUser": "Compte",
+ "settingsGroups": "Groupes",
+ "settingsServer": "Serveur",
+ "settingsUsers": "Utilisateurs",
+ "settingsSpeedUnit": "Vitesse",
+ "settingsTwelveHourFormat": "Format d'heure - 12 heures",
+ "reportTitle": "Rapports",
+ "reportDevice": "Dispositif",
+ "reportGroup": "Groupe",
+ "reportFrom": "De",
+ "reportTo": "A",
+ "reportShow": "Afficher",
+ "reportClear": "Effacer",
+ "positionFixTime": "Heure",
+ "positionValid": "Valide",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "Vitesse",
+ "positionCourse": "Orientation",
+ "positionAddress": "Adresse",
+ "positionProtocol": "Protocole",
+ "serverTitle": "Paramètres de serveur",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Inscription",
+ "serverReadonly": "Lecture seule",
+ "mapTitle": "Carte",
+ "mapLayer": "Couche cartographique",
+ "mapCustom": "Carte personnalisée",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Map Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Polygone",
+ "mapShapeCircle": "Cercle",
+ "stateTitle": "Etat",
+ "stateName": "Paramètre",
+ "stateValue": "Valeur",
+ "commandTitle": "Commande",
+ "commandSend": "Envoyer",
+ "commandSent": "Commande envoyée",
+ "commandPositionPeriodic": "Périodicité du rapport de position",
+ "commandPositionStop": "Arrêt du rapport de position",
+ "commandEngineStop": "Arrêt moteur",
+ "commandEngineResume": "Démarrage moteur",
+ "commandFrequency": "Fréquence",
+ "commandUnit": "Unité",
+ "commandCustom": "Commande personnalisée",
+ "commandPositionSingle": "Rapport de position unique",
+ "commandAlarmArm": "Activer l'alarme",
+ "commandAlarmDisarm": "Désactiver l'alarme",
+ "commandSetTimezone": "Régler le fuseau horaire",
+ "commandRequestPhoto": "Demander une photo",
+ "commandRebootDevice": "Redémarrer l'appareil",
+ "commandSendSms": "Envoyer un SMS",
+ "commandSendUssd": "Envoyer un USSD",
+ "commandSosNumber": "Régler le n° SOS",
+ "commandSilenceTime": "Définir le temps de silence",
+ "commandSetPhonebook": "Définir l'annuaire",
+ "commandVoiceMessage": "Message vocal",
+ "commandOutputControl": "Contrôle de la sortie",
+ "commandAlarmSpeed": "Alarme de dépassement de vitesse",
+ "commandDeviceIdentification": "Identification de l'appareil",
+ "commandIndex": "Index",
+ "commandData": "Données",
+ "commandPhone": "Numéro de téléphone",
+ "commandMessage": "Message",
+ "eventAll": "Tous les événements",
+ "eventDeviceOnline": "L'appareil est en ligne",
+ "eventDeviceOffline": "L'appareil est hors-ligne",
+ "eventDeviceMoving": "L'appareil est en mouvement",
+ "eventDeviceStopped": "L'appareil est arrêté",
+ "eventDeviceOverspeed": "L'appareil dépasse la vitesse",
+ "eventCommandResult": "Résultat de la commande",
+ "eventGeofenceEnter": "L'appareil est entré dans un périmètre virtuel",
+ "eventGeofenceExit": "L'appareil est sorti d'un périmètre virtuel",
+ "eventAlarm": "Alarmes",
+ "eventIgnitionOn": "Contact mis",
+ "eventIgnitionOff": "Contact coupé",
+ "alarm": "Alarme",
+ "alarmSos": "Alarme SOS",
+ "alarmVibration": "Alarme vibration",
+ "alarmMovement": "Alarme mouvement",
+ "alarmOverspeed": "Alarme de survitesse",
+ "alarmFallDown": "Alarme de chute",
+ "alarmLowBattery": "Alarme de batterie faible",
+ "alarmFault": "Alarme de problème",
+ "notificationType": "Type de notification",
+ "notificationWeb": "Envoyer par internet",
+ "notificationMail": "Envoyer par E-mail",
+ "reportRoute": "Route",
+ "reportEvents": "Evénements",
+ "reportTrips": "Trajets",
+ "reportSummary": "Résumé",
+ "reportConfigure": "Configurer",
+ "reportEventTypes": "Types d'événements",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Nom du dispositif",
+ "reportAverageSpeed": "Vitesse moyenne",
+ "reportMaximumSpeed": "Vitesse maximum",
+ "reportEngineHours": "Heures du moteur",
+ "reportDuration": "Durée",
+ "reportStartTime": "Date de départ",
+ "reportStartAddress": "Adresse de départ",
+ "reportEndTime": "Date de fin",
+ "reportEndAddress": "Adresse de fin",
+ "reportSpentFuel": "Consommation de carburant"
+} \ No newline at end of file
diff --git a/web/l10n/he.json b/web/l10n/he.json
new file mode 100644
index 0000000..d7f4edd
--- /dev/null
+++ b/web/l10n/he.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "בטעינה...",
+ "sharedSave": "שמור",
+ "sharedCancel": "ביטול",
+ "sharedAdd": "הוסף",
+ "sharedEdit": "ערוך",
+ "sharedRemove": "הסר",
+ "sharedRemoveConfirm": "הסר פריט",
+ "sharedKm": "ק\"מ",
+ "sharedMi": "מייל",
+ "sharedKn": "kn",
+ "sharedKmh": "ק\"מ/שעה",
+ "sharedMph": "מייל/שעה",
+ "sharedHour": "שעה",
+ "sharedMinute": "דקה",
+ "sharedSecond": "שנייה",
+ "sharedName": "שם",
+ "sharedDescription": "תיאור",
+ "sharedSearch": "חיפוש",
+ "sharedGeofence": "גדר וירטואלית",
+ "sharedGeofences": "גדרות וירטואליות",
+ "sharedNotifications": "התראות",
+ "sharedAttributes": "מאפיינים",
+ "sharedAttribute": "מאפיין",
+ "sharedArea": "איזור",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "שגיאה",
+ "errorUnknown": "שגיאה לא ידועה",
+ "errorConnection": "בעייה בחיבור",
+ "userEmail": "אימייל",
+ "userPassword": "סיסמה",
+ "userAdmin": "אדמין",
+ "userRemember": "זכור אותי",
+ "loginTitle": "כניסה",
+ "loginLanguage": "שפה",
+ "loginRegister": "הרשם",
+ "loginLogin": "כניסה",
+ "loginFailed": "אימייל או סיסמה שגויים",
+ "loginCreated": "משתמש חדש נרשם",
+ "loginLogout": "יציאה",
+ "devicesAndState": "מכשירים וסטטוס",
+ "deviceDialog": "מכשיר",
+ "deviceTitle": "מכשירים",
+ "deviceIdentifier": "מזהה",
+ "deviceLastUpdate": "עודכן לאחרונה",
+ "deviceCommand": "פקודה",
+ "deviceFollow": "עקוב",
+ "groupDialog": "קבוצה",
+ "groupParent": "קבוצה",
+ "groupNoGroup": "ללא קבוצה",
+ "settingsTitle": "הגדרות",
+ "settingsUser": "חשבון",
+ "settingsGroups": "קבוצות",
+ "settingsServer": "שרת",
+ "settingsUsers": "משתמשים",
+ "settingsSpeedUnit": "מהירות",
+ "settingsTwelveHourFormat": "פורמט של 12 שעות",
+ "reportTitle": "דו\"חות",
+ "reportDevice": "מכשיר",
+ "reportGroup": "Group",
+ "reportFrom": "מ-",
+ "reportTo": "עד",
+ "reportShow": "הצג",
+ "reportClear": "נקה",
+ "positionFixTime": "זמן",
+ "positionValid": "תקין",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "מהירות",
+ "positionCourse": "מסלול",
+ "positionAddress": "כתובת",
+ "positionProtocol": "פרוטוקול",
+ "serverTitle": "הגדרות שרת",
+ "serverZoom": "זום",
+ "serverRegistration": "הרשמה",
+ "serverReadonly": "לקריאה בלבד",
+ "mapTitle": "מפה",
+ "mapLayer": "שכבת מפה",
+ "mapCustom": "מפה בהתאמה",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "פוליגון",
+ "mapShapeCircle": "מעגל",
+ "stateTitle": "מצב",
+ "stateName": "תכונה",
+ "stateValue": "ערך",
+ "commandTitle": "פקודה",
+ "commandSend": "שליחה",
+ "commandSent": "הפקודה נשלחה",
+ "commandPositionPeriodic": "דיווח תקופתי",
+ "commandPositionStop": "עצור דיווח",
+ "commandEngineStop": "דומם מנוע",
+ "commandEngineResume": "הפעל מנוע",
+ "commandFrequency": "תדירות",
+ "commandUnit": "יחידה",
+ "commandCustom": "פקודה בהתאמה אישית",
+ "commandPositionSingle": "דו\"ח יחיד",
+ "commandAlarmArm": "הפעלת אזעקה",
+ "commandAlarmDisarm": "נטרול אזעקה",
+ "commandSetTimezone": "קבע איזור זמן",
+ "commandRequestPhoto": "בקשה לתמונה",
+ "commandRebootDevice": "איתחול המכשיר",
+ "commandSendSms": "שלח סמס",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "קבע מספר חירום",
+ "commandSilenceTime": "קבע משך זמן הדממה",
+ "commandSetPhonebook": "הגדר ספר טלפונים",
+ "commandVoiceMessage": "הודעה קולית",
+ "commandOutputControl": "בקרת פלט",
+ "commandAlarmSpeed": "התראת מהירות",
+ "commandDeviceIdentification": "זיהוי מכשיר",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "המכשיר און לין",
+ "eventDeviceOffline": "המכשיר מנותק",
+ "eventDeviceMoving": "המכשיר בתזוזה",
+ "eventDeviceStopped": "המכשיר עצר",
+ "eventDeviceOverspeed": "המכשיר עבר את המהירות המותרת",
+ "eventCommandResult": "תוצאות הפקודה",
+ "eventGeofenceEnter": "המכשיר נכנס לתחום המוגדר",
+ "eventGeofenceExit": "המכשיר יצא מהתחום המוגדר",
+ "eventAlarm": "אזעקות",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "אזעקה",
+ "alarmSos": "אתרעת SOS",
+ "alarmVibration": "אזעקת רטט",
+ "alarmMovement": "אזעקת תנועה",
+ "alarmOverspeed": "אזעקת מהירות יתר",
+ "alarmFallDown": "אזעקת נפילה",
+ "alarmLowBattery": "אזעקת סוללה חלשה",
+ "alarmFault": "אזעקת שווא",
+ "notificationType": "סוג ההתראה",
+ "notificationWeb": "שלח דרך ווב",
+ "notificationMail": "שלח באימייל",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/hi.json b/web/l10n/hi.json
new file mode 100644
index 0000000..a4ff7f0
--- /dev/null
+++ b/web/l10n/hi.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "लोड हो रहा है...",
+ "sharedSave": "सुरक्षित करें",
+ "sharedCancel": "रद्द करें ",
+ "sharedAdd": "जोड़ें ",
+ "sharedEdit": "संपादित करें",
+ "sharedRemove": "हटाएं ",
+ "sharedRemoveConfirm": "आइटम हटाएं ?",
+ "sharedKm": "किमी ( किलोमीटर )",
+ "sharedMi": "एम आई ",
+ "sharedKn": "के.एन.",
+ "sharedKmh": "किमी / घंटा",
+ "sharedMph": "मील प्रति घंटा",
+ "sharedHour": "घंटा",
+ "sharedMinute": "मिनट",
+ "sharedSecond": "सैकंड ",
+ "sharedName": "नाम",
+ "sharedDescription": "विवरण",
+ "sharedSearch": "खोजें",
+ "sharedGeofence": "जिओफेंस / भूगौलिक परिधि",
+ "sharedGeofences": "जिओफेंसस / भूगौलिक परिधियां",
+ "sharedNotifications": "सूचनाएं",
+ "sharedAttributes": "गुण",
+ "sharedAttribute": "गुण",
+ "sharedArea": "क्षेत्र",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "त्रुटि",
+ "errorUnknown": "अज्ञात त्रुटि",
+ "errorConnection": "कनेक्शन त्रुटि",
+ "userEmail": "ईमेल",
+ "userPassword": "पासवर्ड / गोपनीय शब्द ",
+ "userAdmin": "एडमिन / व्यवस्थापक",
+ "userRemember": "Remember",
+ "loginTitle": "लॉगिन / प्रवेश करें ",
+ "loginLanguage": "भाषा",
+ "loginRegister": "रजिस्टर / पंजीकृत करें",
+ "loginLogin": "लॉगिन / प्रवेश करें ",
+ "loginFailed": "ई-मेल पता या पासवर्ड गलत है",
+ "loginCreated": "New user has been registered",
+ "loginLogout": "लॉगआउट / निष्कासन करें",
+ "devicesAndState": "Devices and State",
+ "deviceDialog": "उपकरण",
+ "deviceTitle": "उपकरण",
+ "deviceIdentifier": "पहचानकर्ता",
+ "deviceLastUpdate": "Last Update",
+ "deviceCommand": "आदेश",
+ "deviceFollow": "Follow",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "सेटिंग्स",
+ "settingsUser": "Account",
+ "settingsGroups": "Groups",
+ "settingsServer": "सर्वर",
+ "settingsUsers": "Users",
+ "settingsSpeedUnit": "गति",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Reports",
+ "reportDevice": "उपकरण",
+ "reportGroup": "Group",
+ "reportFrom": "From",
+ "reportTo": "To",
+ "reportShow": "Show",
+ "reportClear": "Clear",
+ "positionFixTime": "समय",
+ "positionValid": "Valid",
+ "positionLatitude": "अक्षांश / अक्षरेखा",
+ "positionLongitude": "देशान्तर",
+ "positionAltitude": "ऊंचाई",
+ "positionSpeed": "गति",
+ "positionCourse": "मार्ग",
+ "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",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "State",
+ "stateName": "Attribute",
+ "stateValue": "Value",
+ "commandTitle": "आदेश",
+ "commandSend": "भेजें / प्रेषित करें",
+ "commandSent": "कमांड / आदेश भेज दी गयी है ",
+ "commandPositionPeriodic": "Periodic Reporting",
+ "commandPositionStop": "Stop Reporting",
+ "commandEngineStop": "Engine Stop",
+ "commandEngineResume": "Engine Resume",
+ "commandFrequency": "फ्रीक्वेंसी / आवृत्ति",
+ "commandUnit": "इकाई",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/hu.json b/web/l10n/hu.json
new file mode 100644
index 0000000..151dccb
--- /dev/null
+++ b/web/l10n/hu.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Betöltés...",
+ "sharedSave": "Mentés",
+ "sharedCancel": "Mégse",
+ "sharedAdd": "Hozzáadás",
+ "sharedEdit": "Szerkesztés",
+ "sharedRemove": "Törlés",
+ "sharedRemoveConfirm": "Biztosan törli?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "csomó",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Óra",
+ "sharedMinute": "Perc",
+ "sharedSecond": "Másodperc",
+ "sharedName": "Név",
+ "sharedDescription": "Leírás",
+ "sharedSearch": "Keresés",
+ "sharedGeofence": "Geokerítés",
+ "sharedGeofences": "Geokerítések",
+ "sharedNotifications": "Értesítések",
+ "sharedAttributes": "Tulajdonságok",
+ "sharedAttribute": "Tulajdonság",
+ "sharedArea": "Terület",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Hiba",
+ "errorUnknown": "Ismeretlen hiba",
+ "errorConnection": "Kapcsolódási hiba",
+ "userEmail": "Email",
+ "userPassword": "Jelszó",
+ "userAdmin": "Adminisztrátor",
+ "userRemember": "Remember",
+ "loginTitle": "Bejelentkezés",
+ "loginLanguage": "Nyelv",
+ "loginRegister": "Regisztráció",
+ "loginLogin": "Bejelentkezés",
+ "loginFailed": "Hibás email vagy jelszó",
+ "loginCreated": "Az új felhasználó sikeresen létrehozva",
+ "loginLogout": "Kilépés",
+ "devicesAndState": "Eszközök és állapotuk",
+ "deviceDialog": "Eszköz",
+ "deviceTitle": "Eszközök",
+ "deviceIdentifier": "Azonosító",
+ "deviceLastUpdate": "Utolsó frissítés",
+ "deviceCommand": "Parancs",
+ "deviceFollow": "Követ",
+ "groupDialog": "Csoport",
+ "groupParent": "Csoport",
+ "groupNoGroup": "Nincs Csoport",
+ "settingsTitle": "Beállítások",
+ "settingsUser": "Fiók",
+ "settingsGroups": "Csoportok",
+ "settingsServer": "Szerver",
+ "settingsUsers": "Felhasználók",
+ "settingsSpeedUnit": "Sebesség",
+ "settingsTwelveHourFormat": "12-órás formátum",
+ "reportTitle": "Jelentések",
+ "reportDevice": "Eszköz",
+ "reportGroup": "Group",
+ "reportFrom": "Kezdő dátum:",
+ "reportTo": "Végső dátum:",
+ "reportShow": "Mutat",
+ "reportClear": "Töröl",
+ "positionFixTime": "Idő",
+ "positionValid": "Valós",
+ "positionLatitude": "Szélességi fok",
+ "positionLongitude": "Hosszúsági fok",
+ "positionAltitude": "Magasság",
+ "positionSpeed": "Sebesség",
+ "positionCourse": "Irány",
+ "positionAddress": "Cím",
+ "positionProtocol": "Protokoll",
+ "serverTitle": "Szerver beállítások",
+ "serverZoom": "Nagyítás",
+ "serverRegistration": "Regisztráció",
+ "serverReadonly": "Csak olvasható",
+ "mapTitle": "Térkép",
+ "mapLayer": "Térkép réteg",
+ "mapCustom": "Egyéni térkép",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps kulcs",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Poligon",
+ "mapShapeCircle": "Kör",
+ "stateTitle": "Helyzet",
+ "stateName": "Paraméter",
+ "stateValue": "Érték",
+ "commandTitle": "Parancs",
+ "commandSend": "Küld",
+ "commandSent": "A parancs elküldve",
+ "commandPositionPeriodic": "Pozició küldés",
+ "commandPositionStop": "Pozició küldés vége",
+ "commandEngineStop": "Motor letiltás",
+ "commandEngineResume": "Motor engedélyezés",
+ "commandFrequency": "Frekvencia",
+ "commandUnit": "Egység",
+ "commandCustom": "Egyedi parancs",
+ "commandPositionSingle": "Egyszeri jelentés",
+ "commandAlarmArm": "Riasztó élesítés",
+ "commandAlarmDisarm": "Riasztó kikapcsolás",
+ "commandSetTimezone": "Időzóna beállítás",
+ "commandRequestPhoto": "Kép lekérés",
+ "commandRebootDevice": "Eszköz újraindítása",
+ "commandSendSms": "SMS küldés",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "SOS szám beállítás",
+ "commandSilenceTime": "Csendes idő beállítás",
+ "commandSetPhonebook": "Telefonkönyv beállítás",
+ "commandVoiceMessage": "Hangüzenet",
+ "commandOutputControl": "Kimenet Ellenőrzés",
+ "commandAlarmSpeed": "Riasztás Gyorshajtásról",
+ "commandDeviceIdentification": "Eszköz azonosító",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Eszköz online",
+ "eventDeviceOffline": "Eszköz offline",
+ "eventDeviceMoving": "Eszköz mozog",
+ "eventDeviceStopped": "Eszköz megállt",
+ "eventDeviceOverspeed": "Eszköz túllépte a sebességkorlátot",
+ "eventCommandResult": "Parancs eredmény",
+ "eventGeofenceEnter": "Eszköz belépett a geokerítésbe",
+ "eventGeofenceExit": "Eszköz kilépett a geokerítésből",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Értesítés Típusa",
+ "notificationWeb": "Küldés Weben",
+ "notificationMail": "Küldés E-mailben",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/id.json b/web/l10n/id.json
new file mode 100644
index 0000000..1040574
--- /dev/null
+++ b/web/l10n/id.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Memuat...",
+ "sharedSave": "Simpan",
+ "sharedCancel": "Batal",
+ "sharedAdd": "Tambah",
+ "sharedEdit": "Ubah",
+ "sharedRemove": "Hapus",
+ "sharedRemoveConfirm": "Hapus item?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mp/h",
+ "sharedHour": "Jam",
+ "sharedMinute": "Menit",
+ "sharedSecond": "Detik",
+ "sharedName": "Nama",
+ "sharedDescription": "Description",
+ "sharedSearch": "Cari",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Error",
+ "errorUnknown": "Error tidak diketahui",
+ "errorConnection": "Koneksi error",
+ "userEmail": "Email",
+ "userPassword": "Sandi",
+ "userAdmin": "Admin",
+ "userRemember": "Remember",
+ "loginTitle": "Masuk",
+ "loginLanguage": "Bahasa",
+ "loginRegister": "Daftar",
+ "loginLogin": "Masuk",
+ "loginFailed": "Email atau password salah",
+ "loginCreated": "Pengguna baru telah terdaftar",
+ "loginLogout": "Keluar",
+ "devicesAndState": "Perangkat dan Status",
+ "deviceDialog": "Perangkat",
+ "deviceTitle": "Perangkat",
+ "deviceIdentifier": "Identifikasi",
+ "deviceLastUpdate": "Terbaru",
+ "deviceCommand": "Perintah",
+ "deviceFollow": "Ikuti",
+ "groupDialog": "Grup",
+ "groupParent": "Grup",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "Pengaturan",
+ "settingsUser": "Akun",
+ "settingsGroups": "Grup",
+ "settingsServer": "Server",
+ "settingsUsers": "Pengguna",
+ "settingsSpeedUnit": "Kecepatan",
+ "settingsTwelveHourFormat": "Format 12 Jam",
+ "reportTitle": "Laporan",
+ "reportDevice": "Perangkat",
+ "reportGroup": "Group",
+ "reportFrom": "Dari",
+ "reportTo": "Ke",
+ "reportShow": "Tampil",
+ "reportClear": "Bersihkan",
+ "positionFixTime": "Waktu",
+ "positionValid": "Benar",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Ketinggian",
+ "positionSpeed": "Kecepatan",
+ "positionCourse": "Arah",
+ "positionAddress": "Alamat",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Pengaturan Server",
+ "serverZoom": "Perbesar",
+ "serverRegistration": "Pendaftaran",
+ "serverReadonly": "Hanya Dilihat",
+ "mapTitle": "Peta",
+ "mapLayer": "Layer Peta",
+ "mapCustom": "Peta Buatan",
+ "mapOsm": "Peta Open Street",
+ "mapBingKey": "Key untuk Peta Bing",
+ "mapBingRoad": "Peta Jalan Bing",
+ "mapBingAerial": "Peta Udara Bing",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "Status",
+ "stateName": "atribut",
+ "stateValue": "Nilai",
+ "commandTitle": "Perintah",
+ "commandSend": "Kirim",
+ "commandSent": "Perintah terkirim",
+ "commandPositionPeriodic": "Laporan berkala",
+ "commandPositionStop": "Stop Laporan",
+ "commandEngineStop": "Stop Mesin",
+ "commandEngineResume": "Mulai Mesin",
+ "commandFrequency": "Frekuensi",
+ "commandUnit": "unit",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/it.json b/web/l10n/it.json
new file mode 100644
index 0000000..51ba448
--- /dev/null
+++ b/web/l10n/it.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Loading...",
+ "sharedSave": "Salva",
+ "sharedCancel": "Cancella",
+ "sharedAdd": "Aggiungi",
+ "sharedEdit": "Modifica",
+ "sharedRemove": "Rimuovi",
+ "sharedRemoveConfirm": "Rimuovere oggetto?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Ora",
+ "sharedMinute": "Minuto",
+ "sharedSecond": "Secondo",
+ "sharedName": "Nome",
+ "sharedDescription": "Descrizione",
+ "sharedSearch": "Cerca",
+ "sharedGeofence": "GeoRecinto",
+ "sharedGeofences": "GeoRecinto",
+ "sharedNotifications": "Notifiche",
+ "sharedAttributes": "Attributi",
+ "sharedAttribute": "Attributo",
+ "sharedArea": "Area",
+ "sharedMute": "Muto",
+ "sharedType": "Tipo",
+ "sharedDistance": "Distanza",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Ottieni Stato Mappa",
+ "errorTitle": "Errore",
+ "errorUnknown": "Errore sconosciuto",
+ "errorConnection": "Errore di connessione",
+ "userEmail": "Email",
+ "userPassword": "Password",
+ "userAdmin": "Admin",
+ "userRemember": "Ricorda",
+ "loginTitle": "Login",
+ "loginLanguage": "Lingua",
+ "loginRegister": "Registrazione",
+ "loginLogin": "Login",
+ "loginFailed": "Indirizzo email o password errati",
+ "loginCreated": "Un nuovo utente si e` registrato",
+ "loginLogout": "Logout",
+ "devicesAndState": "Dispositivi e stato",
+ "deviceDialog": "Dispositivo",
+ "deviceTitle": "Dispositivi",
+ "deviceIdentifier": "Identificativo",
+ "deviceLastUpdate": "Ultimo aggiornamento",
+ "deviceCommand": "Comando",
+ "deviceFollow": "Segui",
+ "groupDialog": "Gruppo",
+ "groupParent": "Gruppo",
+ "groupNoGroup": "Nessun Gruppo",
+ "settingsTitle": "Impostazioni",
+ "settingsUser": "Account",
+ "settingsGroups": "Gruppi",
+ "settingsServer": "Server",
+ "settingsUsers": "Utenti",
+ "settingsSpeedUnit": "Velocità",
+ "settingsTwelveHourFormat": "Formato 12 ore",
+ "reportTitle": "Reports",
+ "reportDevice": "Dispositivo",
+ "reportGroup": "Gruppo",
+ "reportFrom": "Da",
+ "reportTo": "A",
+ "reportShow": "Visualizza",
+ "reportClear": "Pulisci",
+ "positionFixTime": "Tempo",
+ "positionValid": "Valido",
+ "positionLatitude": "Latitudine",
+ "positionLongitude": "Longitudine",
+ "positionAltitude": "Altitudine",
+ "positionSpeed": "Velocità",
+ "positionCourse": "Percorso",
+ "positionAddress": "Indirizzo",
+ "positionProtocol": "Protocollo",
+ "serverTitle": "Impostazioni Server",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registrazione",
+ "serverReadonly": "Sola lettura",
+ "mapTitle": "Mappa",
+ "mapLayer": "Livelli Mappa",
+ "mapCustom": "Mappa Personalizzata",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Poligono",
+ "mapShapeCircle": "Cerchio",
+ "stateTitle": "Stato",
+ "stateName": "Attributo",
+ "stateValue": "Valore",
+ "commandTitle": "Commando",
+ "commandSend": "Invia",
+ "commandSent": "Commando inviato",
+ "commandPositionPeriodic": "Report periodici",
+ "commandPositionStop": "Ferma i report",
+ "commandEngineStop": "Ferma Engine",
+ "commandEngineResume": "Riavvio Engine",
+ "commandFrequency": "Frequenza",
+ "commandUnit": "Unità ",
+ "commandCustom": "Comando personalizzato",
+ "commandPositionSingle": "Report singolo",
+ "commandAlarmArm": "Attiva allarme",
+ "commandAlarmDisarm": "Disattiva Allarme",
+ "commandSetTimezone": "Imposta Timezone",
+ "commandRequestPhoto": "Richiedi foto",
+ "commandRebootDevice": "Riavvia dispositivo",
+ "commandSendSms": "Invia SMS",
+ "commandSendUssd": "Invia USSD",
+ "commandSosNumber": "Imposta Numero SOS",
+ "commandSilenceTime": "Imposta Orario Silenzione",
+ "commandSetPhonebook": "Imposta rubrica",
+ "commandVoiceMessage": "Messaggio vocale",
+ "commandOutputControl": "Controllo Output",
+ "commandAlarmSpeed": "Allarme Velocità Elevata",
+ "commandDeviceIdentification": "Identificativo dispositivo",
+ "commandIndex": "Indice",
+ "commandData": "Dati",
+ "commandPhone": "Numero Telefonico",
+ "commandMessage": "Messaggio",
+ "eventAll": "Tutti gli Eventi",
+ "eventDeviceOnline": "Dispositivo online",
+ "eventDeviceOffline": "Dispositivo offline",
+ "eventDeviceMoving": "Dispositivo in movimento",
+ "eventDeviceStopped": "Dispositivo fermo",
+ "eventDeviceOverspeed": "Dispostivo troppo veloce",
+ "eventCommandResult": "Risultato comando",
+ "eventGeofenceEnter": "Il dipositivo e` entrato nel GeoRecinto",
+ "eventGeofenceExit": "Il dipositivo e` uscito dal GeoRecinto",
+ "eventAlarm": "Allarmi",
+ "eventIgnitionOn": "Accensione è inserita",
+ "eventIgnitionOff": "Accensione è disinserita",
+ "alarm": "Allarme",
+ "alarmSos": "Allarme SOS",
+ "alarmVibration": "Allarme Vibrazione",
+ "alarmMovement": "Allarme Movimento",
+ "alarmOverspeed": "Allarme Velocità Elevata",
+ "alarmFallDown": "Allarme Caduta",
+ "alarmLowBattery": "Allarme Livello Batteria Basso",
+ "alarmFault": "Allarme Guasto",
+ "notificationType": "Tipo notica",
+ "notificationWeb": "Invia tramite Web",
+ "notificationMail": "Invia tramite Mail",
+ "reportRoute": "Percorso",
+ "reportEvents": "Eventi",
+ "reportTrips": "Viaggi",
+ "reportSummary": "Sommario",
+ "reportConfigure": "Configura",
+ "reportEventTypes": "Tipi Evento",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Nome Dispositivo",
+ "reportAverageSpeed": "Velocità Media",
+ "reportMaximumSpeed": "Velocità Massima",
+ "reportEngineHours": "Ore del Engine",
+ "reportDuration": "Durata",
+ "reportStartTime": "Ora di inizio",
+ "reportStartAddress": "Indirizzo iniziale",
+ "reportEndTime": "Tempo finale",
+ "reportEndAddress": "Indirizzo finale",
+ "reportSpentFuel": "Carburante Consumato"
+} \ No newline at end of file
diff --git a/web/l10n/ka.json b/web/l10n/ka.json
new file mode 100644
index 0000000..5460a6d
--- /dev/null
+++ b/web/l10n/ka.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "იტვირთება...",
+ "sharedSave": "შენახვა",
+ "sharedCancel": "უარყოფა",
+ "sharedAdd": "დამატება",
+ "sharedEdit": "შეცვლა",
+ "sharedRemove": "წაშლა",
+ "sharedRemoveConfirm": "გსურთ წაშლა ?",
+ "sharedKm": "კმ",
+ "sharedMi": "მლ",
+ "sharedKn": "kn",
+ "sharedKmh": "კმ/სთ",
+ "sharedMph": "მლ/სთ",
+ "sharedHour": "საათი",
+ "sharedMinute": "წუთი",
+ "sharedSecond": "წამი",
+ "sharedName": "დასახელება",
+ "sharedDescription": "Description",
+ "sharedSearch": "ძებნა",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "შეცდომა",
+ "errorUnknown": "უცნობი შეცდომა",
+ "errorConnection": "კავშირის შეცდომა",
+ "userEmail": "ელ-ფოსტა",
+ "userPassword": "პაროლი",
+ "userAdmin": "ადმინი",
+ "userRemember": "Remember",
+ "loginTitle": "ავტორიზაცია",
+ "loginLanguage": "ენა",
+ "loginRegister": "რეგისტრაცია",
+ "loginLogin": "შესვლა",
+ "loginFailed": "არასწორი ელ-ფოსტა ან პაროლი",
+ "loginCreated": "ახალი მომხარებელი დარეგისტრირდა",
+ "loginLogout": "გამოსვლა",
+ "devicesAndState": "მოწყობილობები და სტატუსი",
+ "deviceDialog": "მოწყობილობა",
+ "deviceTitle": "მოწყობილობები",
+ "deviceIdentifier": "იდენტიფიკატორი",
+ "deviceLastUpdate": "ბოლო განახლება",
+ "deviceCommand": "ბრძანება",
+ "deviceFollow": "გაყოლა",
+ "groupDialog": "ჯგუფი",
+ "groupParent": "ჯგუფი",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "პარამეტრები",
+ "settingsUser": "პროფილი",
+ "settingsGroups": "ჯგუფები",
+ "settingsServer": "სერვერი",
+ "settingsUsers": "მომხამრებლები",
+ "settingsSpeedUnit": "სიჩქარე",
+ "settingsTwelveHourFormat": "12-საათიანი ფორმატი",
+ "reportTitle": "რეპორტები",
+ "reportDevice": "მოწყობილობა",
+ "reportGroup": "Group",
+ "reportFrom": "დან",
+ "reportTo": "მდე",
+ "reportShow": "ჩვენება",
+ "reportClear": "გასუფთავება",
+ "positionFixTime": "დრო",
+ "positionValid": "ვარგისი",
+ "positionLatitude": "განედი",
+ "positionLongitude": "გრძედი",
+ "positionAltitude": "სიმაღლე",
+ "positionSpeed": "სიჩქარე",
+ "positionCourse": "კურსი",
+ "positionAddress": "მისამართი",
+ "positionProtocol": "პროტოკოლი",
+ "serverTitle": "სერვერის პარამეტრები",
+ "serverZoom": "ზუმი",
+ "serverRegistration": "რეგისტრაცია",
+ "serverReadonly": "მხოლოდ ნახვის",
+ "mapTitle": "რუკა",
+ "mapLayer": "რუკის ფენა",
+ "mapCustom": "მომხმარებლის რუკა",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "სტატუსი",
+ "stateName": "ატრიბუტი",
+ "stateValue": "მნიშვნელობა",
+ "commandTitle": "ბრძანება",
+ "commandSend": "გაგზავნა",
+ "commandSent": "ბრძანება გაიგზავნა",
+ "commandPositionPeriodic": "პერიოდული რეპორტი",
+ "commandPositionStop": "რეპორტის შეჩერება",
+ "commandEngineStop": "ძრავის გამორთვა",
+ "commandEngineResume": "ძრავის ჩართვა",
+ "commandFrequency": "სიხშირე",
+ "commandUnit": "ერთეული",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/lo.json b/web/l10n/lo.json
new file mode 100644
index 0000000..7c52a3f
--- /dev/null
+++ b/web/l10n/lo.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "ກຳລັງໂຫລດ...",
+ "sharedSave": "ບັນທຶກ",
+ "sharedCancel": "ຍົກເລີກ",
+ "sharedAdd": "ເພີ່ມ",
+ "sharedEdit": "ແກ້ໄຂ",
+ "sharedRemove": "ລົບອອກ",
+ "sharedRemoveConfirm": "ລົບລາຍການນີ້ບໍ່?",
+ "sharedKm": "ກມ.",
+ "sharedMi": "ໄມລ໌",
+ "sharedKn": "ນ໊ອດ",
+ "sharedKmh": "ກມ. /ຊມ.",
+ "sharedMph": "ໄມລ໌ຕໍ່ຊົ່ວໂມງ",
+ "sharedHour": "ຊົ່ວໂມງ",
+ "sharedMinute": "ນາທີ",
+ "sharedSecond": "ວິນາທີ",
+ "sharedName": "ຊື່",
+ "sharedDescription": "ລັກສະນະ",
+ "sharedSearch": "ຄົ້ນຫາ",
+ "sharedGeofence": "ເຂດພື້ນທີ່",
+ "sharedGeofences": "ເຂດພື້ນທີ່",
+ "sharedNotifications": "ການແຈ້ງເຕືອນ",
+ "sharedAttributes": "ຄຸນລັກສະນະ",
+ "sharedAttribute": "ຄຸນລັກສະນະ",
+ "sharedArea": "ພື້ນທີ່",
+ "sharedMute": "ປິດສຽງ",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "ຜິດພາດ",
+ "errorUnknown": "ຂໍ້ຜິດພາດທີ່ບໍ່ຮູ້ຈັກ",
+ "errorConnection": "ການເຊື່ອມຕໍ່ຜິດພາດ",
+ "userEmail": "ອີເມວ",
+ "userPassword": "ລະຫັດຜ່ານ",
+ "userAdmin": "ຜູ້ເບິ່ງແຍງລະບົບ",
+ "userRemember": "ຈື່ໄວ້",
+ "loginTitle": "ເຂົ້າສູ່ລະບົບ",
+ "loginLanguage": "ພາສາ",
+ "loginRegister": "ລົງທະບຽນ",
+ "loginLogin": "ເຂົ້າສູ່ລະບົບ",
+ "loginFailed": "ທີ່ຢູ່ອີເມວຫລືລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ",
+ "loginCreated": "ຜູ້ໃຊ້ໃຫມ່ ໄດ້ຮັບການລົງທະບຽນ",
+ "loginLogout": "ອອກຈາກລະບົບ",
+ "devicesAndState": "ອຸປະກອນແລະສະຖານະ",
+ "deviceDialog": "ເຄື່ອງ/ອຸປະກອນ",
+ "deviceTitle": "ເຄື່ອງ/ອຸປະກອນ",
+ "deviceIdentifier": "ລະບຸເລກອຸປະກອນ",
+ "deviceLastUpdate": "ແກ້ໄຂລ່າສຸດ",
+ "deviceCommand": "ຄຳສັ່ງ",
+ "deviceFollow": "ຕິດຕາມ",
+ "groupDialog": "ກຸ່ມ",
+ "groupParent": "ກຸ່ມ",
+ "groupNoGroup": "ບໍ່ຈັດໃນກຸ່ມ",
+ "settingsTitle": "ການຕັ້ງຄ່າ",
+ "settingsUser": "ບັນຊີຜູ້ໃຊ້",
+ "settingsGroups": "ຕັ້ງຄ່າກຸ່ມ",
+ "settingsServer": "ຕັ້ງຄ່າລະບົບ",
+ "settingsUsers": "ຕັ້ງຄ່າຜູ້ໃຊ້ງານ",
+ "settingsSpeedUnit": "ຫນ່ວຍຄວາມໄວ",
+ "settingsTwelveHourFormat": "ຮູບແບບເວລາ 12 ຊົ່ວໂມງ",
+ "reportTitle": "ລາຍງານ",
+ "reportDevice": "ລາຍງານເຄື່ອງ/ອຸປະກອນ",
+ "reportGroup": "Group",
+ "reportFrom": "ຈາກ",
+ "reportTo": "ໄປເຖິງ",
+ "reportShow": "ສະແດງ",
+ "reportClear": "ລົບລ້າງລາຍງານ",
+ "positionFixTime": "ເວລາ",
+ "positionValid": "ຖືກຕ້ອງ",
+ "positionLatitude": "ລາຕິຈູດ",
+ "positionLongitude": "ລອງຈິຈູດ",
+ "positionAltitude": "ລະດັບຄວາມສູງ",
+ "positionSpeed": "ຄວາມໄວ",
+ "positionCourse": "ທິດທາງ",
+ "positionAddress": "ທີ່ຢູ່",
+ "positionProtocol": "ໂປຣໂຕຄໍລ໌",
+ "serverTitle": "ການຕັ້ງຄ່າເຊີເວີ້",
+ "serverZoom": "ຂະຫຍາຍ +/-",
+ "serverRegistration": "ລົງທະບຽນ",
+ "serverReadonly": "ອ່ານໄດ້ຢ່າງດຽວ",
+ "mapTitle": "ແຜ່ນທີ",
+ "mapLayer": "ຊັ້ນແຜ່ນທີ",
+ "mapCustom": "ແຜ່ນທີ່ທີ່ກຳຫນົດເອງ",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps ສຳຄັນ",
+ "mapBingRoad": "Bing Maps ຖະຫນົນ",
+ "mapBingAerial": "Bing Maps ທາງອາກາດ",
+ "mapShapePolygon": "ໂພລີກອນ",
+ "mapShapeCircle": "ວົງກົມ",
+ "stateTitle": "ສະຖານະ",
+ "stateName": "ຄຸນລັກສະນະ",
+ "stateValue": "ມູນຄ່າ",
+ "commandTitle": "ຄຳສັ່ງ",
+ "commandSend": "ສົ່ງ",
+ "commandSent": "ຄຳສັ່ງໄດ້ຖືກສົ່ງແລ້ວ",
+ "commandPositionPeriodic": "ແກ້ໄຂຕ່ຳແຫນ່ງ",
+ "commandPositionStop": "ຕ່ຳແຫນ່ງ ຢຸດ",
+ "commandEngineStop": "ດັບເຄື່ອງຈັກ",
+ "commandEngineResume": "ຕິດເຄື່ອງຈັກຄືນໃຫມ່",
+ "commandFrequency": "ຄວາມຖີ່",
+ "commandUnit": "ຫນ່ວຍ",
+ "commandCustom": "ຄຳສັ່ງກຳຫນົດເອງ",
+ "commandPositionSingle": "ລາຍງານຕ່ຳແຫນ່ງດຽວ",
+ "commandAlarmArm": "ແຈ້ງເຕືອນຕິດຕໍ່ສາຂາ",
+ "commandAlarmDisarm": "ແຈ້ງເຕືອນຍົກເລີກຕິດຕໍ່ສາຂາ",
+ "commandSetTimezone": "ຕັ້ງຄ່າເຂດເວລາ",
+ "commandRequestPhoto": "ສັ່ງຖ່າຍຮູບ",
+ "commandRebootDevice": "ຣີບູດ",
+ "commandSendSms": "ສົ່ງ SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "ຕັ້ງຄ່າເລກໝາຍໂທສຸກເສີນ SOS",
+ "commandSilenceTime": "ຕັ້ງຄ່າຊ່ວງເວລາຢຸດນິ່ງ",
+ "commandSetPhonebook": "ຕັ້ງຄ່າສະໝຸດໂທລະສັບ",
+ "commandVoiceMessage": "ຂໍ້ຄວາມສຽງ",
+ "commandOutputControl": "ຄວບຄຸມຂໍ້ມູນທີ່ສົ່ງອອກ",
+ "commandAlarmSpeed": "ແຈ້ງເຕືອນຄວາມໄວເກີນກຳນົດ",
+ "commandDeviceIdentification": "ໝາຍເລກອຸປະກອນ",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "ອຸປະກອນເຊື່ອມຕໍ່ແລ້ວ",
+ "eventDeviceOffline": "ອຸປະກອນບໍ່ໄດ້ເຊື່ອມຕໍ່",
+ "eventDeviceMoving": "ອຸປະກອນກຳລັງເຄື່ອນທີ່",
+ "eventDeviceStopped": "ອຸປະກອນບໍ່ເຄື່ອນໄຫວ",
+ "eventDeviceOverspeed": "ອຸປະກອນເກີນກຳນົດຄວາມໄວ",
+ "eventCommandResult": "ຜົນຮັບຈາກຄຳສັ່ງ",
+ "eventGeofenceEnter": "ອຸປະກອນເຂົ້າໃນເຂດພື້ນທີ່",
+ "eventGeofenceExit": "ອຸປະກອນອອກນອກເຂດພື້ນທີ່",
+ "eventAlarm": "ລາຍການແຈ້ງເຕືອນ",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "ແຈ້ງເຕືອນ",
+ "alarmSos": "ແຈ້ງເຕືອນ SOS",
+ "alarmVibration": "ແຈ້ງເຕືອນແບບສັ່ນ",
+ "alarmMovement": "ແຈ້ງເຕືອນມີການເຄື່ອນທີ່",
+ "alarmOverspeed": "ແຈ້ງເຕືອນຄວາມໄວສູງເກີນກຳນົດ",
+ "alarmFallDown": "ແຈ້ງເຕືອນການຕົກ",
+ "alarmLowBattery": "ແຈ້ງເຕືອນແບັດເຕີລີ້ອ່ອນ",
+ "alarmFault": "ແຈ້ງເຕື່ອນຜິດພາດ",
+ "notificationType": "ຊະນິດການແຈ້ງເຕືອນ",
+ "notificationWeb": "ສົ່ງທາງເວັບ",
+ "notificationMail": "ສົ່ງທາງເມວ",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/lt.json b/web/l10n/lt.json
new file mode 100644
index 0000000..adfb51b
--- /dev/null
+++ b/web/l10n/lt.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Kraunasi..",
+ "sharedSave": "Išsaugoti",
+ "sharedCancel": "Atšaukti",
+ "sharedAdd": "Pridėti",
+ "sharedEdit": "Redaguoti",
+ "sharedRemove": "Ištrinti",
+ "sharedRemoveConfirm": "Ar tikrais norite ištrinti?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "mazgai",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Valanda(-os)",
+ "sharedMinute": "Minutė(-es)",
+ "sharedSecond": "Sekundė(-es)",
+ "sharedName": "Pavadinimas",
+ "sharedDescription": "Description",
+ "sharedSearch": "Paieška",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Klaida",
+ "errorUnknown": "Nenumatyta klaida",
+ "errorConnection": "Ryšio klaida",
+ "userEmail": "Vartotojo vardas",
+ "userPassword": "Slaptažodis",
+ "userAdmin": "Administratorius",
+ "userRemember": "Remember",
+ "loginTitle": "Prisijungimas",
+ "loginLanguage": "Kalba",
+ "loginRegister": "Registruotis",
+ "loginLogin": "Prisijungti",
+ "loginFailed": "Neteisingas el.paštas ir/ar slaptažodis",
+ "loginCreated": "Registracija sėkminga",
+ "loginLogout": "Atsijungti",
+ "devicesAndState": "Prietaisai ir Statusas",
+ "deviceDialog": "Prietaisas",
+ "deviceTitle": "Prietaisai",
+ "deviceIdentifier": "Identifikacinis kodas",
+ "deviceLastUpdate": "Naujausias atnaujinimas",
+ "deviceCommand": "Komanda",
+ "deviceFollow": "Sekti",
+ "groupDialog": "Grupė",
+ "groupParent": "Grupė",
+ "groupNoGroup": "Nenurodyta grupė",
+ "settingsTitle": "Nustatymai",
+ "settingsUser": "Paskyra",
+ "settingsGroups": "Grupės",
+ "settingsServer": "Serveris",
+ "settingsUsers": "Vartotojai",
+ "settingsSpeedUnit": "Greitis",
+ "settingsTwelveHourFormat": "12-val formatas",
+ "reportTitle": "Ataskaita",
+ "reportDevice": "Prietaisas",
+ "reportGroup": "Group",
+ "reportFrom": "Nuo",
+ "reportTo": "Iki",
+ "reportShow": "Rodyti",
+ "reportClear": "Valyti",
+ "positionFixTime": "Laikas",
+ "positionValid": "Galiojantis",
+ "positionLatitude": "Platuma",
+ "positionLongitude": "Ilguma",
+ "positionAltitude": "Aukštis",
+ "positionSpeed": "Greitis",
+ "positionCourse": "Eiga",
+ "positionAddress": "Adresas",
+ "positionProtocol": "Protokolas",
+ "serverTitle": "Serverio nustatymai",
+ "serverZoom": "Priartinimas",
+ "serverRegistration": "Registracija",
+ "serverReadonly": "Skaitymo",
+ "mapTitle": "Žemėlapis",
+ "mapLayer": "Žemėlapio sluoksnis",
+ "mapCustom": "Pasirinktinis Žemėlapis",
+ "mapOsm": "Open Street žemėlapis",
+ "mapBingKey": "Bing Maps raktas",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "Būklė",
+ "stateName": "Parametras",
+ "stateValue": "Reikšmė",
+ "commandTitle": "Komanda",
+ "commandSend": "Siųsti",
+ "commandSent": "Komanda buvo išsiųsta",
+ "commandPositionPeriodic": "Periodinės ataskaitos",
+ "commandPositionStop": "Stabdyti ataskaitas",
+ "commandEngineStop": "Stabdyti variklį",
+ "commandEngineResume": "Paleisti variklį",
+ "commandFrequency": "Dažnis",
+ "commandUnit": "Vienetai",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/ml.json b/web/l10n/ml.json
new file mode 100644
index 0000000..42a0c49
--- /dev/null
+++ b/web/l10n/ml.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "ലോഡുചെയ്യുന്നു ..",
+ "sharedSave": "രക്ഷിക്കും",
+ "sharedCancel": "റദ്ദാക്കുക",
+ "sharedAdd": "ചേര്‍ക്കുക",
+ "sharedEdit": "തിരുത്തുക",
+ "sharedRemove": "നീക്കം ചെയ്യുക",
+ "sharedRemoveConfirm": "വിഷയം നീക്കം ചെയ്യുക",
+ "sharedKm": "കിലോമീറ്റർ",
+ "sharedMi": "നാഴിക",
+ "sharedKn": "കുരുക്ക്",
+ "sharedKmh": "കിലോമീറ്റർ / മണിക്കൂർ",
+ "sharedMph": "മണിക്കൂറിൽ മൈൽ",
+ "sharedHour": "മണിക്കൂര്",
+ "sharedMinute": "മിനിറ്റ്",
+ "sharedSecond": "സെക്കന്റ്",
+ "sharedName": "പേര്\n",
+ "sharedDescription": "വിവരണം",
+ "sharedSearch": "തിരയൽ",
+ "sharedGeofence": "എർത്ത് വേലി",
+ "sharedGeofences": "ശില്പ്പശാല എർത്ത്",
+ "sharedNotifications": "അറിയിപ്പുകൾ",
+ "sharedAttributes": "ഗുണവിശേഷങ്ങൾ",
+ "sharedAttribute": "ഗുണവിശേഷങ്ങ",
+ "sharedArea": "പ്രദേശം",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "പിശക്‌",
+ "errorUnknown": "അജ്ഞാത പിശക്",
+ "errorConnection": "കണക്ഷൻ പിശക്",
+ "userEmail": "ഇമെയിൽ",
+ "userPassword": "രഹസ്യ കോഡ്‌",
+ "userAdmin": "നിർവാജി ",
+ "userRemember": "Remember",
+ "loginTitle": "അകത്തു പ്രവേശിക്കുക",
+ "loginLanguage": "ഭാഷ",
+ "loginRegister": "രെജിസ്റ്റർ ",
+ "loginLogin": "അകത്തു പ്രവേശിക്കുക",
+ "loginFailed": "തെറ്റായ ഇമെയിൽ വിലാസവും പാസ്വേഡും",
+ "loginCreated": "പുതിയ ഉപയോക്താവ് രജിസ്റ്റർ ചെയ്തു",
+ "loginLogout": "പുറത്തുകടക്കുക",
+ "devicesAndState": "സാധനങ്ങളിന് നില ",
+ "deviceDialog": "ഉപകരണം",
+ "deviceTitle": "സാധനങ്ങളിന് ",
+ "deviceIdentifier": "ഐഡന്റിഫയർ",
+ "deviceLastUpdate": "Last Update",
+ "deviceCommand": "Command",
+ "deviceFollow": "Follow",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "Settings",
+ "settingsUser": "Account",
+ "settingsGroups": "Groups",
+ "settingsServer": "Server",
+ "settingsUsers": "Users",
+ "settingsSpeedUnit": "വേഗം",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Reports",
+ "reportDevice": "ഉപകരണം",
+ "reportGroup": "Group",
+ "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",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "State",
+ "stateName": "Attribute",
+ "stateValue": "Value",
+ "commandTitle": "Command",
+ "commandSend": "Send",
+ "commandSent": "Command has been sent",
+ "commandPositionPeriodic": "Periodic Reporting",
+ "commandPositionStop": "Stop Reporting",
+ "commandEngineStop": "Engine Stop",
+ "commandEngineResume": "Engine Resume",
+ "commandFrequency": "Frequency",
+ "commandUnit": "Unit",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/ms.json b/web/l10n/ms.json
new file mode 100644
index 0000000..4b65256
--- /dev/null
+++ b/web/l10n/ms.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Memuatkan...",
+ "sharedSave": "Simpan",
+ "sharedCancel": "Batal",
+ "sharedAdd": "Tambah",
+ "sharedEdit": "Ubah",
+ "sharedRemove": "Hapus",
+ "sharedRemoveConfirm": "Hapuskan item?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Jam",
+ "sharedMinute": "Minit",
+ "sharedSecond": "Saat",
+ "sharedName": "Name",
+ "sharedDescription": "Description",
+ "sharedSearch": "Search",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Ralat",
+ "errorUnknown": "Ralat tidak diketahui",
+ "errorConnection": "Ralat penyambungan",
+ "userEmail": "Emel",
+ "userPassword": "Katalaluan",
+ "userAdmin": "Admin",
+ "userRemember": "Remember",
+ "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",
+ "deviceIdentifier": "IMEI/ID",
+ "deviceLastUpdate": "Kemaskini Terakhir",
+ "deviceCommand": "Arahan",
+ "deviceFollow": "Ikut",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "Tetapan",
+ "settingsUser": "Akaun",
+ "settingsGroups": "Groups",
+ "settingsServer": "Server",
+ "settingsUsers": "Pengguna",
+ "settingsSpeedUnit": "Kelajuan",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Laporan",
+ "reportDevice": "Peranti",
+ "reportGroup": "Group",
+ "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",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "Negeri",
+ "stateName": "Atribut",
+ "stateValue": "Nilai",
+ "commandTitle": "Arahan",
+ "commandSend": "Hantar",
+ "commandSent": "Arahan telah dihantar",
+ "commandPositionPeriodic": "Laporan Berkala",
+ "commandPositionStop": "Hentikan Laporan",
+ "commandEngineStop": "Matikan Enjin",
+ "commandEngineResume": "Hidupkan Enjin",
+ "commandFrequency": "Frekuensi",
+ "commandUnit": "Unit",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/nb.json b/web/l10n/nb.json
new file mode 100644
index 0000000..57b3cf2
--- /dev/null
+++ b/web/l10n/nb.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Laster...",
+ "sharedSave": "Lagre",
+ "sharedCancel": "Avbryt",
+ "sharedAdd": "Legg til",
+ "sharedEdit": "Endre",
+ "sharedRemove": "Fjern",
+ "sharedRemoveConfirm": "Fjern element?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/t",
+ "sharedMph": "mph",
+ "sharedHour": "Time",
+ "sharedMinute": "Minutt",
+ "sharedSecond": "Sekund",
+ "sharedName": "Navn",
+ "sharedDescription": "Beskrivelse",
+ "sharedSearch": "Søk",
+ "sharedGeofence": "geo-gjerde",
+ "sharedGeofences": "Geo-gjerder",
+ "sharedNotifications": "Varsel",
+ "sharedAttributes": "Egenskaper",
+ "sharedAttribute": "Egenskap",
+ "sharedArea": "Område",
+ "sharedMute": "Demp",
+ "sharedType": "Type",
+ "sharedDistance": "Avstand",
+ "sharedHourAbbreviation": "t",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Få karttilstand",
+ "errorTitle": "Feil",
+ "errorUnknown": "Ukjent feil",
+ "errorConnection": "Forbindelse feilet",
+ "userEmail": "E-post",
+ "userPassword": "Passord",
+ "userAdmin": "Admin",
+ "userRemember": "Husk",
+ "loginTitle": "Logg inn",
+ "loginLanguage": "Språk",
+ "loginRegister": "Registrer",
+ "loginLogin": "Logg inn",
+ "loginFailed": "Feil e-post eller passord",
+ "loginCreated": "Ny bruker har blitt registrert",
+ "loginLogout": "Logg ut",
+ "devicesAndState": "Enheter og status",
+ "deviceDialog": "Enhet",
+ "deviceTitle": "Enheter",
+ "deviceIdentifier": "Identifikator",
+ "deviceLastUpdate": "Sist oppdatert",
+ "deviceCommand": "Kommando",
+ "deviceFollow": "Følg",
+ "groupDialog": "Gruppe",
+ "groupParent": "Gruppe",
+ "groupNoGroup": "Ingen gruppe",
+ "settingsTitle": "Innstillinger",
+ "settingsUser": "Konto",
+ "settingsGroups": "Grupper",
+ "settingsServer": "Server",
+ "settingsUsers": "Brukere",
+ "settingsSpeedUnit": "Hastighet",
+ "settingsTwelveHourFormat": "Tolvtimersformat",
+ "reportTitle": "Rapporter",
+ "reportDevice": "Enhet",
+ "reportGroup": "Gruppe",
+ "reportFrom": "Fra",
+ "reportTo": "Til",
+ "reportShow": "Vis",
+ "reportClear": "Nullstill",
+ "positionFixTime": "Tid",
+ "positionValid": "Gyldig",
+ "positionLatitude": "Breddegrad",
+ "positionLongitude": "Lengdegrad",
+ "positionAltitude": "Høyde",
+ "positionSpeed": "Hastighet",
+ "positionCourse": "Retning",
+ "positionAddress": "Adresse",
+ "positionProtocol": "Protokoll",
+ "serverTitle": "Serverinnstillinger",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registering",
+ "serverReadonly": "Skrivebeskyttet",
+ "mapTitle": "Kart",
+ "mapLayer": "Kartlag",
+ "mapCustom": "Egendefinert kart",
+ "mapOsm": "Open Street-kart",
+ "mapBingKey": "Bing Maps-nøkkel",
+ "mapBingRoad": "Bing Maps-veg",
+ "mapBingAerial": "Bing Maps-flyfoto",
+ "mapShapePolygon": "Mangekant",
+ "mapShapeCircle": "Sirkel",
+ "stateTitle": "Status",
+ "stateName": "Egenskap",
+ "stateValue": "Verdi",
+ "commandTitle": "Kommando",
+ "commandSend": "Send",
+ "commandSent": "Kommando har blitt sendt",
+ "commandPositionPeriodic": "Periodisk rapportering",
+ "commandPositionStop": "Stopp rapportering",
+ "commandEngineStop": "Stopp motor",
+ "commandEngineResume": "Fortsett motor",
+ "commandFrequency": "Frekvens",
+ "commandUnit": "Enhet",
+ "commandCustom": "Egendefinert kommando",
+ "commandPositionSingle": "Enkel-rapportering",
+ "commandAlarmArm": "Slå alarm på",
+ "commandAlarmDisarm": "Slå alarm av",
+ "commandSetTimezone": "Sett tidssone",
+ "commandRequestPhoto": "Be om foto",
+ "commandRebootDevice": "Omstart enhet",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Sett SOS-nummer",
+ "commandSilenceTime": "Sett stilletid",
+ "commandSetPhonebook": "Sett telefonbok",
+ "commandVoiceMessage": "Talemelding",
+ "commandOutputControl": "Utgangkontroll",
+ "commandAlarmSpeed": "Fartsgrensealarm",
+ "commandDeviceIdentification": "Enhetsidentifikasjon",
+ "commandIndex": "Register",
+ "commandData": "Data",
+ "commandPhone": "Telefonnummer",
+ "commandMessage": "Melding",
+ "eventAll": "Alle hendelser",
+ "eventDeviceOnline": "Enhet er tilkoblet",
+ "eventDeviceOffline": "Enhet er frakoblet",
+ "eventDeviceMoving": "Enheten beveger seg",
+ "eventDeviceStopped": "Enheten har stoppet",
+ "eventDeviceOverspeed": "Enheten bryter fartsgrensen",
+ "eventCommandResult": "Kommandoresultat",
+ "eventGeofenceEnter": "Enheten har kommet inn i geo-gjerde",
+ "eventGeofenceExit": "Enheten har forlatt geo-gjerde",
+ "eventAlarm": "Alarmer",
+ "eventIgnitionOn": "Tenning er PÅ",
+ "eventIgnitionOff": "Tenning er AV",
+ "alarm": "Alarm",
+ "alarmSos": "SOS-alarm",
+ "alarmVibration": "Vibrasjonsalarm",
+ "alarmMovement": "Bevegelsesalarm",
+ "alarmOverspeed": "Fartsgrensealarm",
+ "alarmFallDown": "Fallalarm",
+ "alarmLowBattery": "Lavt-batteri-alarm",
+ "alarmFault": "Feilalarm",
+ "notificationType": "Varseltype",
+ "notificationWeb": "Send via web",
+ "notificationMail": "Send via e-post",
+ "reportRoute": "Rute",
+ "reportEvents": "Hendelser",
+ "reportTrips": "Turer",
+ "reportSummary": "Oppsumering",
+ "reportConfigure": "Sett opp",
+ "reportEventTypes": "Hendelsestyper",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Enhetsnavn",
+ "reportAverageSpeed": "Gjennomsnittshastighet ",
+ "reportMaximumSpeed": "Maksimumshastighet",
+ "reportEngineHours": "Motortimer",
+ "reportDuration": "Varighet",
+ "reportStartTime": "Starttidspunkt",
+ "reportStartAddress": "Startadresse",
+ "reportEndTime": "Sluttidspunkt",
+ "reportEndAddress": "Sluttadresse",
+ "reportSpentFuel": "Brukt drivstoff"
+} \ No newline at end of file
diff --git a/web/l10n/ne.json b/web/l10n/ne.json
new file mode 100644
index 0000000..895db57
--- /dev/null
+++ b/web/l10n/ne.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "लोड हुँदै ",
+ "sharedSave": "सुरक्षित गर्ने ",
+ "sharedCancel": "रद्ध गर्ने ",
+ "sharedAdd": "थप्ने",
+ "sharedEdit": "सच्याउने",
+ "sharedRemove": "हटाउने ",
+ "sharedRemoveConfirm": "हटाउने हो?",
+ "sharedKm": "कि मि ",
+ "sharedMi": "माइल",
+ "sharedKn": "kn",
+ "sharedKmh": "कि मि /घण्टा ",
+ "sharedMph": "माइल /घण्टा ",
+ "sharedHour": "घण्टा ",
+ "sharedMinute": "मिनेट ",
+ "sharedSecond": "सेकेन्ड ",
+ "sharedName": "Name",
+ "sharedDescription": "Description",
+ "sharedSearch": "Search",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "त्रुटी",
+ "errorUnknown": "अज्ञात त्रुटी ",
+ "errorConnection": "जडान मा त्रुटी भयो ",
+ "userEmail": "इ मेल ",
+ "userPassword": "गोप्य शब्द ",
+ "userAdmin": "ब्यबस्थापक",
+ "userRemember": "Remember",
+ "loginTitle": "लगिन गर्ने ",
+ "loginLanguage": "भाषा ",
+ "loginRegister": "दर्ता गर्ने",
+ "loginLogin": "भित्रिने ",
+ "loginFailed": "इ मेल वा गोप्य शब्द गलत भयो ",
+ "loginCreated": "नया प्रयोगकर्ता दर्ता भयो ",
+ "loginLogout": "बाहिरिने ",
+ "devicesAndState": "यन्त्रहरू तथा अवस्था ",
+ "deviceDialog": "यन्त्र",
+ "deviceTitle": "यन्त्रहरू ",
+ "deviceIdentifier": "परिचायक ",
+ "deviceLastUpdate": "अन्तिम अपडेट ",
+ "deviceCommand": "आदेश ",
+ "deviceFollow": "पिछा गर्ने ",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "सेटिंग ",
+ "settingsUser": "खाता ",
+ "settingsGroups": "Groups",
+ "settingsServer": "सर्भर ",
+ "settingsUsers": "प्रयोगकर्ताहरु ",
+ "settingsSpeedUnit": "गति ",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "प्रतिबेदनहरु ",
+ "reportDevice": "यन्त्र ",
+ "reportGroup": "Group",
+ "reportFrom": "बाट ",
+ "reportTo": "सम्म ",
+ "reportShow": "देखाउने ",
+ "reportClear": "सफा गर्ने ",
+ "positionFixTime": "समय ",
+ "positionValid": "ठिक",
+ "positionLatitude": "अक्षांश",
+ "positionLongitude": "देशान्तर ",
+ "positionAltitude": "उचाई ",
+ "positionSpeed": "गति ",
+ "positionCourse": "दिशा ",
+ "positionAddress": "ठेगाना ",
+ "positionProtocol": "प्रोटोकल ",
+ "serverTitle": "सर्भर सेटिंग",
+ "serverZoom": "ठुलो बनाउने ",
+ "serverRegistration": "दर्ता ",
+ "serverReadonly": "पढ्ने मात्रै ",
+ "mapTitle": "नक्शा ",
+ "mapLayer": "नक्शा को तह ",
+ "mapCustom": "अनुकुल नक्शा ",
+ "mapOsm": "ओपन स्ट्रिट नक्शा ",
+ "mapBingKey": "बिंग नक्शाको चाबी (कि) ",
+ "mapBingRoad": "बिंग नक्शा (सडक)",
+ "mapBingAerial": "बिंग नक्शा (एरियल)",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "अवस्था ",
+ "stateName": "गुण ",
+ "stateValue": "मूल्य ",
+ "commandTitle": "आदेश ",
+ "commandSend": "पठाउने ",
+ "commandSent": "आदेश पठाईएको छ ",
+ "commandPositionPeriodic": "आवधिक प्रतिबेदन ",
+ "commandPositionStop": "प्रतिबेदन बन्द गर्ने ",
+ "commandEngineStop": "इन्जिन बन्द गर्ने ",
+ "commandEngineResume": "इन्जिन खोल्ने ",
+ "commandFrequency": "आव्रती ",
+ "commandUnit": "इकाई ",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/nl.json b/web/l10n/nl.json
new file mode 100644
index 0000000..0c1f25f
--- /dev/null
+++ b/web/l10n/nl.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Laden...",
+ "sharedSave": "Opslaan",
+ "sharedCancel": "Annuleren",
+ "sharedAdd": "Toevoegen",
+ "sharedEdit": "Bewerken",
+ "sharedRemove": "Verwijderen",
+ "sharedRemoveConfirm": "Item verwijderen?",
+ "sharedKm": "km",
+ "sharedMi": "mijl",
+ "sharedKn": "knopen",
+ "sharedKmh": "km/h",
+ "sharedMph": "mijl per uur",
+ "sharedHour": "Uur",
+ "sharedMinute": "Minuut",
+ "sharedSecond": "Seconde",
+ "sharedName": "Naam",
+ "sharedDescription": "Omschrijving",
+ "sharedSearch": "Zoeken",
+ "sharedGeofence": "Geografisch gebied",
+ "sharedGeofences": "Geografische gebieden",
+ "sharedNotifications": "Melding",
+ "sharedAttributes": "Attributen",
+ "sharedAttribute": "Attribuut",
+ "sharedArea": "Gebied",
+ "sharedMute": "Stil",
+ "sharedType": "Type",
+ "sharedDistance": "Afstand",
+ "sharedHourAbbreviation": "u",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Haal kaartstatus op",
+ "errorTitle": "Fout",
+ "errorUnknown": "Onbekende fout",
+ "errorConnection": "Verbindingsfout",
+ "userEmail": "E-mail",
+ "userPassword": "Wachtwoord",
+ "userAdmin": "Administrator",
+ "userRemember": "Onthouden",
+ "loginTitle": "Inloggen",
+ "loginLanguage": "Taal",
+ "loginRegister": "Registreren",
+ "loginLogin": "Inloggen",
+ "loginFailed": "Onjuist e-mailadres of wachtwoord",
+ "loginCreated": "De nieuwe gebruiker is geregistreerd",
+ "loginLogout": "Afmelden",
+ "devicesAndState": "Apparaten en status",
+ "deviceDialog": "Apparaat",
+ "deviceTitle": "Apparaten",
+ "deviceIdentifier": "Identifier",
+ "deviceLastUpdate": "Laatste update",
+ "deviceCommand": "Commando",
+ "deviceFollow": "Volgen",
+ "groupDialog": "Groep",
+ "groupParent": "Groep",
+ "groupNoGroup": "Geen groep",
+ "settingsTitle": "Instellingen",
+ "settingsUser": "Account",
+ "settingsGroups": "Groepen",
+ "settingsServer": "Server",
+ "settingsUsers": "Gebruikers",
+ "settingsSpeedUnit": "Snelheid",
+ "settingsTwelveHourFormat": "12-uurs indeling",
+ "reportTitle": "Rapportages",
+ "reportDevice": "Apparaat",
+ "reportGroup": "Groep",
+ "reportFrom": "Van",
+ "reportTo": "Naar",
+ "reportShow": "Laat zien",
+ "reportClear": "Leegmaken",
+ "positionFixTime": "Tijd",
+ "positionValid": "Geldig",
+ "positionLatitude": "Breedtegraad",
+ "positionLongitude": "Lengtegraad",
+ "positionAltitude": "Hoogte",
+ "positionSpeed": "Snelheid",
+ "positionCourse": "Koers",
+ "positionAddress": "Adres",
+ "positionProtocol": "Protocol",
+ "serverTitle": "Serverinstellingen",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registratie",
+ "serverReadonly": "Alleen lezen",
+ "mapTitle": "Kaart",
+ "mapLayer": "Kaart laag",
+ "mapCustom": "Aangepaste kaart",
+ "mapOsm": "OpenStreetMap",
+ "mapBingKey": "Bing Maps sleutel",
+ "mapBingRoad": "Bing Maps Wegen",
+ "mapBingAerial": "Bing Maps Luchtfoto",
+ "mapShapePolygon": "Polygoon",
+ "mapShapeCircle": "Cirkel",
+ "stateTitle": "Status",
+ "stateName": "Parameter",
+ "stateValue": "Waarde",
+ "commandTitle": "Commando",
+ "commandSend": "Verstuur",
+ "commandSent": "Commando verstuurd",
+ "commandPositionPeriodic": "Periodiek rapporteren",
+ "commandPositionStop": "Stop rapporteren",
+ "commandEngineStop": "Motor stoppen",
+ "commandEngineResume": "Motor hervatten",
+ "commandFrequency": "Frequentie",
+ "commandUnit": "Eenheid",
+ "commandCustom": "Aangepast commando",
+ "commandPositionSingle": "Enkel commando",
+ "commandAlarmArm": "Alarm aan",
+ "commandAlarmDisarm": "Alarm uit",
+ "commandSetTimezone": "Tijdzone instellen",
+ "commandRequestPhoto": "Vraag foto",
+ "commandRebootDevice": "Herstart apparaat",
+ "commandSendSms": "Stuur SMS",
+ "commandSendUssd": "Stuur USDD",
+ "commandSosNumber": "Stel SOS-nummer in",
+ "commandSilenceTime": "Stel 'Stille tijd' in",
+ "commandSetPhonebook": "Bewerk telefoonboek",
+ "commandVoiceMessage": "Spraakbericht",
+ "commandOutputControl": "Stel output in",
+ "commandAlarmSpeed": "Snelheidsalarm",
+ "commandDeviceIdentification": "Apparaatidentificatie",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Telefoonnummer",
+ "commandMessage": "Bericht",
+ "eventAll": "Alle gebeurtenissen",
+ "eventDeviceOnline": "Apparaat is online",
+ "eventDeviceOffline": "Apparaat is offline",
+ "eventDeviceMoving": "Apparaat beweegt",
+ "eventDeviceStopped": "Apparaat is gestopt",
+ "eventDeviceOverspeed": "Apparaat overschrijdt snelheid",
+ "eventCommandResult": "Commando resultaat",
+ "eventGeofenceEnter": "Appraat is binnen geografisch gebied",
+ "eventGeofenceExit": "Apparaat verlaat geografisch gebied",
+ "eventAlarm": "Alarmen",
+ "eventIgnitionOn": "Contact aan",
+ "eventIgnitionOff": "Contact uit",
+ "alarm": "Alarm",
+ "alarmSos": "SOS alarm",
+ "alarmVibration": "Vibratiealarm",
+ "alarmMovement": "Bewegingsalarm",
+ "alarmOverspeed": "Snelheidsalarm",
+ "alarmFallDown": "Valalarm",
+ "alarmLowBattery": "Lege batterij alarm",
+ "alarmFault": "Foutalarm",
+ "notificationType": "Notificatietype",
+ "notificationWeb": "Stuur via web",
+ "notificationMail": "Stuur via mail",
+ "reportRoute": "Route",
+ "reportEvents": "Gebeurtenissen",
+ "reportTrips": "Ritten",
+ "reportSummary": "Samenvatting",
+ "reportConfigure": "Configureer",
+ "reportEventTypes": "Gebeurtenistypen",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Apparaatnaam",
+ "reportAverageSpeed": "Gemiddelde snelheid",
+ "reportMaximumSpeed": "Maximale snelheid",
+ "reportEngineHours": "Draaiuren motor",
+ "reportDuration": "Duur",
+ "reportStartTime": "Starttijd",
+ "reportStartAddress": "Beginadres",
+ "reportEndTime": "Eindtijd",
+ "reportEndAddress": "Eindadres",
+ "reportSpentFuel": "Verbruikte brandstof"
+} \ No newline at end of file
diff --git a/web/l10n/nn.json b/web/l10n/nn.json
new file mode 100644
index 0000000..6b6d180
--- /dev/null
+++ b/web/l10n/nn.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Lastar...",
+ "sharedSave": "Lagre",
+ "sharedCancel": "Avbryt",
+ "sharedAdd": "Legg til",
+ "sharedEdit": "Endre",
+ "sharedRemove": "Fjern",
+ "sharedRemoveConfirm": "Fjern element?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/t",
+ "sharedMph": "mph",
+ "sharedHour": "Time",
+ "sharedMinute": "Minutt",
+ "sharedSecond": "Sekund",
+ "sharedName": "Namn",
+ "sharedDescription": "Beskriving",
+ "sharedSearch": "Søk",
+ "sharedGeofence": "Geo-gjerde",
+ "sharedGeofences": "Geo-gjerde",
+ "sharedNotifications": "Varsel",
+ "sharedAttributes": "Eigenskapar",
+ "sharedAttribute": "Eigenskap",
+ "sharedArea": "Område",
+ "sharedMute": "Demp",
+ "sharedType": "Type",
+ "sharedDistance": "Avstand",
+ "sharedHourAbbreviation": "t",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Få karttilstand",
+ "errorTitle": "Feil",
+ "errorUnknown": "Ukjent feil",
+ "errorConnection": "Forbindelse feila",
+ "userEmail": "E-post",
+ "userPassword": "Passord",
+ "userAdmin": "Admin",
+ "userRemember": "Hugs",
+ "loginTitle": "Logg inn",
+ "loginLanguage": "Språk",
+ "loginRegister": "Registrer",
+ "loginLogin": "Logg inn",
+ "loginFailed": "Feil e-post eller passord",
+ "loginCreated": "Ny brukar har blitt registrert",
+ "loginLogout": "Logg ut",
+ "devicesAndState": "Einingar og status",
+ "deviceDialog": "Eining",
+ "deviceTitle": "Einingar",
+ "deviceIdentifier": "Identifikator",
+ "deviceLastUpdate": "Sist oppdatert",
+ "deviceCommand": "Kommando",
+ "deviceFollow": "Følj",
+ "groupDialog": "Gruppe",
+ "groupParent": "Gruppe",
+ "groupNoGroup": "Inga gruppe",
+ "settingsTitle": "Innstillingar",
+ "settingsUser": "Konto",
+ "settingsGroups": "Gruppar",
+ "settingsServer": "Tenar",
+ "settingsUsers": "Brukarar",
+ "settingsSpeedUnit": "Hastigheit",
+ "settingsTwelveHourFormat": "Tolvtimersformat",
+ "reportTitle": "Rapportar",
+ "reportDevice": "Eining",
+ "reportGroup": "Gruppe",
+ "reportFrom": "Frå",
+ "reportTo": "Til",
+ "reportShow": "Syn",
+ "reportClear": "Nullstill",
+ "positionFixTime": "Tid",
+ "positionValid": "Gyldig",
+ "positionLatitude": "Breddegrad",
+ "positionLongitude": "Lengdegrad",
+ "positionAltitude": "Høgde",
+ "positionSpeed": "Hastigheit",
+ "positionCourse": "Retning",
+ "positionAddress": "Adresse",
+ "positionProtocol": "Protokoll",
+ "serverTitle": "Tenarinnstillingar",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registering",
+ "serverReadonly": "Skrivebeskytta",
+ "mapTitle": "Kart",
+ "mapLayer": "Kartlag",
+ "mapCustom": "Eigedefinert kart",
+ "mapOsm": "Open Street-kart",
+ "mapBingKey": "Bing Maps-nøkkel",
+ "mapBingRoad": "Bing Maps-veg",
+ "mapBingAerial": "Bing Maps-flyfoto",
+ "mapShapePolygon": "Mangekant",
+ "mapShapeCircle": "Sirkel",
+ "stateTitle": "Status",
+ "stateName": "Eigenskap",
+ "stateValue": "Verdi",
+ "commandTitle": "Kommando",
+ "commandSend": "Send",
+ "commandSent": "Kommando har blitt send",
+ "commandPositionPeriodic": "Periodisk rapportering",
+ "commandPositionStop": "Stopp rapportering",
+ "commandEngineStop": "Stopp motor",
+ "commandEngineResume": "Fortsett motor",
+ "commandFrequency": "Frekvens",
+ "commandUnit": "Eining",
+ "commandCustom": "Eigendefinert kommando",
+ "commandPositionSingle": "Enkel-rapportering",
+ "commandAlarmArm": "Slå alarm på",
+ "commandAlarmDisarm": "Slå alarm av",
+ "commandSetTimezone": "Sett opp tidssone",
+ "commandRequestPhoto": "Be om foto",
+ "commandRebootDevice": "Omstart eining",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SMS-nummer",
+ "commandSilenceTime": "Sett stilletid",
+ "commandSetPhonebook": "Sett telefonkatalog",
+ "commandVoiceMessage": "Talemelding",
+ "commandOutputControl": "Utgangkontroll",
+ "commandAlarmSpeed": "Fartsgrensealarm",
+ "commandDeviceIdentification": "Einingsidentifikasjon",
+ "commandIndex": "Register",
+ "commandData": "Data",
+ "commandPhone": "Telefonnummer",
+ "commandMessage": "Melding",
+ "eventAll": "Alle hendingar",
+ "eventDeviceOnline": "Eining er tilkopla",
+ "eventDeviceOffline": "Eininga er fråkopla",
+ "eventDeviceMoving": "Eininga rører seg",
+ "eventDeviceStopped": "Eininga er stoppa",
+ "eventDeviceOverspeed": "Eininga bryt fartsgrensa",
+ "eventCommandResult": "Kommandoresultat",
+ "eventGeofenceEnter": "Eininga har komme inn i geo-gjerde",
+ "eventGeofenceExit": "Eininga har forlatt geo-gjerde",
+ "eventAlarm": "Alarmar",
+ "eventIgnitionOn": "Tenninga er PÅ",
+ "eventIgnitionOff": "Tenninga er AV",
+ "alarm": "Alarm",
+ "alarmSos": "SOS-alarm",
+ "alarmVibration": "Vibrasjonsalarm",
+ "alarmMovement": "Rørslealarm",
+ "alarmOverspeed": "Fartsgrensealarm",
+ "alarmFallDown": "Fallalarm",
+ "alarmLowBattery": "Lavt-batteri-alarm",
+ "alarmFault": "Feilalarm",
+ "notificationType": "Varseltype",
+ "notificationWeb": "Send via web",
+ "notificationMail": "Send via e-post",
+ "reportRoute": "Rute",
+ "reportEvents": "Hendingar",
+ "reportTrips": "Turar",
+ "reportSummary": "Oppsumering",
+ "reportConfigure": "Set opp",
+ "reportEventTypes": "Hendingstypar",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Einingsnamn",
+ "reportAverageSpeed": "Gjennomsnittshastighet",
+ "reportMaximumSpeed": "Maksimumshastighet",
+ "reportEngineHours": "Motortimar",
+ "reportDuration": "Varigheit",
+ "reportStartTime": "Starttidspunkt",
+ "reportStartAddress": "Startadresse",
+ "reportEndTime": "Sluttidspunkt",
+ "reportEndAddress": "Sluttadresse",
+ "reportSpentFuel": "Brukt drivstoff"
+} \ No newline at end of file
diff --git a/web/l10n/pl.json b/web/l10n/pl.json
new file mode 100644
index 0000000..1bb48ae
--- /dev/null
+++ b/web/l10n/pl.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Wczytywanie...",
+ "sharedSave": "Zapisz",
+ "sharedCancel": "Anuluj",
+ "sharedAdd": "Dodaj",
+ "sharedEdit": "Edytuj",
+ "sharedRemove": "Usuń",
+ "sharedRemoveConfirm": "Usunąć obiekt?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Godzina",
+ "sharedMinute": "Minuta",
+ "sharedSecond": "Sekunda",
+ "sharedName": "Nazwa",
+ "sharedDescription": "Opis",
+ "sharedSearch": "Szukaj",
+ "sharedGeofence": "Nadzór",
+ "sharedGeofences": "Nadzory",
+ "sharedNotifications": "Powiadomienia",
+ "sharedAttributes": "Atrybuty",
+ "sharedAttribute": "Atrybut",
+ "sharedArea": "Strefa",
+ "sharedMute": "Wycisz",
+ "sharedType": "Typ",
+ "sharedDistance": "Odległość",
+ "sharedHourAbbreviation": "g",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Pobierz stan mapy",
+ "errorTitle": "Bląd",
+ "errorUnknown": "Nieznany błąd",
+ "errorConnection": "Błąd przy połączeniu",
+ "userEmail": "Email",
+ "userPassword": "Hasło",
+ "userAdmin": "Administrator",
+ "userRemember": "Zapamiętaj",
+ "loginTitle": "Login",
+ "loginLanguage": "Język",
+ "loginRegister": "Rejestracja",
+ "loginLogin": "Zaloguj",
+ "loginFailed": "Nieprawidłowy adres e-mail lub hasło",
+ "loginCreated": "Nowy użytkownik został zarejestrowany",
+ "loginLogout": "Wyloguj",
+ "devicesAndState": "Urządzenia i stan",
+ "deviceDialog": "Urządzenie",
+ "deviceTitle": "Urządzenia",
+ "deviceIdentifier": "Identyfikator",
+ "deviceLastUpdate": "Ostatnia aktualizacja",
+ "deviceCommand": "Polecenie",
+ "deviceFollow": "Podążaj",
+ "groupDialog": "Grupa",
+ "groupParent": "Grupa",
+ "groupNoGroup": "Brak grupy",
+ "settingsTitle": "Ustawienia",
+ "settingsUser": "Konto",
+ "settingsGroups": "Grupy",
+ "settingsServer": "Serwer",
+ "settingsUsers": "Użytkownicy",
+ "settingsSpeedUnit": "Prędkość",
+ "settingsTwelveHourFormat": "Format 12-godz.",
+ "reportTitle": "Raporty",
+ "reportDevice": "Urządzenie",
+ "reportGroup": "Grupa",
+ "reportFrom": "Z",
+ "reportTo": "Do",
+ "reportShow": "Pokaż",
+ "reportClear": "Wyczyść",
+ "positionFixTime": "Czas",
+ "positionValid": "Aktywny",
+ "positionLatitude": "Szerokość",
+ "positionLongitude": "Długość",
+ "positionAltitude": "Wysokość",
+ "positionSpeed": "Prędkość",
+ "positionCourse": "Kurs",
+ "positionAddress": "Adres",
+ "positionProtocol": "Protokół",
+ "serverTitle": "Ustawienia serwera",
+ "serverZoom": "Powiększenie",
+ "serverRegistration": "Rejestracja",
+ "serverReadonly": "Tylko do odczytu",
+ "mapTitle": "Mapa",
+ "mapLayer": "Warstwa mapy",
+ "mapCustom": "Własna mapa",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Wielokąt",
+ "mapShapeCircle": "Okrąg",
+ "stateTitle": "Stan i lokalizacja",
+ "stateName": "Właściwość",
+ "stateValue": "Wartość",
+ "commandTitle": "Komenda",
+ "commandSend": "Wyślij",
+ "commandSent": "Komenda została wysłana",
+ "commandPositionPeriodic": "Okresowy raport",
+ "commandPositionStop": "Zatrzymaj raportowanie",
+ "commandEngineStop": "Silnik - Stop",
+ "commandEngineResume": "Silnik - Wznów",
+ "commandFrequency": "Częstotliwość",
+ "commandUnit": "Jednostka",
+ "commandCustom": "Własna komenda",
+ "commandPositionSingle": "Pojedyncze raportowanie",
+ "commandAlarmArm": "Włączanie alarmu",
+ "commandAlarmDisarm": "Wyłączanie alarmu",
+ "commandSetTimezone": "Ustaw strefę czasową",
+ "commandRequestPhoto": "Żądanie zdjęcia\n",
+ "commandRebootDevice": "Zresetuj urządzenie",
+ "commandSendSms": "Wyślij SMS",
+ "commandSendUssd": "Wyślij USSD",
+ "commandSosNumber": "Ustaw numer SOS",
+ "commandSilenceTime": "Ustaw czas milczenia",
+ "commandSetPhonebook": "Ustaw książkę adresową",
+ "commandVoiceMessage": "Wiadomość głosowa",
+ "commandOutputControl": "Kontrola wyjścia",
+ "commandAlarmSpeed": "Alarm przekrocznie prędkości",
+ "commandDeviceIdentification": "Identyfikacja urządzenia",
+ "commandIndex": "Index",
+ "commandData": "Dane",
+ "commandPhone": "Numer telefonu",
+ "commandMessage": "Wiadomość",
+ "eventAll": "Wszystkie zdarzenia",
+ "eventDeviceOnline": "Urządzenie jest online",
+ "eventDeviceOffline": "Urządzenie jest offline",
+ "eventDeviceMoving": "Urządzenie przemieszcza się",
+ "eventDeviceStopped": "Urządzenia zatrzymane",
+ "eventDeviceOverspeed": "Urządzenie przekroczyło prędkość",
+ "eventCommandResult": "Rezultat polecenia",
+ "eventGeofenceEnter": "Urządzenie wkroczyło w strefę nadzoru",
+ "eventGeofenceExit": "Urządzenie wykroczyło po za strefę nadzoru",
+ "eventAlarm": "Alarmy",
+ "eventIgnitionOn": "Zapłon włączony",
+ "eventIgnitionOff": "Zapłon wyłączony",
+ "alarm": "Alarm",
+ "alarmSos": "Alarm SOS",
+ "alarmVibration": "Alarm wibracyjny",
+ "alarmMovement": "Alarm ruchu",
+ "alarmOverspeed": "Alarm przekroczenia prędkości",
+ "alarmFallDown": "Alarm upadku",
+ "alarmLowBattery": "Alarm niskiego stanu baterii",
+ "alarmFault": "Alarm usterki",
+ "notificationType": "Rodzaj powiadomienia",
+ "notificationWeb": "Wyślij przez sieć",
+ "notificationMail": "Wyślij przez Email",
+ "reportRoute": "Trasa",
+ "reportEvents": "Zdarzenia",
+ "reportTrips": "Trips",
+ "reportSummary": "Podsumowanie",
+ "reportConfigure": "Konfiguracja",
+ "reportEventTypes": "Rodzaje zdarzeń",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Nazwa urządzenia",
+ "reportAverageSpeed": "Średnia prędkość",
+ "reportMaximumSpeed": "Maksymalna prędkość",
+ "reportEngineHours": "Czas pracy silnika",
+ "reportDuration": "Czas trwania",
+ "reportStartTime": "Czas uruchomienia",
+ "reportStartAddress": "Adres początkowy",
+ "reportEndTime": "Czas końowy",
+ "reportEndAddress": "Adres końcowy",
+ "reportSpentFuel": "Zużyte paliwo"
+} \ No newline at end of file
diff --git a/web/l10n/pt.json b/web/l10n/pt.json
new file mode 100644
index 0000000..fe7c6ac
--- /dev/null
+++ b/web/l10n/pt.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Carregando...",
+ "sharedSave": "Salvar",
+ "sharedCancel": "Cancelar",
+ "sharedAdd": "Adicionar",
+ "sharedEdit": "Editar",
+ "sharedRemove": "Remover",
+ "sharedRemoveConfirm": "Remover item?",
+ "sharedKm": "Km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "Km/h",
+ "sharedMph": "Mph",
+ "sharedHour": "Hora",
+ "sharedMinute": "Minuto",
+ "sharedSecond": "Segundo",
+ "sharedName": "Name",
+ "sharedDescription": "Description",
+ "sharedSearch": "Search",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Erro",
+ "errorUnknown": "Erro desconhecido",
+ "errorConnection": "Erro de conexão",
+ "userEmail": "E-mail",
+ "userPassword": "Senha",
+ "userAdmin": "Admin",
+ "userRemember": "Remember",
+ "loginTitle": "Entrar",
+ "loginLanguage": "Idioma",
+ "loginRegister": "Registrar",
+ "loginLogin": "Entrar",
+ "loginFailed": "Endereço de e-mail ou senha incorreta",
+ "loginCreated": "Novo usuário foi registrado",
+ "loginLogout": "Sair",
+ "devicesAndState": "Devices and State",
+ "deviceDialog": "Dispositivo",
+ "deviceTitle": "Devices",
+ "deviceIdentifier": "Identificador",
+ "deviceLastUpdate": "Last Update",
+ "deviceCommand": "Comando",
+ "deviceFollow": "Follow",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "Configurações",
+ "settingsUser": "Conta",
+ "settingsGroups": "Groups",
+ "settingsServer": "Servidor",
+ "settingsUsers": "Usuário",
+ "settingsSpeedUnit": "Velocidade",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Relatórios",
+ "reportDevice": "Dispositivo",
+ "reportGroup": "Group",
+ "reportFrom": "De",
+ "reportTo": "Para",
+ "reportShow": "Mostrar",
+ "reportClear": "Limpar",
+ "positionFixTime": "Tempo",
+ "positionValid": "Válido",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "Velocidade",
+ "positionCourse": "Curso",
+ "positionAddress": "Endereço",
+ "positionProtocol": "protocolo",
+ "serverTitle": "Configurações do Servidor",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registro",
+ "serverReadonly": "Readonly",
+ "mapTitle": "Mapa",
+ "mapLayer": "Camada Mapa",
+ "mapCustom": "Mapa personalizado",
+ "mapOsm": "Open Street Mapa",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Mapas Estrada",
+ "mapBingAerial": "Bing Mapas Aérea",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "Estado",
+ "stateName": "Parâmetro",
+ "stateValue": "Valor",
+ "commandTitle": "Comando",
+ "commandSend": "Enviar",
+ "commandSent": "Comando foi enviado",
+ "commandPositionPeriodic": "Posição Tempo",
+ "commandPositionStop": "Parar Posição",
+ "commandEngineStop": "Bloqueio Veículo",
+ "commandEngineResume": "Desbloqueio Veículo",
+ "commandFrequency": "Frequência",
+ "commandUnit": "Unidade",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/pt_BR.json b/web/l10n/pt_BR.json
new file mode 100644
index 0000000..0731fdd
--- /dev/null
+++ b/web/l10n/pt_BR.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Carregando...",
+ "sharedSave": "Gravar",
+ "sharedCancel": "Cancelar",
+ "sharedAdd": "Adicionar",
+ "sharedEdit": "Editar",
+ "sharedRemove": "Remover",
+ "sharedRemoveConfirm": "Remover item?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Hora",
+ "sharedMinute": "Minuto",
+ "sharedSecond": "Segundo",
+ "sharedName": "Nome",
+ "sharedDescription": "Descrição",
+ "sharedSearch": "Busca",
+ "sharedGeofence": "Geocerca",
+ "sharedGeofences": "Geocercas",
+ "sharedNotifications": "Notificações",
+ "sharedAttributes": "Atributos",
+ "sharedAttribute": "Atributo",
+ "sharedArea": "Área",
+ "sharedMute": "Mudo",
+ "sharedType": "Tipo",
+ "sharedDistance": "Distância",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Erro",
+ "errorUnknown": "Erro desconhecido",
+ "errorConnection": "Erro de conexão",
+ "userEmail": "Email",
+ "userPassword": "Senha",
+ "userAdmin": "Admin",
+ "userRemember": "Lembrar",
+ "loginTitle": "Entrar",
+ "loginLanguage": "Idioma",
+ "loginRegister": "Registrar",
+ "loginLogin": "Entrar",
+ "loginFailed": "Endereço de email ou senha incorretos",
+ "loginCreated": "O novo usuário foi registrado",
+ "loginLogout": "Sair",
+ "devicesAndState": "Dispositivo e Estado",
+ "deviceDialog": "Dispositivo",
+ "deviceTitle": "Dispositivos",
+ "deviceIdentifier": "Identificador",
+ "deviceLastUpdate": "Última Atualização",
+ "deviceCommand": "Comando",
+ "deviceFollow": "Seguir",
+ "groupDialog": "Grupo",
+ "groupParent": "Grupo",
+ "groupNoGroup": "Sem Grupo",
+ "settingsTitle": "Configurações",
+ "settingsUser": "Conta",
+ "settingsGroups": "Grupos",
+ "settingsServer": "Servidor",
+ "settingsUsers": "Usuários",
+ "settingsSpeedUnit": "Velocidade",
+ "settingsTwelveHourFormat": "Formato de 12 Horas",
+ "reportTitle": "Relatórios",
+ "reportDevice": "Dispositivo",
+ "reportGroup": "Group",
+ "reportFrom": "De",
+ "reportTo": "Para",
+ "reportShow": "Mostrar",
+ "reportClear": "Limpar",
+ "positionFixTime": "Tempo",
+ "positionValid": "Válido",
+ "positionLatitude": "Latitude",
+ "positionLongitude": "Longitude",
+ "positionAltitude": "Altitude",
+ "positionSpeed": "Velocidade",
+ "positionCourse": "Curso",
+ "positionAddress": "Endereço",
+ "positionProtocol": "Protocolo",
+ "serverTitle": "Configurações do Servidor",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registro",
+ "serverReadonly": "Somente leitura",
+ "mapTitle": "Mapa",
+ "mapLayer": "Camada de Mapa",
+ "mapCustom": "Mapa Personalizado",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Estradas",
+ "mapBingAerial": "Bing Maps Aéreo",
+ "mapShapePolygon": "Polígono",
+ "mapShapeCircle": "Círculo",
+ "stateTitle": "Estado",
+ "stateName": "Atributo",
+ "stateValue": "Valor",
+ "commandTitle": "Comando",
+ "commandSend": "Enviar",
+ "commandSent": "Comando foi enviado",
+ "commandPositionPeriodic": "Atualização Periódica",
+ "commandPositionStop": "Parar Atualizaçao",
+ "commandEngineStop": "Desligar Motor",
+ "commandEngineResume": "Religar Motor",
+ "commandFrequency": "Frequencia",
+ "commandUnit": "Unidade",
+ "commandCustom": "Comando personalizado",
+ "commandPositionSingle": "Relatório único",
+ "commandAlarmArm": "Ativar Alarme",
+ "commandAlarmDisarm": "Desativar Alarme",
+ "commandSetTimezone": "Definir fuso horário",
+ "commandRequestPhoto": "Pegar foto",
+ "commandRebootDevice": "Reiniciar dispositivo",
+ "commandSendSms": "Enviar SMS",
+ "commandSendUssd": "Enviar USSD",
+ "commandSosNumber": "Definir numero SOS",
+ "commandSilenceTime": "Silencioso",
+ "commandSetPhonebook": "Definir lista telefônica",
+ "commandVoiceMessage": "Mensagem de voz",
+ "commandOutputControl": "Controle de saída",
+ "commandAlarmSpeed": "Alarme de excesso de velocidade",
+ "commandDeviceIdentification": "Identificação do dispositivo",
+ "commandIndex": "Indice",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Mensagem",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Dispositivo está on-line",
+ "eventDeviceOffline": "Dispositivo está offline",
+ "eventDeviceMoving": "Dispositivo está se movendo",
+ "eventDeviceStopped": "Dispositivo está parado",
+ "eventDeviceOverspeed": "Dispositivo excede a velocidade",
+ "eventCommandResult": "Resultado do comando",
+ "eventGeofenceEnter": "Dispositivo entrou geocerca",
+ "eventGeofenceExit": "Dispositivo saiu geocerca",
+ "eventAlarm": "Alarmes",
+ "eventIgnitionOn": "Ignição está ON",
+ "eventIgnitionOff": "Ignição está OFF",
+ "alarm": "Alarme",
+ "alarmSos": "Alarme SOS",
+ "alarmVibration": "Alarme de Vibração",
+ "alarmMovement": "Alarme de Movimento",
+ "alarmOverspeed": "Alarme de Alta Velocidade",
+ "alarmFallDown": "Alarme de Queda",
+ "alarmLowBattery": "Alarme de Bateria Fraca",
+ "alarmFault": "Alarme de Problema",
+ "notificationType": "Tipo de Notificação",
+ "notificationWeb": "Enviar via Web",
+ "notificationMail": "Enviar via Email",
+ "reportRoute": "Rota",
+ "reportEvents": "Eventos",
+ "reportTrips": "Viagens",
+ "reportSummary": "Resumo",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Nome do Dispositivo ",
+ "reportAverageSpeed": "Velocidade Média",
+ "reportMaximumSpeed": "Velocidade Máxima",
+ "reportEngineHours": "Horas ligado",
+ "reportDuration": "Duração",
+ "reportStartTime": "Hora inicial",
+ "reportStartAddress": "Endereço inicial",
+ "reportEndTime": "Hora final",
+ "reportEndAddress": "Endereço final",
+ "reportSpentFuel": "Gasto de Combustível"
+} \ No newline at end of file
diff --git a/web/l10n/ro.json b/web/l10n/ro.json
new file mode 100644
index 0000000..79f3c6a
--- /dev/null
+++ b/web/l10n/ro.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Se încarcă",
+ "sharedSave": "Salvează",
+ "sharedCancel": "Anulează",
+ "sharedAdd": "Adaugă",
+ "sharedEdit": "Modifică",
+ "sharedRemove": "Elimină",
+ "sharedRemoveConfirm": "Ștergeți obiectul?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Oră",
+ "sharedMinute": "Minut",
+ "sharedSecond": "Secundă",
+ "sharedName": "Nume",
+ "sharedDescription": "Descriere",
+ "sharedSearch": "Căutare",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notificările",
+ "sharedAttributes": "Atribute",
+ "sharedAttribute": "Atribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mut",
+ "sharedType": "Tip",
+ "sharedDistance": "Distanţă",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Eroare",
+ "errorUnknown": "Eroare necunoscută",
+ "errorConnection": "Eroare de conexiune",
+ "userEmail": "Email",
+ "userPassword": "Parolă",
+ "userAdmin": "Admin",
+ "userRemember": "Ţine minte",
+ "loginTitle": "Autentificare",
+ "loginLanguage": "Limbă",
+ "loginRegister": "Înregistrare",
+ "loginLogin": "Intră în cont",
+ "loginFailed": "E-mail sau parolă incorectă",
+ "loginCreated": "Un utilizator nou a fost înregistrat",
+ "loginLogout": "Deconectare",
+ "devicesAndState": "Stare dispozitive",
+ "deviceDialog": "Dispozitiv",
+ "deviceTitle": "Dispozitive",
+ "deviceIdentifier": "Identificator",
+ "deviceLastUpdate": "Ultima actualizare",
+ "deviceCommand": "Comandă",
+ "deviceFollow": "Urmareste",
+ "groupDialog": "Grup",
+ "groupParent": "Grup",
+ "groupNoGroup": "Nici-un grup",
+ "settingsTitle": "Setări",
+ "settingsUser": "Cont",
+ "settingsGroups": "Grupuri",
+ "settingsServer": "Server",
+ "settingsUsers": "Utilizatori",
+ "settingsSpeedUnit": "Viteză",
+ "settingsTwelveHourFormat": "12-oră",
+ "reportTitle": "Rapoarte",
+ "reportDevice": "Dispozitiv",
+ "reportGroup": "Group",
+ "reportFrom": "De la ",
+ "reportTo": "Până la",
+ "reportShow": "Arată",
+ "reportClear": "Sterge",
+ "positionFixTime": "Timp",
+ "positionValid": "Valabil",
+ "positionLatitude": "Latitudine",
+ "positionLongitude": "Longitudine",
+ "positionAltitude": "Altitudine",
+ "positionSpeed": "Viteză",
+ "positionCourse": "Curs",
+ "positionAddress": "Adresă",
+ "positionProtocol": "Protocol",
+ "serverTitle": "Setări server",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Înregistrare",
+ "serverReadonly": "Doar citire",
+ "mapTitle": "Hartă",
+ "mapLayer": "Strat Hartă",
+ "mapCustom": "Personalizare Hartă",
+ "mapOsm": "Hartă Open Street",
+ "mapBingKey": "Cheie Hărți Bing",
+ "mapBingRoad": "Bing Hartă Drumuri",
+ "mapBingAerial": "Bing Hartă Aeriană",
+ "mapShapePolygon": "Poligon",
+ "mapShapeCircle": "Cerc",
+ "stateTitle": "Stare",
+ "stateName": "Atribut",
+ "stateValue": "Valoare",
+ "commandTitle": "Comandă",
+ "commandSend": "Trimite",
+ "commandSent": "Comandă a fost trimisa",
+ "commandPositionPeriodic": "Raportarea Periodică",
+ "commandPositionStop": "Oprire Raportare",
+ "commandEngineStop": "Blocare Motor",
+ "commandEngineResume": "Deblocare Motor",
+ "commandFrequency": "Frecvenţă",
+ "commandUnit": "Unitate",
+ "commandCustom": "Comandă personalizată",
+ "commandPositionSingle": "Raportarea unică",
+ "commandAlarmArm": "Activare Alarmă",
+ "commandAlarmDisarm": "Dezactivare alarmă",
+ "commandSetTimezone": "Setare Fus Orar",
+ "commandRequestPhoto": "Cere Foto",
+ "commandRebootDevice": "Repornire Dispozitiv",
+ "commandSendSms": "Trimite SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set număr SOS",
+ "commandSilenceTime": "Set Timp Silențios",
+ "commandSetPhonebook": "Set Agendă telefonică",
+ "commandVoiceMessage": "Mesaj Vocal",
+ "commandOutputControl": "Controlul de ieșire",
+ "commandAlarmSpeed": "Alarmă depăşire viteză",
+ "commandDeviceIdentification": "Identificare dispozitiv",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Dispozitivul este pornit",
+ "eventDeviceOffline": "Dispozitivul este oprit",
+ "eventDeviceMoving": "Dispozitivul este în mişcare",
+ "eventDeviceStopped": "Dispozitivul este staţionar",
+ "eventDeviceOverspeed": "Dispozitivul a depăşit limita de viteză",
+ "eventCommandResult": "Rezultat comandă",
+ "eventGeofenceEnter": "Dispozitivul a intrat in zona geofence",
+ "eventGeofenceExit": "Dispozitivul a ieşit din zona geofence",
+ "eventAlarm": "Alarme",
+ "eventIgnitionOn": "Contact pornit",
+ "eventIgnitionOff": "Contact oprit",
+ "alarm": "Alarmă",
+ "alarmSos": "Alarmă SOS",
+ "alarmVibration": "Alarmă vibraţii",
+ "alarmMovement": "Alarmă Mişcare",
+ "alarmOverspeed": "Alarmă depăsire viteză",
+ "alarmFallDown": "Alarmă cădere",
+ "alarmLowBattery": "Alarmă nivel baterie scăzută",
+ "alarmFault": "Alarmă Defect",
+ "notificationType": "Tip de notificare",
+ "notificationWeb": "Trimite din Web",
+ "notificationMail": "Trimite din Mail",
+ "reportRoute": "Rute",
+ "reportEvents": "Evenimente",
+ "reportTrips": "Trips",
+ "reportSummary": "Sumar",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Nume dispozitiv",
+ "reportAverageSpeed": "Viteză medie",
+ "reportMaximumSpeed": "Viteză Maximă",
+ "reportEngineHours": "Ore motor",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/ru.json b/web/l10n/ru.json
new file mode 100644
index 0000000..9438ea8
--- /dev/null
+++ b/web/l10n/ru.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Загрузка...",
+ "sharedSave": "Сохранить",
+ "sharedCancel": "Отмена",
+ "sharedAdd": "Добавить",
+ "sharedEdit": "Редактировать",
+ "sharedRemove": "Удалить",
+ "sharedRemoveConfirm": "Удалить элемент?",
+ "sharedKm": "км",
+ "sharedMi": "мили",
+ "sharedKn": "уз",
+ "sharedKmh": "км/ч",
+ "sharedMph": "миль/ч",
+ "sharedHour": "Часы",
+ "sharedMinute": "Минуты",
+ "sharedSecond": "Секунды",
+ "sharedName": "Имя",
+ "sharedDescription": "Описание",
+ "sharedSearch": "Поиск",
+ "sharedGeofence": "Геозона",
+ "sharedGeofences": "Геозоны",
+ "sharedNotifications": "Уведомления",
+ "sharedAttributes": "Атрибуты",
+ "sharedAttribute": "Атрибут",
+ "sharedArea": "Область",
+ "sharedMute": "Выкл. звук",
+ "sharedType": "Тип",
+ "sharedDistance": "Расстояние",
+ "sharedHourAbbreviation": "ч",
+ "sharedMinuteAbbreviation": "м",
+ "sharedGetMapState": "Получить состояние карты",
+ "errorTitle": "Ошибка",
+ "errorUnknown": "Неизвестная ошибка",
+ "errorConnection": "Ошибка соединения",
+ "userEmail": "Email",
+ "userPassword": "Пароль",
+ "userAdmin": "Администратор",
+ "userRemember": "Запомнить",
+ "loginTitle": "Вход",
+ "loginLanguage": "Язык",
+ "loginRegister": "Регистрация",
+ "loginLogin": "Вход",
+ "loginFailed": "Неправильный email адрес или пароль",
+ "loginCreated": "Новый пользователь зарегистрирован",
+ "loginLogout": "Выход",
+ "devicesAndState": "Устройства и Состояния",
+ "deviceDialog": "Устройство",
+ "deviceTitle": "Устройства",
+ "deviceIdentifier": "Идентификатор",
+ "deviceLastUpdate": "Последнее Обновление",
+ "deviceCommand": "Команда",
+ "deviceFollow": "Следовать",
+ "groupDialog": "Группа",
+ "groupParent": "Группа",
+ "groupNoGroup": "Без Группы",
+ "settingsTitle": "Настройки",
+ "settingsUser": "Аккаунт",
+ "settingsGroups": "Группы",
+ "settingsServer": "Сервер",
+ "settingsUsers": "Пользователи",
+ "settingsSpeedUnit": "Скорость",
+ "settingsTwelveHourFormat": "12-часовой формат",
+ "reportTitle": "Отчеты",
+ "reportDevice": "Устройство",
+ "reportGroup": "Группа",
+ "reportFrom": "С",
+ "reportTo": "По",
+ "reportShow": "Показать",
+ "reportClear": "Очистить",
+ "positionFixTime": "Время",
+ "positionValid": "Корректность",
+ "positionLatitude": "Широта",
+ "positionLongitude": "Долгота",
+ "positionAltitude": "Высота",
+ "positionSpeed": "Скорость",
+ "positionCourse": "Направление",
+ "positionAddress": "Адрес",
+ "positionProtocol": "Протокол",
+ "serverTitle": "Настройки Сервера",
+ "serverZoom": "Приближение",
+ "serverRegistration": "Регистрация",
+ "serverReadonly": "Только Просмотр",
+ "mapTitle": "Карта",
+ "mapLayer": "Слой Карты",
+ "mapCustom": "Пользовательская карта",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Ключ Bing Maps",
+ "mapBingRoad": "Bing Maps Дороги",
+ "mapBingAerial": "Bing Maps Спутник",
+ "mapShapePolygon": "Многоугольник",
+ "mapShapeCircle": "Круг",
+ "stateTitle": "Состояние",
+ "stateName": "Параметр",
+ "stateValue": "Значение",
+ "commandTitle": "Команда",
+ "commandSend": "Отправить",
+ "commandSent": "Команда отправлена",
+ "commandPositionPeriodic": "Начать Отслеживание",
+ "commandPositionStop": "Отменить Отслеживание",
+ "commandEngineStop": "Заблокировать Двигатель",
+ "commandEngineResume": "Разблокировать Двигатель",
+ "commandFrequency": "Частота",
+ "commandUnit": "Единицы",
+ "commandCustom": "Пользовательская команда",
+ "commandPositionSingle": "Разовое Отслеживание",
+ "commandAlarmArm": "Активировать Сигнализацию",
+ "commandAlarmDisarm": "Деактивировать Сигнализацию",
+ "commandSetTimezone": "Настроить Часовой пояс",
+ "commandRequestPhoto": "Запросить Фото",
+ "commandRebootDevice": "Перезагрузить Устройство",
+ "commandSendSms": "Отправить СМС",
+ "commandSendUssd": "Отправить USSD",
+ "commandSosNumber": "Настроить Экстренный Номер",
+ "commandSilenceTime": "Настроить Время Тишины",
+ "commandSetPhonebook": "Настроить Телефонную книгу",
+ "commandVoiceMessage": "Голосовое Сообщение",
+ "commandOutputControl": "Контроль Выхода",
+ "commandAlarmSpeed": "Превышение Скорости",
+ "commandDeviceIdentification": "Идентификация Устройства",
+ "commandIndex": "Индекс",
+ "commandData": "Данные",
+ "commandPhone": "Номер Телефона",
+ "commandMessage": "Сообщение",
+ "eventAll": "Все События",
+ "eventDeviceOnline": "Устройство в сети",
+ "eventDeviceOffline": "Устройство не в сети",
+ "eventDeviceMoving": "Устройство движется",
+ "eventDeviceStopped": "Устройство остановилось",
+ "eventDeviceOverspeed": "Устройство превышает скорость",
+ "eventCommandResult": "Результат команды",
+ "eventGeofenceEnter": "Устройство вошло в геозону",
+ "eventGeofenceExit": "Устройство покинуло геозону",
+ "eventAlarm": "Тревоги",
+ "eventIgnitionOn": "Зажигание ВКЛ",
+ "eventIgnitionOff": "Зажигание ВЫКЛ",
+ "alarm": "Тревога",
+ "alarmSos": "Тревога SOS",
+ "alarmVibration": "Тревога Вибрации",
+ "alarmMovement": "Тревога Сигнализации",
+ "alarmOverspeed": "Тревога Превышения скорости",
+ "alarmFallDown": "Тревога Падения",
+ "alarmLowBattery": "Тревога Батарея разряжена",
+ "alarmFault": "Тревога Неисправность",
+ "notificationType": "Тип уведомления",
+ "notificationWeb": "Отправлять через Веб",
+ "notificationMail": "Отправлять через Почту",
+ "reportRoute": "Маршрут",
+ "reportEvents": "События",
+ "reportTrips": "Поездки",
+ "reportSummary": "Сводка",
+ "reportConfigure": "Конфигурировать",
+ "reportEventTypes": "Тип События",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Имя Устройства",
+ "reportAverageSpeed": "Средняя Скорость",
+ "reportMaximumSpeed": "Максимальная Скорость",
+ "reportEngineHours": "Моточасы",
+ "reportDuration": "Длительность",
+ "reportStartTime": "Начальное Время",
+ "reportStartAddress": "Начальный Адрес",
+ "reportEndTime": "Конечное Время",
+ "reportEndAddress": "Конечный Адрес",
+ "reportSpentFuel": "Использовано Топлива"
+} \ No newline at end of file
diff --git a/web/l10n/si.json b/web/l10n/si.json
new file mode 100644
index 0000000..4619486
--- /dev/null
+++ b/web/l10n/si.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "පූරණය කරමින් ...",
+ "sharedSave": "සුරකින්න",
+ "sharedCancel": "අවලංගු කරන්න",
+ "sharedAdd": "එක් කරන්න",
+ "sharedEdit": "සංස්කරණය කරන්න",
+ "sharedRemove": "ඉවත් කරන්න",
+ "sharedRemoveConfirm": "අයිතමය ඉවත් කරන්න ද?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "පැය",
+ "sharedMinute": "මිනිත්තු",
+ "sharedSecond": "තත්පර",
+ "sharedName": "නම",
+ "sharedDescription": "විස්තරය",
+ "sharedSearch": "සොයන්න",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "නිවේදන",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "ප්‍රදේශය",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "දෝෂයක් ",
+ "errorUnknown": "නොදන්නා දෝෂයක් !",
+ "errorConnection": "සම්බන්ධතා දෝෂයක් !",
+ "userEmail": "විද්යුත් තැපෑල",
+ "userPassword": "මුරපදය",
+ "userAdmin": "පරිපාලක",
+ "userRemember": "Remember",
+ "loginTitle": "පිවිසුම් ගිණුම",
+ "loginLanguage": "භාෂාව",
+ "loginRegister": "ලියාපදිංචි කරන්න",
+ "loginLogin": "පිවිසුම",
+ "loginFailed": "ඊ-මේල් ලිපිනය හෝ මුරපදය වැරදිය !",
+ "loginCreated": "නව පරිශීලක ලියාපදිංචි කරන ලදි !",
+ "loginLogout": "ඉවත්වන්න",
+ "devicesAndState": "උපාංග සහ ස්වභාවය",
+ "deviceDialog": "උපාංගය",
+ "deviceTitle": "උපාංග",
+ "deviceIdentifier": "හඳුනාගැනීමේ කේතය",
+ "deviceLastUpdate": "අවසන් යාවත්කාලීනය",
+ "deviceCommand": "විධානය",
+ "deviceFollow": "Follow",
+ "groupDialog": "සමූහය",
+ "groupParent": "සමූහය",
+ "groupNoGroup": "සමූහ එපා",
+ "settingsTitle": "සැකසුම්",
+ "settingsUser": "ගිණුම",
+ "settingsGroups": "සමූහ",
+ "settingsServer": "සේවාදායකය",
+ "settingsUsers": "පරිශීලකයන්",
+ "settingsSpeedUnit": "වේගය",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "වාර්තා",
+ "reportDevice": "උපාංගය",
+ "reportGroup": "Group",
+ "reportFrom": "සිට",
+ "reportTo": "දක්වා",
+ "reportShow": "පෙන්වන්න",
+ "reportClear": "ඉවත් කරන්න",
+ "positionFixTime": "කාලය",
+ "positionValid": "වලංගු",
+ "positionLatitude": "අක්ෂාංශ",
+ "positionLongitude": "දේශාංශ",
+ "positionAltitude": "උන්නතාංශය",
+ "positionSpeed": "වේගය",
+ "positionCourse": "දිගංශය",
+ "positionAddress": "ලිපිනය",
+ "positionProtocol": "ප්රොටොකෝලය",
+ "serverTitle": "සේවාදායකයේ සැකසුම්",
+ "serverZoom": "විශාලනය",
+ "serverRegistration": "ලියාපදිංචි කිරීම",
+ "serverReadonly": "Readonly",
+ "mapTitle": "සිතියම",
+ "mapLayer": "සිතියම් ස්තරය",
+ "mapCustom": "අභිරුචි සිතියම",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "බහුඅශ්‍රය",
+ "mapShapeCircle": "වෘත්තය",
+ "stateTitle": "තත්වය",
+ "stateName": "පරාමිතිය",
+ "stateValue": "අගය",
+ "commandTitle": "විධානය",
+ "commandSend": "යවන්න",
+ "commandSent": "විධානය යවා ඇත",
+ "commandPositionPeriodic": "ආවර්තිතව වාර්තා කරන්න",
+ "commandPositionStop": "වාර්තා කිරීම නවත්වන්න",
+ "commandEngineStop": "එන්ජිම නවත්වන්න",
+ "commandEngineResume": "එන්ජිම නැවත ආරම්භ කරන්න",
+ "commandFrequency": "සංඛ්යාතය",
+ "commandUnit": "ඒකකය",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/sk.json b/web/l10n/sk.json
new file mode 100644
index 0000000..9e807c6
--- /dev/null
+++ b/web/l10n/sk.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Načítava...",
+ "sharedSave": "Uložiť",
+ "sharedCancel": "Zrušiť",
+ "sharedAdd": "Pridať",
+ "sharedEdit": "Upraviť",
+ "sharedRemove": "Odstrániť",
+ "sharedRemoveConfirm": "Odstrániť položku?",
+ "sharedKm": "Km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "Km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Hodina",
+ "sharedMinute": "Minúta",
+ "sharedSecond": "Sekunda",
+ "sharedName": "Meno",
+ "sharedDescription": "Popis",
+ "sharedSearch": "Hľadať",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifikácie",
+ "sharedAttributes": "Atribúty",
+ "sharedAttribute": "Atribút",
+ "sharedArea": "Oblasť",
+ "sharedMute": "Stlmiť zvuk",
+ "sharedType": "Typ",
+ "sharedDistance": "Vzdialenosť",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Získať mapu štát",
+ "errorTitle": "Chyba",
+ "errorUnknown": "Neznáma chyba",
+ "errorConnection": "Chyba pripojenia",
+ "userEmail": "E-mail",
+ "userPassword": "Heslo",
+ "userAdmin": "Admin",
+ "userRemember": "Zapamätať",
+ "loginTitle": "Prihlásenie",
+ "loginLanguage": "Jazyk",
+ "loginRegister": "Registrovať",
+ "loginLogin": "Prihlásenie",
+ "loginFailed": "Nesprávna e-mailová adresa alebo heslo",
+ "loginCreated": "Nový užívateľ sa zaregistroval",
+ "loginLogout": "Odhlásiť",
+ "devicesAndState": "Zariadenia a Status",
+ "deviceDialog": "Zariadenie",
+ "deviceTitle": "Zariadena",
+ "deviceIdentifier": "Identifikátor",
+ "deviceLastUpdate": "Posledný update",
+ "deviceCommand": "Príkaz",
+ "deviceFollow": "Nasleduj",
+ "groupDialog": "Skupina",
+ "groupParent": "Skupina",
+ "groupNoGroup": "Žiadna skupina",
+ "settingsTitle": "Nastavenia",
+ "settingsUser": "Účet",
+ "settingsGroups": "Skupiny",
+ "settingsServer": "Server",
+ "settingsUsers": "Užívatelia",
+ "settingsSpeedUnit": "Rýchlosť jazdy",
+ "settingsTwelveHourFormat": "12-hodinový formát",
+ "reportTitle": "Správy",
+ "reportDevice": "Zariadenie",
+ "reportGroup": "Skupina",
+ "reportFrom": "Od",
+ "reportTo": "Do",
+ "reportShow": "Zobraziť",
+ "reportClear": "Vyčistiť",
+ "positionFixTime": "Čas",
+ "positionValid": "Platný",
+ "positionLatitude": "Šírka",
+ "positionLongitude": "Dĺžka",
+ "positionAltitude": "Výška",
+ "positionSpeed": "Rýchlosť jazdy",
+ "positionCourse": "Kurz",
+ "positionAddress": "Adresa",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Nastavenie servera",
+ "serverZoom": "Zoom",
+ "serverRegistration": "Registrácia",
+ "serverReadonly": "Iba na čítanie",
+ "mapTitle": "Mapa",
+ "mapLayer": "Mapové vrstvy",
+ "mapCustom": "Vlastná mapa",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Klúč Bing Maps",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Arial",
+ "mapShapePolygon": "Polygón",
+ "mapShapeCircle": "Kruh",
+ "stateTitle": "Štát",
+ "stateName": "Parameter",
+ "stateValue": "Hodnota",
+ "commandTitle": "Príkaz",
+ "commandSend": "Odoslať",
+ "commandSent": "Príkaz bol odoslaný",
+ "commandPositionPeriodic": "Pravidelné podávanie správ",
+ "commandPositionStop": "Zastavte podávanie správ",
+ "commandEngineStop": "Zastavenie motora",
+ "commandEngineResume": "Spustenie motora",
+ "commandFrequency": "Frekvencia",
+ "commandUnit": "Jednotka",
+ "commandCustom": "Vlastný príkaz",
+ "commandPositionSingle": "Jednoduché podávanie správ",
+ "commandAlarmArm": "Nastaviť upozornenie",
+ "commandAlarmDisarm": "Zrušiť upozornenie",
+ "commandSetTimezone": "Nastaviť časovú zónu",
+ "commandRequestPhoto": "Poslať fotku",
+ "commandRebootDevice": "Rebootovať zariadenie",
+ "commandSendSms": "Postať SMS",
+ "commandSendUssd": "Postať USSD",
+ "commandSosNumber": "Nastaviť čislo SOS",
+ "commandSilenceTime": "Nastav tichý čas",
+ "commandSetPhonebook": "Nastav telefónny zoznam",
+ "commandVoiceMessage": "Hlasové správy",
+ "commandOutputControl": "Výstupná kontrola",
+ "commandAlarmSpeed": "Upozornenie na prekročenie rýchlosti",
+ "commandDeviceIdentification": "Identifikácia zariadenia",
+ "commandIndex": "Index",
+ "commandData": "Dáta",
+ "commandPhone": "Telefónne číslo",
+ "commandMessage": "Správa",
+ "eventAll": "Všetky akcie",
+ "eventDeviceOnline": "Zariadenie je online",
+ "eventDeviceOffline": "Zariadenie je offline",
+ "eventDeviceMoving": "Zariadenie je v pohybe",
+ "eventDeviceStopped": "Zariadenie je zastavené",
+ "eventDeviceOverspeed": "Zariadenie prekročilo rýchlosť",
+ "eventCommandResult": "Výsledok príkazu",
+ "eventGeofenceEnter": "Zariadenie vstúpilo geofence zóny",
+ "eventGeofenceExit": "Zariadenie opustilo geofence zónu",
+ "eventAlarm": "Upozornenia",
+ "eventIgnitionOn": "Zapaľovanie je ZAPNUTÉ",
+ "eventIgnitionOff": "Zapaľovanie je VYPNUTÉ",
+ "alarm": "Upozornenie",
+ "alarmSos": "SOS upozornenie",
+ "alarmVibration": "Vibračné upozornenie",
+ "alarmMovement": "Upozornenie pohnutia",
+ "alarmOverspeed": "Upozornenie prekročenia rýchlosti ",
+ "alarmFallDown": "Upozornenie FallDown ",
+ "alarmLowBattery": "Upozornenie LowBattery",
+ "alarmFault": "Upozorneie poruchy",
+ "notificationType": "Typ notifikácie",
+ "notificationWeb": "Poslať cez Web",
+ "notificationMail": "Poslať e-mailom",
+ "reportRoute": "Cesta",
+ "reportEvents": "Udalosti",
+ "reportTrips": "Cesty",
+ "reportSummary": "Zhrnutie",
+ "reportConfigure": "Konfigurácia",
+ "reportEventTypes": "Typy udalstí",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Meno zariadenia",
+ "reportAverageSpeed": "Priemerná rýchlosť",
+ "reportMaximumSpeed": "Maximálna rýchlosť",
+ "reportEngineHours": "Prevádzkové hodiny motora",
+ "reportDuration": "Trvanie",
+ "reportStartTime": "Čas spustenia",
+ "reportStartAddress": "Počiatočná adresa",
+ "reportEndTime": "Čas ukončenia",
+ "reportEndAddress": "Koncová adresa",
+ "reportSpentFuel": "Spotrebované palivo"
+} \ No newline at end of file
diff --git a/web/l10n/sl.json b/web/l10n/sl.json
new file mode 100644
index 0000000..2f3eab0
--- /dev/null
+++ b/web/l10n/sl.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Nalagam...",
+ "sharedSave": "Shrani",
+ "sharedCancel": "Prekini",
+ "sharedAdd": "Dodaj",
+ "sharedEdit": "Uredi",
+ "sharedRemove": "Odstrani",
+ "sharedRemoveConfirm": "Odstranim zapis?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Ura",
+ "sharedMinute": "Minuta",
+ "sharedSecond": "Sekunda",
+ "sharedName": "Name",
+ "sharedDescription": "Description",
+ "sharedSearch": "Search",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Napaka",
+ "errorUnknown": "Neznana napaka",
+ "errorConnection": "Napaka v povezavi",
+ "userEmail": "E-Pošta",
+ "userPassword": "Geslo",
+ "userAdmin": "Admin",
+ "userRemember": "Remember",
+ "loginTitle": "Prijava",
+ "loginLanguage": "Jezik",
+ "loginRegister": "Registracija",
+ "loginLogin": "Prijava",
+ "loginFailed": "Nepravilna e-pošta ali geslo",
+ "loginCreated": "Nov uporabnik je registriran",
+ "loginLogout": "Odjava",
+ "devicesAndState": "Devices and State",
+ "deviceDialog": "Naprave",
+ "deviceTitle": "Naprave",
+ "deviceIdentifier": "Identifikacija",
+ "deviceLastUpdate": "Last Update",
+ "deviceCommand": "Ukaz",
+ "deviceFollow": "Follow",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "Nastavitve",
+ "settingsUser": "Račun",
+ "settingsGroups": "Groups",
+ "settingsServer": "Strežnik",
+ "settingsUsers": "Uporabniki",
+ "settingsSpeedUnit": "Hitrost",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "Poročila",
+ "reportDevice": "Naprava",
+ "reportGroup": "Group",
+ "reportFrom": "Od",
+ "reportTo": "Do",
+ "reportShow": "Prikaži",
+ "reportClear": "Očisti",
+ "positionFixTime": "Čas",
+ "positionValid": "Veljavnost",
+ "positionLatitude": "Širina",
+ "positionLongitude": "Dolžina",
+ "positionAltitude": "Višina",
+ "positionSpeed": "Hitrost",
+ "positionCourse": "Smer",
+ "positionAddress": "Naslov",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Nastavitve strežnika",
+ "serverZoom": "Povečava",
+ "serverRegistration": "Registracija",
+ "serverReadonly": "Readonly",
+ "mapTitle": "Karta",
+ "mapLayer": "Zemljevidi",
+ "mapCustom": "Poljubna karta",
+ "mapOsm": "Open Street Karta",
+ "mapBingKey": "Bing Mapk Ključ",
+ "mapBingRoad": "Bing Maps Ceste",
+ "mapBingAerial": "Bing Maps Satelit",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "Stanje",
+ "stateName": "Parameter",
+ "stateValue": "Vrednost",
+ "commandTitle": "Ukaz",
+ "commandSend": "Pošlji",
+ "commandSent": "Ukaz poslan",
+ "commandPositionPeriodic": "Periodično poročanje",
+ "commandPositionStop": "Ustavi poročanje",
+ "commandEngineStop": "Ugasni motor",
+ "commandEngineResume": "Prižgi motor",
+ "commandFrequency": "Frekvenca",
+ "commandUnit": "Naprava",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/sq.json b/web/l10n/sq.json
new file mode 100644
index 0000000..285752e
--- /dev/null
+++ b/web/l10n/sq.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Ngarkim…",
+ "sharedSave": "Ruaj",
+ "sharedCancel": "Anullim",
+ "sharedAdd": "Shto",
+ "sharedEdit": "Ndrysho",
+ "sharedRemove": "Hiq",
+ "sharedRemoveConfirm": "Hiq skedarin",
+ "sharedKm": "km",
+ "sharedMi": "Milje",
+ "sharedKn": "Nyje",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Orë",
+ "sharedMinute": "Minuta",
+ "sharedSecond": "Sekonda",
+ "sharedName": "Emri",
+ "sharedDescription": "Description",
+ "sharedSearch": "Kërkim",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Gabim",
+ "errorUnknown": "Gabim i panjohur",
+ "errorConnection": "Gabim lidhjeje",
+ "userEmail": "Email",
+ "userPassword": "Fjalëkalimi",
+ "userAdmin": "Administratori",
+ "userRemember": "Remember",
+ "loginTitle": "Hyrje",
+ "loginLanguage": "Gjuha",
+ "loginRegister": "Regjistrim",
+ "loginLogin": "Lidhu",
+ "loginFailed": "Adresë Email-i ose fjalëkalim i gabuar",
+ "loginCreated": "Përdoruesi i ri u regjistrua",
+ "loginLogout": "Shkëputu",
+ "devicesAndState": "Gjendja e pajisjeve",
+ "deviceDialog": "Pajisje",
+ "deviceTitle": "Pajisjet",
+ "deviceIdentifier": "Identifikues",
+ "deviceLastUpdate": "Përditësimi i fundit",
+ "deviceCommand": "Komandë",
+ "deviceFollow": "Ndjek",
+ "groupDialog": "Grup",
+ "groupParent": "Grup",
+ "groupNoGroup": "Pa Grup",
+ "settingsTitle": "Parametra",
+ "settingsUser": "Llogari",
+ "settingsGroups": "Grupe",
+ "settingsServer": "Rrjeti",
+ "settingsUsers": "Përdorues",
+ "settingsSpeedUnit": "Shpejtësi",
+ "settingsTwelveHourFormat": "Formë 12-orëshe",
+ "reportTitle": "Raporte",
+ "reportDevice": "Pajisje",
+ "reportGroup": "Group",
+ "reportFrom": "Nga",
+ "reportTo": "Tek",
+ "reportShow": "Shfaqje",
+ "reportClear": "Pastrim",
+ "positionFixTime": "Koha",
+ "positionValid": "I vlefshëm",
+ "positionLatitude": "Gjerësi Gjeografike",
+ "positionLongitude": "Gjatësi Gjeografike",
+ "positionAltitude": "Lartësia",
+ "positionSpeed": "Shpejtësia",
+ "positionCourse": "Itinerari (Rruga)",
+ "positionAddress": "Adresa",
+ "positionProtocol": "Protokolli",
+ "serverTitle": "Server-Parametrat",
+ "serverZoom": "Fokus",
+ "serverRegistration": "Regjistrim",
+ "serverReadonly": "Vetëm për lexim",
+ "mapTitle": "Harta",
+ "mapLayer": "Zgjedhje harte",
+ "mapCustom": "Hartë e përshtatur",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "Gjëndja",
+ "stateName": "Atribut",
+ "stateValue": "Vlera",
+ "commandTitle": "Komandë",
+ "commandSend": "Dërgo",
+ "commandSent": "Komanda u dërgua",
+ "commandPositionPeriodic": "Raporte periodike",
+ "commandPositionStop": "Ndalo raportin",
+ "commandEngineStop": "Ndalo punën",
+ "commandEngineResume": "Rinis",
+ "commandFrequency": "Frekuenca",
+ "commandUnit": "Njësi",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/sr.json b/web/l10n/sr.json
new file mode 100644
index 0000000..abab940
--- /dev/null
+++ b/web/l10n/sr.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Učitava...",
+ "sharedSave": "Sačuvaj",
+ "sharedCancel": "Odustani",
+ "sharedAdd": "Dodaj",
+ "sharedEdit": "Podesi",
+ "sharedRemove": "Ukloni",
+ "sharedRemoveConfirm": "Ukloniti jedinicu?",
+ "sharedKm": "km",
+ "sharedMi": "mi",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Čas",
+ "sharedMinute": "Minut",
+ "sharedSecond": "Sekunda",
+ "sharedName": "Ime",
+ "sharedDescription": "Opis",
+ "sharedSearch": "Traži",
+ "sharedGeofence": "Geoograda",
+ "sharedGeofences": "Geoograde",
+ "sharedNotifications": "Obaveštenja",
+ "sharedAttributes": "Osobine",
+ "sharedAttribute": "Osobina",
+ "sharedArea": "Oblast",
+ "sharedMute": "Nečujno",
+ "sharedType": "Tip",
+ "sharedDistance": "Razdaljina",
+ "sharedHourAbbreviation": "č",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Stanje mape",
+ "errorTitle": "Greška",
+ "errorUnknown": "Nepoznata greška",
+ "errorConnection": "Greška u konekciji",
+ "userEmail": "Email",
+ "userPassword": "Lozinka",
+ "userAdmin": "Admin",
+ "userRemember": "Zapamti",
+ "loginTitle": "Prijava",
+ "loginLanguage": "Jezik",
+ "loginRegister": "Registruj se",
+ "loginLogin": "Prijava",
+ "loginFailed": "Neispravna email adresa ili lozinka",
+ "loginCreated": "Novi korisnik je registrovan",
+ "loginLogout": "Odjava",
+ "devicesAndState": "Uređaji i Stanje ",
+ "deviceDialog": "Uređaj",
+ "deviceTitle": "Uređaji",
+ "deviceIdentifier": "Identifikator",
+ "deviceLastUpdate": "Poslednji kontakt",
+ "deviceCommand": "Komanda",
+ "deviceFollow": "Prati",
+ "groupDialog": "Grupa",
+ "groupParent": "Grupa",
+ "groupNoGroup": "Nema grupe",
+ "settingsTitle": "Podešavanja",
+ "settingsUser": "Nalog",
+ "settingsGroups": "Grupe",
+ "settingsServer": "Server",
+ "settingsUsers": "Korisnici",
+ "settingsSpeedUnit": "Brzina",
+ "settingsTwelveHourFormat": "12-časovni format",
+ "reportTitle": "Izveštaji",
+ "reportDevice": "Uređaj",
+ "reportGroup": "Grupa",
+ "reportFrom": "Od",
+ "reportTo": "Do",
+ "reportShow": "Prikaži",
+ "reportClear": "Izbriši",
+ "positionFixTime": "Vreme",
+ "positionValid": "Ispravno",
+ "positionLatitude": "Geografska širina",
+ "positionLongitude": "Geografska dužina",
+ "positionAltitude": "Visina",
+ "positionSpeed": "Brzina",
+ "positionCourse": "Pravac",
+ "positionAddress": "Adresa",
+ "positionProtocol": "Protokol",
+ "serverTitle": "Podešavanja Servera",
+ "serverZoom": "Zumiranje",
+ "serverRegistration": "Registracija",
+ "serverReadonly": "Readonly verzija",
+ "mapTitle": "Mapa",
+ "mapLayer": "Vrsta Mape",
+ "mapCustom": "Prilagođena mapa",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Višeugao",
+ "mapShapeCircle": "Krug",
+ "stateTitle": "Stanje",
+ "stateName": "Parametar",
+ "stateValue": "Vrednost",
+ "commandTitle": "Komanda",
+ "commandSend": "Pošalji",
+ "commandSent": "Komanda je poslata",
+ "commandPositionPeriodic": "Periodično izveštavanje",
+ "commandPositionStop": "Prekini izveštavanja",
+ "commandEngineStop": "Zaustavi motor",
+ "commandEngineResume": "Pokreni motor",
+ "commandFrequency": "Frekvencija",
+ "commandUnit": "Jedinica",
+ "commandCustom": "Prilagođena komanda",
+ "commandPositionSingle": "Izveštaj za jednog",
+ "commandAlarmArm": "Omogući alarm",
+ "commandAlarmDisarm": "Onemogući alarm",
+ "commandSetTimezone": "Podesi vremensku zonu",
+ "commandRequestPhoto": "Zahtevaj fotografiju",
+ "commandRebootDevice": "Ponovo pokreni uređaj",
+ "commandSendSms": "Pošalji SMS",
+ "commandSendUssd": "Pošalji USSD",
+ "commandSosNumber": "Podesi SOS broj",
+ "commandSilenceTime": "Podesi nečujno vreme ",
+ "commandSetPhonebook": "Podesi kontakte",
+ "commandVoiceMessage": "Glasovna poruka",
+ "commandOutputControl": "Kontrola izlaza",
+ "commandAlarmSpeed": "Alarm prekoračenja brzine",
+ "commandDeviceIdentification": "Identifikacija uređaja",
+ "commandIndex": "Lista",
+ "commandData": "Podaci",
+ "commandPhone": "Broj telefona",
+ "commandMessage": "Poruka",
+ "eventAll": "Svi događaji",
+ "eventDeviceOnline": "Uređaj je na mreži",
+ "eventDeviceOffline": "Uređaj je van mreže",
+ "eventDeviceMoving": "Uređaj se kreće",
+ "eventDeviceStopped": "Uređaj je zaustavljen",
+ "eventDeviceOverspeed": "Uređaj prelazi brzinu",
+ "eventCommandResult": "Stanje komande",
+ "eventGeofenceEnter": "Uređaj je ušao u geoogradu",
+ "eventGeofenceExit": "Uređaj je izašao iz geoograde",
+ "eventAlarm": "Alarmi",
+ "eventIgnitionOn": "Kontakt uklj.",
+ "eventIgnitionOff": "Kontakt isklj.",
+ "alarm": "Alarm",
+ "alarmSos": "SOS alarm",
+ "alarmVibration": "Alarm vibracija",
+ "alarmMovement": "Alarm Kretanja",
+ "alarmOverspeed": "Prekoračenje brzine alarm",
+ "alarmFallDown": "Padanje Alarm",
+ "alarmLowBattery": "Slaba baterija alarm",
+ "alarmFault": "Alarm greške",
+ "notificationType": "Tip obaveštenja",
+ "notificationWeb": "Pošalji preko Web-a",
+ "notificationMail": "Pošalji putem Email-a",
+ "reportRoute": "Ruta",
+ "reportEvents": "Događaji",
+ "reportTrips": "Vožnje",
+ "reportSummary": "Izveštaj",
+ "reportConfigure": "Konfiguracija",
+ "reportEventTypes": "Tip događaja",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Ime uređaja",
+ "reportAverageSpeed": "Prosečna brzina",
+ "reportMaximumSpeed": "Maksimalna brzina",
+ "reportEngineHours": "Radni sati",
+ "reportDuration": "Trajanje",
+ "reportStartTime": "Startno vreme",
+ "reportStartAddress": "Početna adresa",
+ "reportEndTime": "Završno vreme",
+ "reportEndAddress": "Krajnja adresa",
+ "reportSpentFuel": "Potrošeno goriva"
+} \ No newline at end of file
diff --git a/web/l10n/ta.json b/web/l10n/ta.json
new file mode 100644
index 0000000..71882b9
--- /dev/null
+++ b/web/l10n/ta.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "பதிவு செய்",
+ "sharedSave": "சேமி",
+ "sharedCancel": "ரத்து செய்",
+ "sharedAdd": "சேர்க்க",
+ "sharedEdit": "தொகுக்க",
+ "sharedRemove": "நீக்குக",
+ "sharedRemoveConfirm": "நீக்கம் உறுதி செய்?",
+ "sharedKm": "கிமீ",
+ "sharedMi": "மைல்",
+ "sharedKn": "கடல் மைல்",
+ "sharedKmh": "கிமீ/மணிக்கு",
+ "sharedMph": "மைல்/மணிக்கு",
+ "sharedHour": "மணி நேரம்",
+ "sharedMinute": "நிமிடம்",
+ "sharedSecond": "விநாடி",
+ "sharedName": "பெயர்",
+ "sharedDescription": "விளக்கம்",
+ "sharedSearch": "தேடுக",
+ "sharedGeofence": "பூகோள வேலி",
+ "sharedGeofences": "பூகோள வேலிகள்",
+ "sharedNotifications": "அறிவிப்புகள்",
+ "sharedAttributes": "பண்புகள்",
+ "sharedAttribute": "பண்பு",
+ "sharedArea": "பகுதி",
+ "sharedMute": "Mute",
+ "sharedType": "வகை",
+ "sharedDistance": "தொலைவு",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "பிழை",
+ "errorUnknown": "அறியப்படாத பிழை",
+ "errorConnection": "இணைப்புப் பிழை",
+ "userEmail": "மின்னஞ்சல்",
+ "userPassword": "கடவுச்சொல்",
+ "userAdmin": "நிர்வாகி",
+ "userRemember": "நினைவில் கொள்",
+ "loginTitle": "உள் நுழை",
+ "loginLanguage": "மொழி",
+ "loginRegister": "பதிவு செய்ய",
+ "loginLogin": "உள்நுழைய",
+ "loginFailed": "தவறான மின்னஞ்சல் முகவரி அல்லது கடவுச்சொல்",
+ "loginCreated": "புதிய பயனர் பதிவு செய்யப்பட்டுள்ளது",
+ "loginLogout": "வெளியேறு",
+ "devicesAndState": "கருவிகள் மற்றும் அதன் நிலை",
+ "deviceDialog": "சாதனம்",
+ "deviceTitle": "சாதனம்",
+ "deviceIdentifier": "அடையாளங்காட்டி",
+ "deviceLastUpdate": "கடைசியாக புதுப்பிக்கப்பட்டது",
+ "deviceCommand": "கட்டளை",
+ "deviceFollow": "பின்தொடர்",
+ "groupDialog": "குழு",
+ "groupParent": "குழு",
+ "groupNoGroup": "குழு இல்லை",
+ "settingsTitle": "அமைப்பு",
+ "settingsUser": "கணக்கு",
+ "settingsGroups": "குழுக்கள்",
+ "settingsServer": "சர்வர்",
+ "settingsUsers": "உறுப்பினர்கள்",
+ "settingsSpeedUnit": "வேகம்",
+ "settingsTwelveHourFormat": "12 மணி நேர வடிவம்",
+ "reportTitle": "அறிக்கை",
+ "reportDevice": "சாதனம்",
+ "reportGroup": "Group",
+ "reportFrom": "இருந்து",
+ "reportTo": "வரை",
+ "reportShow": "காண்பி",
+ "reportClear": "அழி",
+ "positionFixTime": "நேரம்",
+ "positionValid": "செல்லுபடியான",
+ "positionLatitude": "அட்சரேகை",
+ "positionLongitude": "தீர்க்கரேகை",
+ "positionAltitude": "உயரம்",
+ "positionSpeed": "வேகம்",
+ "positionCourse": "பாடநெறி",
+ "positionAddress": "முகவரி",
+ "positionProtocol": "புரோட்டோகால்",
+ "serverTitle": "சர்வர் அமைப்பு",
+ "serverZoom": "பெரிதாக்கு",
+ "serverRegistration": "பதிவுசெய்ய",
+ "serverReadonly": "படிக்கமட்டும்",
+ "mapTitle": "வரைபடம்",
+ "mapLayer": "வரைபடம் அடுக்கு",
+ "mapCustom": "விருப்ப வரைபடம்",
+ "mapOsm": "திறமூல தெரு வரைபடம்",
+ "mapBingKey": "பிங் வரைபட கீ",
+ "mapBingRoad": "பிங் சாலை வரைபடம்",
+ "mapBingAerial": "பிங் வான்வழி வரைபடம்",
+ "mapShapePolygon": "பலகோணம்",
+ "mapShapeCircle": "வட்டம்",
+ "stateTitle": "நிலை",
+ "stateName": "சாட்டு",
+ "stateValue": "மதிப்பு",
+ "commandTitle": "கட்டளை",
+ "commandSend": "அனுப்பு",
+ "commandSent": "கட்டளை அனுப்பப்பட்டது",
+ "commandPositionPeriodic": "காலமுறை அறிக்கையிடல்",
+ "commandPositionStop": "அறிக்கையிடுதல் நிறுத்து ",
+ "commandEngineStop": "எஞ்சின் நிறுத்து",
+ "commandEngineResume": "எஞ்சின் தொடங்க",
+ "commandFrequency": "காலஇடைவெளி",
+ "commandUnit": "அலகு",
+ "commandCustom": "விருப்பமான கட்டளை",
+ "commandPositionSingle": "ஒற்றை அறிக்கை",
+ "commandAlarmArm": "அலறிமணி துவக்கம்",
+ "commandAlarmDisarm": "அலறிமணி நிறுத்தம்",
+ "commandSetTimezone": "நேர மண்டலம்",
+ "commandRequestPhoto": "புகைப்படம் வேண்டு",
+ "commandRebootDevice": "சாதன மறுதுவக்கம்",
+ "commandSendSms": "குருஞ்செய்தி அனுப்பு",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "அவசர அழைப்பு எண்(SOS)",
+ "commandSilenceTime": "அமைதி நேரம் அமைக்க",
+ "commandSetPhonebook": "தொலைபேசிப்புத்தகம் அமை",
+ "commandVoiceMessage": "குரல் செய்தி",
+ "commandOutputControl": "வெளியீட்டு கட்டுப்பாடு",
+ "commandAlarmSpeed": "அதி வேக அலறி ",
+ "commandDeviceIdentification": "\nசாதன அடையாளம்",
+ "commandIndex": "Index",
+ "commandData": "தரவு",
+ "commandPhone": "தொலைபேசி எண்",
+ "commandMessage": "குறுஞ்செய்தி",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "சாதனம் இணைப்பில் உள்ளது",
+ "eventDeviceOffline": "சாதன இணைப்பு துண்டிக்கபட்டது",
+ "eventDeviceMoving": "சாதனம் நகருகிறது",
+ "eventDeviceStopped": "சாதனம் நின்றுவிட்டது",
+ "eventDeviceOverspeed": "சாதனம் நிர்ணயித்த வேகத் திற்கு மேல்",
+ "eventCommandResult": "கட்டளை விளைவு",
+ "eventGeofenceEnter": "சாதனம் பூகோள வேலியினுள் நுழைந்துள்ளது",
+ "eventGeofenceExit": "சாதனம் பூகோள வேலியை விட்டு வெளியேறியது",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "type of notification",
+ "notificationWeb": "வலைதளம் வழி அனுப்புக ",
+ "notificationMail": "மின்னஞ்சல் வழி அனுப்புக",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/th.json b/web/l10n/th.json
new file mode 100644
index 0000000..80f71c0
--- /dev/null
+++ b/web/l10n/th.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "กำลังโหลด",
+ "sharedSave": "จัดเก็บแฟ้มข้อมูล",
+ "sharedCancel": "ยกเลิก",
+ "sharedAdd": "เพิ่ม",
+ "sharedEdit": "ตรวจแก้ ปรับเปลี่ยนข้อมูล",
+ "sharedRemove": "ลบรายการ",
+ "sharedRemoveConfirm": "ยืนยันลบรายการ",
+ "sharedKm": "กม.",
+ "sharedMi": "ไมล์",
+ "sharedKn": "น๊อต",
+ "sharedKmh": "กม./ชม.",
+ "sharedMph": "ไมล์ต่อชั่วโมง",
+ "sharedHour": "ชั่วโมง",
+ "sharedMinute": "นาที",
+ "sharedSecond": "วินาที",
+ "sharedName": "ชื่อ",
+ "sharedDescription": "ลักษณะ",
+ "sharedSearch": "ค้นหา",
+ "sharedGeofence": "เขตพื้นที่",
+ "sharedGeofences": "เขตพื้นที่",
+ "sharedNotifications": "การแจ้งเตือน",
+ "sharedAttributes": "คุณลักษณะ",
+ "sharedAttribute": "คุณลักษณะ",
+ "sharedArea": "พื้นที่",
+ "sharedMute": "ปิดเสียง",
+ "sharedType": "ชนิด",
+ "sharedDistance": "ระยะทาง",
+ "sharedHourAbbreviation": "ชม.",
+ "sharedMinuteAbbreviation": "นาที",
+ "sharedGetMapState": "ได้รับสถานะแผนที่",
+ "errorTitle": "ผิดพลาด",
+ "errorUnknown": "ข้อผิดพลาดที่ไม่รู้จัก",
+ "errorConnection": "การเชื่อมต่อผิดพลาด",
+ "userEmail": "อีเมล์",
+ "userPassword": "รหัสผ่าน",
+ "userAdmin": "ผู้ดูแลระบบ",
+ "userRemember": "จำไว้",
+ "loginTitle": "เข้าสู่ระบบ",
+ "loginLanguage": "ภาษา",
+ "loginRegister": "ลงทะเบียน",
+ "loginLogin": "เข้าสู่ระบบ",
+ "loginFailed": "ที่อยู่อีเมลหรือรหัสผ่านไม่ถูกต้อง",
+ "loginCreated": "ผู้ใช้ใหม่ ได้รับการลงทะเบียน",
+ "loginLogout": "ออกจากระบบ",
+ "devicesAndState": "อุปกรณ์และสถานะ",
+ "deviceDialog": "เครื่อง/อุปกรณ์",
+ "deviceTitle": "เครื่อง/อุปกรณ์",
+ "deviceIdentifier": "ระบุเลขอุปกรณ์",
+ "deviceLastUpdate": "แก้ไขล่าสุด",
+ "deviceCommand": "คำสั่ง",
+ "deviceFollow": "ติดตาม",
+ "groupDialog": "กลุ่ม",
+ "groupParent": "กลุ่ม",
+ "groupNoGroup": "ไม่จัดกลุ่ม",
+ "settingsTitle": "การตั้งค่า",
+ "settingsUser": "บัญชีผู้ใช้",
+ "settingsGroups": "ตั้งค่ากลุ่ม",
+ "settingsServer": "ตั้งค่าระบบ",
+ "settingsUsers": "ตั้งค่าผู้ใช้งาน",
+ "settingsSpeedUnit": "หน่วยความเร็ว",
+ "settingsTwelveHourFormat": "รูปแบบเวลา 12 ชั่วโมง",
+ "reportTitle": "รายงาน",
+ "reportDevice": "รายงานเครื่อง/อุปกรณ์",
+ "reportGroup": "กลุ่ม",
+ "reportFrom": "จาก",
+ "reportTo": "ไปถึง",
+ "reportShow": "แสดง",
+ "reportClear": "ล้างรายงาน",
+ "positionFixTime": "เวลา",
+ "positionValid": "ถูกต้อง",
+ "positionLatitude": "ละติจูด",
+ "positionLongitude": "ลองจิจูด",
+ "positionAltitude": "ระดับความสูง",
+ "positionSpeed": "ความเร็ว",
+ "positionCourse": "ทิศทาง",
+ "positionAddress": "ที่อยู่",
+ "positionProtocol": "โปรโตคอล",
+ "serverTitle": "การตั้งค่าเซิร์ฟเวอ",
+ "serverZoom": "ชยาย +/-",
+ "serverRegistration": "ลงทะเบียน",
+ "serverReadonly": "อ่านได้อย่างเดียว",
+ "mapTitle": "แผนที่",
+ "mapLayer": "ชั้นแผนที่",
+ "mapCustom": "แผนที่ที่กำหนดเอง",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps สำคัญ",
+ "mapBingRoad": "Bing Maps ถนน",
+ "mapBingAerial": "Bing Maps ทางอากาศ",
+ "mapShapePolygon": "โพลิกอน",
+ "mapShapeCircle": "วงกลม",
+ "stateTitle": "สถานะ",
+ "stateName": "พารามิเตอร์",
+ "stateValue": "มูลค่า",
+ "commandTitle": "คำสั่ง",
+ "commandSend": "ส่ง",
+ "commandSent": "คำสั่งถูกส่งไปแล้ว",
+ "commandPositionPeriodic": "แก้ไขตำแหน่ง",
+ "commandPositionStop": "ตำแหน่ง หยุด",
+ "commandEngineStop": "ดับเครื่องยนต์",
+ "commandEngineResume": "ติดครื่องยนต์ใหม่",
+ "commandFrequency": "ความถี่",
+ "commandUnit": "หน่วย",
+ "commandCustom": "คำสั่งกำหนดเอง",
+ "commandPositionSingle": "รายงานตำแหน่งเดียว",
+ "commandAlarmArm": "แจ้งเตือนติดต่อสาขา",
+ "commandAlarmDisarm": "แจ้งเตือนยกเลิกติดต่อสาขา",
+ "commandSetTimezone": "ตั้งค่าเขตเวลา",
+ "commandRequestPhoto": "สั่งถ่ายภาพ",
+ "commandRebootDevice": "รีบูต",
+ "commandSendSms": "ส่ง SMS",
+ "commandSendUssd": "ส่ง USSD",
+ "commandSosNumber": "ตั้งค่าเลขหมายโทรฉุกเฉิน SOS",
+ "commandSilenceTime": "ตั้งค่าช่วงเาลาหยุดนิ่ง",
+ "commandSetPhonebook": "ตั้งค่าสมุดโทรศัพท์",
+ "commandVoiceMessage": "ข้อความเสียง",
+ "commandOutputControl": "ควบคุมข้อมูลที่ส่งออก",
+ "commandAlarmSpeed": "แจ้งเตือนความเร็วเกินกำหนด",
+ "commandDeviceIdentification": "หมายเลขอุปกรณ์",
+ "commandIndex": "ดัชนี",
+ "commandData": "ข้อมูล",
+ "commandPhone": "หมายเลขโทรศัพท์",
+ "commandMessage": "ข้อความ",
+ "eventAll": "เหตุการณ์ทั้งหมด",
+ "eventDeviceOnline": "อุปกรณ์เชื่อมต่อแล้ว",
+ "eventDeviceOffline": "อุปกรณ์ไม่ได้เชื่อมต่อ",
+ "eventDeviceMoving": "อุปกรณ์กำลังเคลื่อนที่",
+ "eventDeviceStopped": "อุปกรณ์ไม่เคลื่อนไหว",
+ "eventDeviceOverspeed": "อุปกรณ์เกินกำหนดความเร็ว",
+ "eventCommandResult": "ผลลัพธ์จากคำสั่ง",
+ "eventGeofenceEnter": "อุปกรณ์เข้าในเขตพื้นที่",
+ "eventGeofenceExit": "อุปกรณ์ออกนอกเขตพื้นที่",
+ "eventAlarm": "แจ้งเตือน",
+ "eventIgnitionOn": "สวิทย์กุญแจ เปิด",
+ "eventIgnitionOff": "สวิทย์กุญแจ ปิด",
+ "alarm": "แจ้งเตือน",
+ "alarmSos": "แจ้งเตือนฉุกเฉิน SOS",
+ "alarmVibration": "แจ้งเตือนการสั่นสะเทือน",
+ "alarmMovement": "แจ้งเตือนการเคลื่อนไหว",
+ "alarmOverspeed": "แจ้งเตือนความเร็วเกินกำหนด",
+ "alarmFallDown": "แจ้งเตือนการล้ม",
+ "alarmLowBattery": "แจ้งเตือนแบตเตอรี่เหลือน้อย",
+ "alarmFault": "แจ้งเตือนข้อผิดพลาด",
+ "notificationType": "ชนิดการแจ้งเตือน",
+ "notificationWeb": "ส่งทางเว็บ",
+ "notificationMail": "ส่งทางเมล์",
+ "reportRoute": "เส้นทาง",
+ "reportEvents": "เหตุการณ์",
+ "reportTrips": "การเดินทาง",
+ "reportSummary": "ผลรวม",
+ "reportConfigure": "ตั้งค่า",
+ "reportEventTypes": "ประเภทเหตุการณ์",
+ "reportCsv": "CSV",
+ "reportDeviceName": "ชื่ออุปกรณ์",
+ "reportAverageSpeed": "ความเร็วเฉลี่ย",
+ "reportMaximumSpeed": "ความเร็วสูงสุด",
+ "reportEngineHours": "เวลาการทำงานเครื่องยนต์",
+ "reportDuration": "ช่วงเวลา",
+ "reportStartTime": "เวลาเริ่มต้น",
+ "reportStartAddress": "จุดเริ่มต้น",
+ "reportEndTime": "เวลาสิ้นสุด",
+ "reportEndAddress": "จุดสิ้นสุด",
+ "reportSpentFuel": "เชื้อเพลิงที่ใช้"
+} \ No newline at end of file
diff --git a/web/l10n/tr.json b/web/l10n/tr.json
new file mode 100644
index 0000000..326d1ad
--- /dev/null
+++ b/web/l10n/tr.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Yükleniyor...",
+ "sharedSave": "Kaydet",
+ "sharedCancel": "İptal",
+ "sharedAdd": "Ekle",
+ "sharedEdit": "Düzenle",
+ "sharedRemove": "Kaldır",
+ "sharedRemoveConfirm": "Öğeyi kaldır",
+ "sharedKm": "km",
+ "sharedMi": "mil",
+ "sharedKn": "Knot",
+ "sharedKmh": "km/s",
+ "sharedMph": "mil/s",
+ "sharedHour": "Saat",
+ "sharedMinute": "Dakika",
+ "sharedSecond": "Saniye",
+ "sharedName": "İsim",
+ "sharedDescription": "Açıklama",
+ "sharedSearch": "Arama",
+ "sharedGeofence": "Güvenli Bölge",
+ "sharedGeofences": "Güvenli Bölgeler",
+ "sharedNotifications": "Bildirimler",
+ "sharedAttributes": "Nitelikler",
+ "sharedAttribute": "Nitelik",
+ "sharedArea": "Bölge",
+ "sharedMute": "Sessiz",
+ "sharedType": "Tip",
+ "sharedDistance": "Mesafe",
+ "sharedHourAbbreviation": "s",
+ "sharedMinuteAbbreviation": "d",
+ "sharedGetMapState": "Harita Durumunu Getir",
+ "errorTitle": "Hata",
+ "errorUnknown": "Bilinmeyen hata ",
+ "errorConnection": "Bağlantı Hatası",
+ "userEmail": "Eposta",
+ "userPassword": "Şifre",
+ "userAdmin": "Yönetici",
+ "userRemember": "Hatırla",
+ "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",
+ "deviceIdentifier": "Kimlik",
+ "deviceLastUpdate": "Son Güncelleme",
+ "deviceCommand": "Komut",
+ "deviceFollow": "Takip",
+ "groupDialog": "Grup",
+ "groupParent": "Grup",
+ "groupNoGroup": "Grupsuz",
+ "settingsTitle": "Ayarlar",
+ "settingsUser": "Hesap",
+ "settingsGroups": "Gruplar",
+ "settingsServer": "Sunucu",
+ "settingsUsers": "Kullanıcı",
+ "settingsSpeedUnit": "Hız",
+ "settingsTwelveHourFormat": "12 saat formatı",
+ "reportTitle": "Raporlar",
+ "reportDevice": "Aygıt",
+ "reportGroup": "Grup",
+ "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",
+ "mapShapePolygon": "Çokgen",
+ "mapShapeCircle": "Çember",
+ "stateTitle": "Bölge",
+ "stateName": "Özellik",
+ "stateValue": "Değer",
+ "commandTitle": "Komut",
+ "commandSend": "Gönder",
+ "commandSent": "Komut gönderildi",
+ "commandPositionPeriodic": "Periyodik Rapor",
+ "commandPositionStop": "Raporlamayı Durdur",
+ "commandEngineStop": "Motoru Durdur",
+ "commandEngineResume": "Motoru Çalıştır",
+ "commandFrequency": "Frekans",
+ "commandUnit": "Ünite",
+ "commandCustom": "Özel komut",
+ "commandPositionSingle": "Tekil Raporlama",
+ "commandAlarmArm": "Alarm Kur",
+ "commandAlarmDisarm": "Alarmı Kapat",
+ "commandSetTimezone": "Zaman Dilimini Belirle",
+ "commandRequestPhoto": "Fotoğraf İste",
+ "commandRebootDevice": "Aygıtı Yeniden Başlat",
+ "commandSendSms": "SMS Gönder",
+ "commandSendUssd": "USSD Gönder",
+ "commandSosNumber": "Acil Durum Numarasını Belirle",
+ "commandSilenceTime": "Sessiz Zamanı Belirle",
+ "commandSetPhonebook": "Telefon Defterini Belirle",
+ "commandVoiceMessage": "Ses Mesajı",
+ "commandOutputControl": "Çıkış Kontrolü",
+ "commandAlarmSpeed": "Hız Alarmı",
+ "commandDeviceIdentification": "Cihaz Tanımı",
+ "commandIndex": "Fihrist",
+ "commandData": "Veri",
+ "commandPhone": "Telefon Numarası",
+ "commandMessage": "Mesaj",
+ "eventAll": "Tüm Olaylar",
+ "eventDeviceOnline": "Cihaz çevrimiçi",
+ "eventDeviceOffline": "Cihaz çevrimdışı",
+ "eventDeviceMoving": "Cihaz hareket halinde",
+ "eventDeviceStopped": "Cihaz durdu",
+ "eventDeviceOverspeed": "Cihaz hızı aştı",
+ "eventCommandResult": "Komut sonucu",
+ "eventGeofenceEnter": "Cihaz güvenli bölgede",
+ "eventGeofenceExit": "Cihaz güvenli bölgeden çıktı",
+ "eventAlarm": "Alarmlar",
+ "eventIgnitionOn": "Kontak Açık",
+ "eventIgnitionOff": "Kontak Kapalı",
+ "alarm": "Alarm",
+ "alarmSos": "İmdat Alarmı",
+ "alarmVibration": "Darbe Alarmı",
+ "alarmMovement": "Hareket Alarmı",
+ "alarmOverspeed": "Hız Alarmı",
+ "alarmFallDown": "Düşme Alarmı",
+ "alarmLowBattery": "Batarya Düşük Alarmı",
+ "alarmFault": "Arıza Alarmı",
+ "notificationType": "Bildirim tipi",
+ "notificationWeb": "Wed ile gönder",
+ "notificationMail": "E-posta ile gönder",
+ "reportRoute": "Rota",
+ "reportEvents": "Olaylar",
+ "reportTrips": "Turlar",
+ "reportSummary": "Özet",
+ "reportConfigure": "Ayarlar",
+ "reportEventTypes": "Olay Tipleri",
+ "reportCsv": "CVS",
+ "reportDeviceName": "Cihaz İsmi",
+ "reportAverageSpeed": "Ortalama Hız",
+ "reportMaximumSpeed": "En Fazla Hız",
+ "reportEngineHours": "Motor Saatleri",
+ "reportDuration": "Süre",
+ "reportStartTime": "Başlama Zamanı",
+ "reportStartAddress": "Başlama Adresi",
+ "reportEndTime": "Bittiği Zaman",
+ "reportEndAddress": "Bittiği Adres",
+ "reportSpentFuel": "Tüketilen Yakıt"
+} \ No newline at end of file
diff --git a/web/l10n/uk.json b/web/l10n/uk.json
new file mode 100644
index 0000000..444b192
--- /dev/null
+++ b/web/l10n/uk.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Завантаження... ",
+ "sharedSave": "Зберегти",
+ "sharedCancel": "Відміна",
+ "sharedAdd": "Додати",
+ "sharedEdit": "Редагувати",
+ "sharedRemove": "Видалити",
+ "sharedRemoveConfirm": "Видалити пункт?",
+ "sharedKm": "км",
+ "sharedMi": "Милi",
+ "sharedKn": "Вузли",
+ "sharedKmh": "км/год",
+ "sharedMph": "Миль/год",
+ "sharedHour": "Години",
+ "sharedMinute": "Хвилини",
+ "sharedSecond": "Секунди",
+ "sharedName": "Назва пристрою",
+ "sharedDescription": "Опис",
+ "sharedSearch": "Пошук",
+ "sharedGeofence": "Геозон",
+ "sharedGeofences": "Геозони",
+ "sharedNotifications": "Повідомлення",
+ "sharedAttributes": "Атрибути",
+ "sharedAttribute": "Атрибут",
+ "sharedArea": "Площа",
+ "sharedMute": "Без звуку",
+ "sharedType": "Тип",
+ "sharedDistance": "Відстань",
+ "sharedHourAbbreviation": "г",
+ "sharedMinuteAbbreviation": "хв",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Помилка",
+ "errorUnknown": "Невiдома помилка",
+ "errorConnection": "Помилка з'єднання",
+ "userEmail": "E-mail",
+ "userPassword": "Пароль",
+ "userAdmin": "Адмiнiстратор",
+ "userRemember": "Запам'ятати",
+ "loginTitle": "Логiн",
+ "loginLanguage": "Мова",
+ "loginRegister": "Реєстрація",
+ "loginLogin": "Ввійти",
+ "loginFailed": "Неправильне адреса електронної пошти або пароль",
+ "loginCreated": "Новий користувач був зареєстрований",
+ "loginLogout": "Вийти",
+ "devicesAndState": "Пристрої та стан",
+ "deviceDialog": "Пристрій",
+ "deviceTitle": " Прилади",
+ "deviceIdentifier": "Iдентифікатор",
+ "deviceLastUpdate": "Останнє оновлення",
+ "deviceCommand": "Команда",
+ "deviceFollow": "Слідувати",
+ "groupDialog": "Група",
+ "groupParent": "Група",
+ "groupNoGroup": "Група відсутня",
+ "settingsTitle": "Налаштування",
+ "settingsUser": "Аккаунт",
+ "settingsGroups": "Групи",
+ "settingsServer": "Сервер",
+ "settingsUsers": "Користувачі",
+ "settingsSpeedUnit": "Швидкість",
+ "settingsTwelveHourFormat": "12-годинний формат",
+ "reportTitle": "Звіти",
+ "reportDevice": "Пристрій ",
+ "reportGroup": "Group",
+ "reportFrom": "З",
+ "reportTo": "До",
+ "reportShow": "Показати",
+ "reportClear": "Очистити",
+ "positionFixTime": "Час ",
+ "positionValid": "Дійсний",
+ "positionLatitude": "Широта",
+ "positionLongitude": "Довгота ",
+ "positionAltitude": "Висота",
+ "positionSpeed": "Швидкість ",
+ "positionCourse": "Напрямок",
+ "positionAddress": "Адреса",
+ "positionProtocol": "Протокол",
+ "serverTitle": "Налаштування сервера",
+ "serverZoom": "Наближення",
+ "serverRegistration": "Реєстрація",
+ "serverReadonly": "Лише для читання",
+ "mapTitle": "Карта",
+ "mapLayer": "Використання мап",
+ "mapCustom": "Користувацька мапа",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Ключ Bing Maps ",
+ "mapBingRoad": "Bing Maps Дороги",
+ "mapBingAerial": "Bing Maps Супутник",
+ "mapShapePolygon": "Багатокутник",
+ "mapShapeCircle": "Коло",
+ "stateTitle": "Стан",
+ "stateName": "Атрибут",
+ "stateValue": "Значення ",
+ "commandTitle": "Команда ",
+ "commandSend": "Послати. ",
+ "commandSent": "Команда була відправлена",
+ "commandPositionPeriodic": "Періодична звітність",
+ "commandPositionStop": "Скасувати відстеження. ",
+ "commandEngineStop": "Заблокувати двигун ",
+ "commandEngineResume": "Розблокувати двигун",
+ "commandFrequency": "Частота",
+ "commandUnit": "Одиниці",
+ "commandCustom": "Користувацька команда",
+ "commandPositionSingle": "Разове відстеження",
+ "commandAlarmArm": "Активувати сигналізацію",
+ "commandAlarmDisarm": "Вимкнути сигналізацію",
+ "commandSetTimezone": "Часовий пояс",
+ "commandRequestPhoto": "Запит фото",
+ "commandRebootDevice": "Перезавантаження пристрою",
+ "commandSendSms": "Надсилання SMS",
+ "commandSendUssd": "Надсилання USSD",
+ "commandSosNumber": "Номер SOS",
+ "commandSilenceTime": "Встановити час пиші",
+ "commandSetPhonebook": "Телефонна книга",
+ "commandVoiceMessage": "Голосове повідомлення",
+ "commandOutputControl": "Контроль виходу",
+ "commandAlarmSpeed": "Перевищення швидкості",
+ "commandDeviceIdentification": "Ідентифікація пристрою",
+ "commandIndex": "Індекс",
+ "commandData": "Дані",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Повідомлення",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Пристрій з'єднався",
+ "eventDeviceOffline": "Пристрій від'єднався",
+ "eventDeviceMoving": "Пристрій в русі",
+ "eventDeviceStopped": "Пристрій зупинився",
+ "eventDeviceOverspeed": "Пристрій перевищує швидкість",
+ "eventCommandResult": "Результат команди",
+ "eventGeofenceEnter": "Пристрій в геозоні",
+ "eventGeofenceExit": "Пристрій залишив геозону",
+ "eventAlarm": "Тревоги",
+ "eventIgnitionOn": "Запалення УВІМК",
+ "eventIgnitionOff": "Запалення ВИМК",
+ "alarm": "Тревога",
+ "alarmSos": "Тривога SOS",
+ "alarmVibration": "Тривога вібрації",
+ "alarmMovement": "Тривога сигналізації",
+ "alarmOverspeed": "Тривога перевищення швидкості",
+ "alarmFallDown": "Тривога падіння",
+ "alarmLowBattery": "Тривога низького заряду",
+ "alarmFault": "Тривога несправності",
+ "notificationType": "Тип повідомлення",
+ "notificationWeb": "Повідомляти у Web",
+ "notificationMail": "Надсилати на Пошту",
+ "reportRoute": "Маршрут",
+ "reportEvents": "Події",
+ "reportTrips": "Подорожі",
+ "reportSummary": "Звіт",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Ім'я пристрою",
+ "reportAverageSpeed": "Середня швидкість",
+ "reportMaximumSpeed": "Максимальна швидкість",
+ "reportEngineHours": "Мотогодинник",
+ "reportDuration": "Тривалість",
+ "reportStartTime": "Початковий час",
+ "reportStartAddress": "Початкова адреса",
+ "reportEndTime": "Кінцевий час",
+ "reportEndAddress": "Кінцева адреса",
+ "reportSpentFuel": "Використано палива"
+} \ No newline at end of file
diff --git a/web/l10n/vi.json b/web/l10n/vi.json
new file mode 100644
index 0000000..f344d7a
--- /dev/null
+++ b/web/l10n/vi.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "Đang tải...",
+ "sharedSave": "Lưu",
+ "sharedCancel": "Hủy",
+ "sharedAdd": "Thêm mới",
+ "sharedEdit": "Chỉnh sửa",
+ "sharedRemove": "Xóa",
+ "sharedRemoveConfirm": "Xóa lựa chọn?",
+ "sharedKm": "km",
+ "sharedMi": "dặm",
+ "sharedKn": "kn",
+ "sharedKmh": "km/h",
+ "sharedMph": "mph",
+ "sharedHour": "Giờ",
+ "sharedMinute": "Phút",
+ "sharedSecond": "Giây",
+ "sharedName": "Tên",
+ "sharedDescription": "Mô tả",
+ "sharedSearch": "Tìm kiếm",
+ "sharedGeofence": "Giới hạn địa lý",
+ "sharedGeofences": "Giới hạn địa lý",
+ "sharedNotifications": "Thông báo",
+ "sharedAttributes": "Thuộc tính",
+ "sharedAttribute": "Thuộc tính",
+ "sharedArea": "Khu vực",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "Lỗi",
+ "errorUnknown": "Lỗi không xác định",
+ "errorConnection": "Lỗi kết nối",
+ "userEmail": "Email",
+ "userPassword": "Mật khẩu",
+ "userAdmin": "Quản trị",
+ "userRemember": "Remember",
+ "loginTitle": "Đăng nhập",
+ "loginLanguage": "Ngôn ngữ",
+ "loginRegister": "Đăng ký",
+ "loginLogin": "Đăng nhập",
+ "loginFailed": "Sai mật khẩu hoặc địa chỉ email",
+ "loginCreated": "Người dùng mới đã được đăng ký",
+ "loginLogout": "Đăng xuất",
+ "devicesAndState": "Các thiết bị và trạng thái",
+ "deviceDialog": "Thiết bị",
+ "deviceTitle": "Các thiết bị",
+ "deviceIdentifier": "Định danh",
+ "deviceLastUpdate": "Cập nhật lần cuối",
+ "deviceCommand": "Lệnh",
+ "deviceFollow": "Theo dõi",
+ "groupDialog": "Nhóm",
+ "groupParent": "Nhóm",
+ "groupNoGroup": "Không có nhóm",
+ "settingsTitle": "Cài đặt",
+ "settingsUser": "Tài khoản",
+ "settingsGroups": "Nhóm",
+ "settingsServer": "Máy chủ",
+ "settingsUsers": "Người dùng",
+ "settingsSpeedUnit": "Tốc độ",
+ "settingsTwelveHourFormat": "Định dạng 12h",
+ "reportTitle": "Báo cáo",
+ "reportDevice": "Thiết bị",
+ "reportGroup": "Group",
+ "reportFrom": "Từ",
+ "reportTo": "Đến",
+ "reportShow": "Hiển thị",
+ "reportClear": "Xóa",
+ "positionFixTime": "Thời gian",
+ "positionValid": "Có hiệu lực",
+ "positionLatitude": "Vĩ độ",
+ "positionLongitude": "Kinh độ",
+ "positionAltitude": "Độ cao",
+ "positionSpeed": "Tốc độ",
+ "positionCourse": "Hướng",
+ "positionAddress": "Địa chỉ",
+ "positionProtocol": "Giao thức",
+ "serverTitle": "Cài đặt máy chủ",
+ "serverZoom": "Phóng to",
+ "serverRegistration": "Đăng ký",
+ "serverReadonly": "Chỉ đọc",
+ "mapTitle": "Bản đồ",
+ "mapLayer": "Lớp bản đồ",
+ "mapCustom": "Bản đồ tùy chỉnh",
+ "mapOsm": "Open Street Map",
+ "mapBingKey": "Bing Maps Key",
+ "mapBingRoad": "Bing Maps Road",
+ "mapBingAerial": "Bing Maps Aerial",
+ "mapShapePolygon": "Đa giác",
+ "mapShapeCircle": "Vòng tròn",
+ "stateTitle": "Trạng thái",
+ "stateName": "Thuộc tính",
+ "stateValue": "Giá trị",
+ "commandTitle": "Lệnh",
+ "commandSend": "Gửi",
+ "commandSent": "Lệnh đã được gửi",
+ "commandPositionPeriodic": "Báo cáo định kỳ",
+ "commandPositionStop": "Dừng báo cáo",
+ "commandEngineStop": "Tắt máy",
+ "commandEngineResume": "Bật máy",
+ "commandFrequency": "Tần suất",
+ "commandUnit": "Đơn vị",
+ "commandCustom": "Lệnh tùy chỉnh",
+ "commandPositionSingle": "Báo cáo đơn",
+ "commandAlarmArm": "Báo động cho phép",
+ "commandAlarmDisarm": "Báo động không cho phép",
+ "commandSetTimezone": "Thiết lập múi giờ",
+ "commandRequestPhoto": "Yêu cầu ảnh",
+ "commandRebootDevice": "Khởi động lại thiết bị",
+ "commandSendSms": "Gửi tin nhắn",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Thiết lập số khẩn cấp",
+ "commandSilenceTime": "Thiêt lập giờ im lặng",
+ "commandSetPhonebook": "Thiết lập danh bạ điện thoại",
+ "commandVoiceMessage": "Tin nhắn thoại",
+ "commandOutputControl": "Điều khiển đầu ra",
+ "commandAlarmSpeed": "Báo động quá tốc độ",
+ "commandDeviceIdentification": "Định danh thiết bị",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Thiết bị trực tuyến",
+ "eventDeviceOffline": "Thiết bị ngoại tuyến",
+ "eventDeviceMoving": "Thiết bị đang di chuyển",
+ "eventDeviceStopped": "Thiết bị đã dừng",
+ "eventDeviceOverspeed": "Thiết bị vượt quá tốc độ",
+ "eventCommandResult": "Kết quả lệnh",
+ "eventGeofenceEnter": "Thiết bị đã đi vào giới hạn địa lý",
+ "eventGeofenceExit": "Thiết bị đã thoát khỏi giới hạn địa lý",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Loại thông báo",
+ "notificationWeb": "Gửi từ web",
+ "notificationMail": "Gửi từ mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/l10n/zh.json b/web/l10n/zh.json
new file mode 100644
index 0000000..d183877
--- /dev/null
+++ b/web/l10n/zh.json
@@ -0,0 +1,164 @@
+{
+ "sharedLoading": "加载",
+ "sharedSave": "保存",
+ "sharedCancel": "取消",
+ "sharedAdd": "新建",
+ "sharedEdit": "编辑",
+ "sharedRemove": "移除",
+ "sharedRemoveConfirm": "要移除选项吗?",
+ "sharedKm": "千米",
+ "sharedMi": "海里",
+ "sharedKn": "kn",
+ "sharedKmh": "千米/小时",
+ "sharedMph": "每小时英里数",
+ "sharedHour": "小时",
+ "sharedMinute": "分钟",
+ "sharedSecond": "秒",
+ "sharedName": "Name",
+ "sharedDescription": "Description",
+ "sharedSearch": "Search",
+ "sharedGeofence": "Geofence",
+ "sharedGeofences": "Geofences",
+ "sharedNotifications": "Notifications",
+ "sharedAttributes": "Attributes",
+ "sharedAttribute": "Attribute",
+ "sharedArea": "Area",
+ "sharedMute": "Mute",
+ "sharedType": "Type",
+ "sharedDistance": "Distance",
+ "sharedHourAbbreviation": "h",
+ "sharedMinuteAbbreviation": "m",
+ "sharedGetMapState": "Get Map State",
+ "errorTitle": "错误",
+ "errorUnknown": "未知错误",
+ "errorConnection": "连接错误",
+ "userEmail": "邮箱",
+ "userPassword": "密码",
+ "userAdmin": "管理员",
+ "userRemember": "Remember",
+ "loginTitle": "登录",
+ "loginLanguage": "语言",
+ "loginRegister": "注册",
+ "loginLogin": "登录",
+ "loginFailed": "邮箱地址或密码不对",
+ "loginCreated": "新用户已经被注册了",
+ "loginLogout": "登出",
+ "devicesAndState": "设备和状态",
+ "deviceDialog": "设备",
+ "deviceTitle": "设备",
+ "deviceIdentifier": "标识符",
+ "deviceLastUpdate": "最后更新",
+ "deviceCommand": "指令",
+ "deviceFollow": "遵循",
+ "groupDialog": "Group",
+ "groupParent": "Group",
+ "groupNoGroup": "No Group",
+ "settingsTitle": "设置",
+ "settingsUser": "账户",
+ "settingsGroups": "Groups",
+ "settingsServer": "服务器",
+ "settingsUsers": "用户",
+ "settingsSpeedUnit": "速度",
+ "settingsTwelveHourFormat": "12-hour Format",
+ "reportTitle": "报表",
+ "reportDevice": "设备",
+ "reportGroup": "Group",
+ "reportFrom": "开始",
+ "reportTo": "结束",
+ "reportShow": "显示",
+ "reportClear": "清空",
+ "positionFixTime": "时间",
+ "positionValid": "有效",
+ "positionLatitude": "纬度",
+ "positionLongitude": "经度",
+ "positionAltitude": "海拔",
+ "positionSpeed": "速度",
+ "positionCourse": "航向",
+ "positionAddress": "地址",
+ "positionProtocol": "协议",
+ "serverTitle": "服务器设置",
+ "serverZoom": "缩放",
+ "serverRegistration": "注册",
+ "serverReadonly": "只读",
+ "mapTitle": "地图",
+ "mapLayer": "地图图层",
+ "mapCustom": "自定义地图",
+ "mapOsm": "OpenStreetMap 地图",
+ "mapBingKey": "Bing 旅游重点",
+ "mapBingRoad": "Bing 公路线路地图",
+ "mapBingAerial": "Bing 航测地图",
+ "mapShapePolygon": "Polygon",
+ "mapShapeCircle": "Circle",
+ "stateTitle": "状态",
+ "stateName": "参数",
+ "stateValue": "数值",
+ "commandTitle": "命令",
+ "commandSend": "发送",
+ "commandSent": "命令已发送",
+ "commandPositionPeriodic": "位置获取",
+ "commandPositionStop": "位置停止",
+ "commandEngineStop": "引擎熄火",
+ "commandEngineResume": "引擎启动",
+ "commandFrequency": "频率",
+ "commandUnit": "单位",
+ "commandCustom": "Custom command",
+ "commandPositionSingle": "Single Reporting",
+ "commandAlarmArm": "Arm Alarm",
+ "commandAlarmDisarm": "Disarm Alarm",
+ "commandSetTimezone": "Set Timezone",
+ "commandRequestPhoto": "Request Photo",
+ "commandRebootDevice": "Reboot Device",
+ "commandSendSms": "Send SMS",
+ "commandSendUssd": "Send USSD",
+ "commandSosNumber": "Set SOS Number",
+ "commandSilenceTime": "Set Silence Time",
+ "commandSetPhonebook": "Set Phonebook",
+ "commandVoiceMessage": "Voice Message",
+ "commandOutputControl": "Output Control",
+ "commandAlarmSpeed": "Overspeed Alarm",
+ "commandDeviceIdentification": "Device Identification",
+ "commandIndex": "Index",
+ "commandData": "Data",
+ "commandPhone": "Phone Number",
+ "commandMessage": "Message",
+ "eventAll": "All Events",
+ "eventDeviceOnline": "Device is online",
+ "eventDeviceOffline": "Device is offline",
+ "eventDeviceMoving": "Device is moving",
+ "eventDeviceStopped": "Device is stopped",
+ "eventDeviceOverspeed": "Device exceeds the speed",
+ "eventCommandResult": "Command result",
+ "eventGeofenceEnter": "Device has entered geofence",
+ "eventGeofenceExit": "Device has exited geofence",
+ "eventAlarm": "Alarms",
+ "eventIgnitionOn": "Ignition is ON",
+ "eventIgnitionOff": "Ignition is OFF",
+ "alarm": "Alarm",
+ "alarmSos": "SOS Alarm",
+ "alarmVibration": "Vibration Alarm",
+ "alarmMovement": "Movement Alarm",
+ "alarmOverspeed": "Overspeed Alarm",
+ "alarmFallDown": "FallDown Alarm",
+ "alarmLowBattery": "LowBattery Alarm",
+ "alarmFault": "Fault Alarm",
+ "notificationType": "Type of Notification",
+ "notificationWeb": "Send via Web",
+ "notificationMail": "Send via Mail",
+ "reportRoute": "Route",
+ "reportEvents": "Events",
+ "reportTrips": "Trips",
+ "reportSummary": "Summary",
+ "reportConfigure": "Configure",
+ "reportEventTypes": "Event Types",
+ "reportCsv": "CSV",
+ "reportDeviceName": "Device Name",
+ "reportAverageSpeed": "Average Speed",
+ "reportMaximumSpeed": "Maximum Speed",
+ "reportEngineHours": "Engine Hours",
+ "reportDuration": "Duration",
+ "reportStartTime": "Start Time",
+ "reportStartAddress": "Start Address",
+ "reportEndTime": "End Time",
+ "reportEndAddress": "End Address",
+ "reportSpentFuel": "Spent Fuel"
+} \ No newline at end of file
diff --git a/web/locale.js b/web/locale.js
new file mode 100644
index 0000000..456b40b
--- /dev/null
+++ b/web/locale.js
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+var Locale = {};
+
+Locale.languages = {
+ 'ar': { name: 'العربية', code: 'en' },
+ 'bg': { name: 'Български', code: 'bg' },
+ 'cs': { name: 'Čeština', code: 'cs' },
+ 'de': { name: 'Deutsch', code: 'de' },
+ 'da': { name: 'Dansk', code: 'da' },
+ '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' },
+ 'he': { name: 'עברית', code: 'he' },
+ 'hi': { name: 'हिन्दी', code: 'en' },
+ 'hu': { name: 'Magyar', code: 'hu' },
+ 'id': { name: 'Bahasa Indonesia', code: 'id' },
+ 'it': { name: 'Italiano', code: 'it' },
+ 'ka': { name: 'ქართული', code: 'en' },
+ 'lo': { name: 'ລາວ', code: 'en' },
+ 'lt': { name: 'Lietuvių', code: 'lt' },
+ 'ml': { name: 'മലയാളം', code: 'en' },
+ 'ms': { name: 'بهاس ملايو', code: 'en' },
+ 'nb': { name: 'Norsk bokmål', code: 'no_NB' },
+ 'ne': { name: 'नेपाली', code: 'en' },
+ 'nl': { name: 'Nederlands', code: 'nl' },
+ 'nn': { name: 'Norsk nynorsk', code: 'no_NN' },
+ 'pl': { name: 'Polski', code: 'pl' },
+ 'pt': { name: 'Português', code: 'pt' },
+ 'pt_BR': { name: 'Português (Brasil)', code: 'pt_BR' },
+ 'ro': { name: 'Română', code: 'ro' },
+ 'ru': { name: 'Русский', code: 'ru' },
+ 'si': { name: 'සිංහල', code: 'en' },
+ 'sk': { name: 'Slovenčina', code: 'sk' },
+ 'sl': { name: 'Slovenščina', code: 'sl' },
+ 'sq': { name: 'Shqipëria', code: 'en' },
+ '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' }
+};
+
+Locale.language = Ext.Object.fromQueryString(window.location.search.substring(1)).locale;
+if (Locale.language === undefined) {
+ Locale.language = window.navigator.userLanguage || window.navigator.language;
+ Locale.language = Locale.language.substr(0, 2);
+}
+
+if (!(Locale.language in Locale.languages)) {
+ Locale.language = 'en'; // default
+}
+
+Ext.Ajax.request({
+ url: 'l10n/' + Locale.language + '.json',
+ callback: function (options, success, response) {
+ Strings = Ext.decode(response.responseText);
+ }
+});
+
+Ext.Loader.loadScript('//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/locale/locale-' + Locale.languages[Locale.language].code + '.js');
diff --git a/web/release.html b/web/release.html
new file mode 100644
index 0000000..3c9d9e1
--- /dev/null
+++ b/web/release.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<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.1/classic/theme-neptune/resources/theme-neptune-all.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.18.2/ol.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css">
+<link rel="stylesheet" href="app.css">
+</head>
+<body>
+<div id="spinner"></div>
+<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.1/ext-all.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.1/classic/theme-neptune/theme-neptune.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.18.2/ol-debug.js"></script>
+<script src="arrow.js"></script>
+<script src="locale.js"></script>
+<script type="text/javascript">
+Ext.Loader.loadScript('app.min.js');
+</script>
+</body>
+</html>