aboutsummaryrefslogtreecommitdiff
path: root/src/org
diff options
context:
space:
mode:
Diffstat (limited to 'src/org')
-rw-r--r--src/org/traccar/web/Traccar.gwt.xml17
-rw-r--r--src/org/traccar/web/client/Application.java98
-rw-r--r--src/org/traccar/web/client/Traccar.java19
-rw-r--r--src/org/traccar/web/client/controller/ArchiveController.java44
-rw-r--r--src/org/traccar/web/client/controller/ContentController.java11
-rw-r--r--src/org/traccar/web/client/controller/DeviceController.java109
-rw-r--r--src/org/traccar/web/client/controller/LoginController.java81
-rw-r--r--src/org/traccar/web/client/controller/MapController.java56
-rw-r--r--src/org/traccar/web/client/model/BaseAsyncCallback.java17
-rw-r--r--src/org/traccar/web/client/model/DataService.java33
-rw-r--r--src/org/traccar/web/client/model/DataServiceAsync.java31
-rw-r--r--src/org/traccar/web/client/model/DeviceProperties.java17
-rw-r--r--src/org/traccar/web/client/model/PositionProperties.java33
-rw-r--r--src/org/traccar/web/client/view/ApplicationView.java33
-rw-r--r--src/org/traccar/web/client/view/ApplicationView.ui.xml42
-rw-r--r--src/org/traccar/web/client/view/ArchiveView.java79
-rw-r--r--src/org/traccar/web/client/view/ArchiveView.ui.xml60
-rw-r--r--src/org/traccar/web/client/view/DeviceDialog.java69
-rw-r--r--src/org/traccar/web/client/view/DeviceDialog.ui.xml40
-rw-r--r--src/org/traccar/web/client/view/DeviceView.java128
-rw-r--r--src/org/traccar/web/client/view/DeviceView.ui.xml46
-rw-r--r--src/org/traccar/web/client/view/LoginDialog.java59
-rw-r--r--src/org/traccar/web/client/view/LoginDialog.ui.xml40
-rw-r--r--src/org/traccar/web/client/view/MapView.java149
-rw-r--r--src/org/traccar/web/server/model/DataServiceImpl.java172
-rw-r--r--src/org/traccar/web/shared/model/Device.java56
-rw-r--r--src/org/traccar/web/shared/model/Position.java115
-rw-r--r--src/org/traccar/web/shared/model/User.java65
28 files changed, 1719 insertions, 0 deletions
diff --git a/src/org/traccar/web/Traccar.gwt.xml b/src/org/traccar/web/Traccar.gwt.xml
new file mode 100644
index 00000000..00813723
--- /dev/null
+++ b/src/org/traccar/web/Traccar.gwt.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.5.0//EN"
+ "http://google-web-toolkit.googlecode.com/svn/tags/2.5.0/distro-source/core/src/gwt-module.dtd">
+
+<module rename-to='traccar'>
+
+ <inherits name='com.google.gwt.user.User'/>
+ <inherits name='com.sencha.gxt.ui.GXT'/>
+ <inherits name='org.gwtopenmaps.openlayers.OpenLayers' />
+
+ <entry-point class='org.traccar.web.client.Traccar'/>
+
+ <source path='client'/>
+ <source path='shared'/>
+
+</module>
diff --git a/src/org/traccar/web/client/Application.java b/src/org/traccar/web/client/Application.java
new file mode 100644
index 00000000..9b14d421
--- /dev/null
+++ b/src/org/traccar/web/client/Application.java
@@ -0,0 +1,98 @@
+package org.traccar.web.client;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.traccar.web.client.controller.ArchiveController;
+import org.traccar.web.client.controller.DeviceController;
+import org.traccar.web.client.controller.MapController;
+import org.traccar.web.client.model.DataService;
+import org.traccar.web.client.model.DataServiceAsync;
+import org.traccar.web.client.view.ApplicationView;
+import org.traccar.web.shared.model.Device;
+import org.traccar.web.shared.model.Position;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.ui.RootPanel;
+
+public class Application {
+
+ private static final DataServiceAsync dataService = GWT.create(DataService.class);
+
+ public static DataServiceAsync getDataService() {
+ return dataService;
+ }
+
+ private final DeviceController deviceController;
+ private final MapController mapController;
+ private final ArchiveController archiveController;
+
+ private ApplicationView view;
+
+ public Application() {
+ deviceController = new DeviceController(deviceHandler);
+ mapController = new MapController();
+ archiveController = new ArchiveController(archiveHanlder);
+
+ view = new ApplicationView(
+ deviceController.getView(), mapController.getView(), archiveController.getView());
+ }
+
+ public void run() {
+ RootPanel.get().add(view);
+
+ deviceController.run();
+ mapController.run();
+ archiveController.run();
+ }
+
+ private Map<Long, Device> devices = new HashMap<Long, Device>();
+
+ private DeviceController.DeviceHandler deviceHandler = new DeviceController.DeviceHandler() {
+
+ @Override
+ public void onLoad(List<Device> devices) {
+ Application.this.devices.clear();
+ for (Device device : devices) {
+ Application.this.devices.put(device.getId(), device);
+ }
+ }
+
+ @Override
+ public void onSelected(Device device) {
+ mapController.select(device);
+ }
+
+ @Override
+ public void onAdd(Device device) {
+ devices.put(device.getId(), device);
+ archiveController.updateDevices(devices.values());
+ mapController.update();
+ }
+
+ @Override
+ public void onUpdate(Device device) {
+ devices.put(device.getId(), device);
+ archiveController.updateDevices(devices.values());
+ }
+
+ @Override
+ public void onRemove(Device device) {
+ devices.remove(device.getId());
+ archiveController.updateDevices(devices.values());
+ mapController.update();
+ }
+
+ };
+
+ private ArchiveController.ArchiveHandler archiveHanlder = new ArchiveController.ArchiveHandler() {
+
+ @Override
+ public void onSelected(Position position) {
+ // TODO select something on map?
+ }
+
+ };
+
+}
diff --git a/src/org/traccar/web/client/Traccar.java b/src/org/traccar/web/client/Traccar.java
new file mode 100644
index 00000000..5d56d402
--- /dev/null
+++ b/src/org/traccar/web/client/Traccar.java
@@ -0,0 +1,19 @@
+package org.traccar.web.client;
+
+import org.traccar.web.client.controller.LoginController;
+
+import com.google.gwt.core.client.EntryPoint;
+
+public class Traccar implements EntryPoint, LoginController.LoginHandler {
+
+ @Override
+ public void onModuleLoad() {
+ new LoginController().login(this);
+ }
+
+ @Override
+ public void onLogin() {
+ new Application().run();
+ }
+
+}
diff --git a/src/org/traccar/web/client/controller/ArchiveController.java b/src/org/traccar/web/client/controller/ArchiveController.java
new file mode 100644
index 00000000..598a744d
--- /dev/null
+++ b/src/org/traccar/web/client/controller/ArchiveController.java
@@ -0,0 +1,44 @@
+package org.traccar.web.client.controller;
+
+import java.util.Collection;
+
+import org.traccar.web.client.view.ArchiveView;
+import org.traccar.web.shared.model.Device;
+import org.traccar.web.shared.model.Position;
+
+import com.sencha.gxt.widget.core.client.ContentPanel;
+
+public class ArchiveController implements ContentController, ArchiveView.ArchiveHandler {
+
+ public interface ArchiveHandler {
+ public void onSelected(Position position);
+ }
+
+ private ArchiveHandler archiveHandler;
+
+ private ArchiveView archiveView;
+
+ public ArchiveController(ArchiveHandler archiveHandler) {
+ this.archiveHandler = archiveHandler;
+ archiveView = new ArchiveView(this);
+ }
+
+ @Override
+ public ContentPanel getView() {
+ return archiveView.getView();
+ }
+
+ @Override
+ public void run() {
+ }
+
+ public void updateDevices(Collection<Device> devices) {
+
+ }
+
+ @Override
+ public void onSelected(Position position) {
+ archiveHandler.onSelected(position);
+ }
+
+}
diff --git a/src/org/traccar/web/client/controller/ContentController.java b/src/org/traccar/web/client/controller/ContentController.java
new file mode 100644
index 00000000..7e8d6778
--- /dev/null
+++ b/src/org/traccar/web/client/controller/ContentController.java
@@ -0,0 +1,11 @@
+package org.traccar.web.client.controller;
+
+import com.sencha.gxt.widget.core.client.ContentPanel;
+
+public interface ContentController {
+
+ public ContentPanel getView();
+
+ public void run();
+
+}
diff --git a/src/org/traccar/web/client/controller/DeviceController.java b/src/org/traccar/web/client/controller/DeviceController.java
new file mode 100644
index 00000000..b24c205e
--- /dev/null
+++ b/src/org/traccar/web/client/controller/DeviceController.java
@@ -0,0 +1,109 @@
+package org.traccar.web.client.controller;
+
+import java.util.List;
+
+import org.traccar.web.client.Application;
+import org.traccar.web.client.model.BaseAsyncCallback;
+import org.traccar.web.client.view.DeviceDialog;
+import org.traccar.web.client.view.DeviceView;
+import org.traccar.web.shared.model.Device;
+
+import com.sencha.gxt.widget.core.client.ContentPanel;
+import com.sencha.gxt.widget.core.client.Dialog.PredefinedButton;
+import com.sencha.gxt.widget.core.client.box.ConfirmMessageBox;
+import com.sencha.gxt.widget.core.client.event.HideEvent;
+import com.sencha.gxt.widget.core.client.event.HideEvent.HideHandler;
+
+public class DeviceController implements ContentController, DeviceView.DeviceHandler {
+
+ public interface DeviceHandler {
+ public void onLoad(List<Device> devices);
+ public void onSelected(Device device);
+ public void onAdd(Device device);
+ public void onUpdate(Device device);
+ public void onRemove(Device device);
+ }
+
+ private DeviceHandler deviceHandler;
+
+ private DeviceView deviceView;
+
+ public DeviceController(DeviceHandler deviceHandler) {
+ this.deviceHandler = deviceHandler;
+ deviceView = new DeviceView(this);
+ }
+
+ @Override
+ public ContentPanel getView() {
+ return deviceView.getView();
+ }
+
+ @Override
+ public void run() {
+ Application.getDataService().getDevices(new BaseAsyncCallback<List<Device>>() {
+ @Override
+ public void onSuccess(List<Device> result) {
+ deviceView.load(result);
+ deviceHandler.onLoad(result);
+ }
+ });
+ }
+
+ @Override
+ public void onSelected(Device device) {
+ deviceHandler.onSelected(device);
+ }
+
+ @Override
+ public void onAdd() {
+ new DeviceDialog(new Device(), new DeviceDialog.DeviceHandler() {
+ @Override
+ public void onSave(Device device) {
+ Application.getDataService().addDevice(device, new BaseAsyncCallback<Device>() {
+ @Override
+ public void onSuccess(Device result) {
+ deviceView.add(result);
+ deviceHandler.onAdd(result);
+ }
+ });
+ }
+ }).show();
+ }
+
+ @Override
+ public void onEdit(Device device) {
+ new DeviceDialog(new Device(device), new DeviceDialog.DeviceHandler() {
+ @Override
+ public void onSave(Device device) {
+ Application.getDataService().updateDevice(device, new BaseAsyncCallback<Device>() {
+ @Override
+ public void onSuccess(Device result) {
+ deviceView.update(result);
+ deviceHandler.onUpdate(result);
+ }
+ });
+ }
+ }).show();
+ }
+
+ @Override
+ public void onRemove(final Device device) {
+ final ConfirmMessageBox dialog = new ConfirmMessageBox("Confirm", "Are you sure you want remove device?");
+ dialog.addHideHandler(new HideHandler() {
+ @Override
+ public void onHide(HideEvent event) {
+ if (dialog.getHideButton() == dialog.getButtonById(PredefinedButton.YES.name())) {
+ Application.getDataService().removeDevice(device, new BaseAsyncCallback<Device>() {
+ @Override
+ public void onSuccess(Device result) {
+ deviceView.remove(device);
+ deviceHandler.onRemove(device);
+ }
+ });
+ }
+ }
+ });
+ dialog.show();
+ }
+
+}
diff --git a/src/org/traccar/web/client/controller/LoginController.java b/src/org/traccar/web/client/controller/LoginController.java
new file mode 100644
index 00000000..ceda85cf
--- /dev/null
+++ b/src/org/traccar/web/client/controller/LoginController.java
@@ -0,0 +1,81 @@
+package org.traccar.web.client.controller;
+
+import org.traccar.web.client.Application;
+import org.traccar.web.client.model.BaseAsyncCallback;
+import org.traccar.web.client.view.LoginDialog;
+
+import com.sencha.gxt.widget.core.client.box.AlertMessageBox;
+
+public class LoginController implements LoginDialog.LoginHandler {
+
+ private LoginDialog dialog;
+
+ public interface LoginHandler {
+ public void onLogin();
+ }
+
+ private LoginHandler loginHandler;
+
+ public void login(final LoginHandler loginHandler) {
+ this.loginHandler = loginHandler;
+
+ Application.getDataService().authenticated(new BaseAsyncCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ if (result) {
+ loginHandler.onLogin();
+ } else {
+ dialog = new LoginDialog(LoginController.this);
+ dialog.show();
+ }
+ }
+ });
+ }
+
+ private boolean validate(String login, String password) {
+ if (login == null || login.isEmpty() || password == null || password.isEmpty()) {
+ new AlertMessageBox("Error", "User name and password must not be empty").show();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onLogin(String login, String password) {
+ if (validate(login, password)) {
+ Application.getDataService().authenticate(login, password, new BaseAsyncCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ if (result) {
+ if (loginHandler != null) {
+ dialog.hide();
+ loginHandler.onLogin();
+ }
+ } else {
+ new AlertMessageBox("Error", "User name or password is invalid").show();
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onRegister(String login, String password) {
+ if (validate(login, password)) {
+ Application.getDataService().register(login, password, new BaseAsyncCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ if (result) {
+ if (loginHandler != null) {
+ dialog.hide();
+ loginHandler.onLogin();
+ }
+ } else {
+ new AlertMessageBox("Error", "Registration error").show();
+ }
+ }
+ });
+ }
+ }
+
+}
diff --git a/src/org/traccar/web/client/controller/MapController.java b/src/org/traccar/web/client/controller/MapController.java
new file mode 100644
index 00000000..5a53cf22
--- /dev/null
+++ b/src/org/traccar/web/client/controller/MapController.java
@@ -0,0 +1,56 @@
+package org.traccar.web.client.controller;
+
+import org.traccar.web.client.view.MapView;
+import org.traccar.web.shared.model.Device;
+
+import com.google.gwt.user.client.Timer;
+import com.sencha.gxt.widget.core.client.ContentPanel;
+
+public class MapController implements ContentController {
+
+ private static final int UPDATE_INTERVAL = 10000;
+
+ private MapView mapView;
+
+ public MapController() {
+ mapView = new MapView();
+ }
+
+ @Override
+ public ContentPanel getView() {
+ return mapView.getView();
+ }
+
+ private Timer updateTimer;
+
+ @Override
+ public void run() {
+ updateTimer = new Timer() {
+ @Override
+ public void run() {
+ update();
+ }
+ };
+ update();
+ }
+
+ public void update() {
+ updateTimer.cancel();
+ /*Application.getDataService().getLatestPositions(new AsyncCallback<List<Position>>() {
+ @Override
+ public void onSuccess(List<Position> result) {
+ mapView.showPositions(result);
+ updateTimer.schedule(UPDATE_INTERVAL);
+ }
+ @Override
+ public void onFailure(Throwable caught) {
+ updateTimer.schedule(UPDATE_INTERVAL);
+ }
+ });*/
+ }
+
+ public void select(Device device) {
+ mapView.select(device, true);
+ }
+
+}
diff --git a/src/org/traccar/web/client/model/BaseAsyncCallback.java b/src/org/traccar/web/client/model/BaseAsyncCallback.java
new file mode 100644
index 00000000..2cc8741b
--- /dev/null
+++ b/src/org/traccar/web/client/model/BaseAsyncCallback.java
@@ -0,0 +1,17 @@
+package org.traccar.web.client.model;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.sencha.gxt.widget.core.client.box.AlertMessageBox;
+
+public class BaseAsyncCallback<T> implements AsyncCallback<T> {
+
+ @Override
+ public void onFailure(Throwable caught) {
+ new AlertMessageBox("Error", "Remote procedure call error").show();
+ }
+
+ @Override
+ public void onSuccess(T result) {
+ }
+
+}
diff --git a/src/org/traccar/web/client/model/DataService.java b/src/org/traccar/web/client/model/DataService.java
new file mode 100644
index 00000000..d8d71520
--- /dev/null
+++ b/src/org/traccar/web/client/model/DataService.java
@@ -0,0 +1,33 @@
+package org.traccar.web.client.model;
+
+import java.util.Date;
+import java.util.List;
+
+import org.traccar.web.shared.model.Device;
+import org.traccar.web.shared.model.Position;
+
+import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
+
+@RemoteServiceRelativePath("dataService")
+public interface DataService extends RemoteService {
+
+ boolean authenticated();
+
+ boolean authenticate(String login, String password);
+
+ boolean register(String login, String password);
+
+ List<Device> getDevices();
+
+ Device addDevice(Device device);
+
+ Device updateDevice(Device device);
+
+ Device removeDevice(Device device);
+
+ List<Position> getPositions(Device device, Date from, Date to);
+
+ List<Position> getLatestPositions();
+
+}
diff --git a/src/org/traccar/web/client/model/DataServiceAsync.java b/src/org/traccar/web/client/model/DataServiceAsync.java
new file mode 100644
index 00000000..357beb5d
--- /dev/null
+++ b/src/org/traccar/web/client/model/DataServiceAsync.java
@@ -0,0 +1,31 @@
+package org.traccar.web.client.model;
+
+import java.util.Date;
+import java.util.List;
+
+import org.traccar.web.shared.model.Device;
+import org.traccar.web.shared.model.Position;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public interface DataServiceAsync {
+
+ void authenticate(String login, String password, AsyncCallback<Boolean> callback);
+
+ void authenticated(AsyncCallback<Boolean> callback);
+
+ void register(String login, String password, AsyncCallback<Boolean> callback);
+
+ void getDevices(AsyncCallback<List<Device>> callback);
+
+ void addDevice(Device device, AsyncCallback<Device> callback);
+
+ void updateDevice(Device device, AsyncCallback<Device> callback);
+
+ void removeDevice(Device device, AsyncCallback<Device> callback);
+
+ void getLatestPositions(AsyncCallback<List<Position>> callback);
+
+ void getPositions(Device device, Date from, Date to, AsyncCallback<List<Position>> callback);
+
+}
diff --git a/src/org/traccar/web/client/model/DeviceProperties.java b/src/org/traccar/web/client/model/DeviceProperties.java
new file mode 100644
index 00000000..2c56ff6d
--- /dev/null
+++ b/src/org/traccar/web/client/model/DeviceProperties.java
@@ -0,0 +1,17 @@
+package org.traccar.web.client.model;
+
+import org.traccar.web.shared.model.Device;
+
+import com.sencha.gxt.core.client.ValueProvider;
+import com.sencha.gxt.data.shared.ModelKeyProvider;
+import com.sencha.gxt.data.shared.PropertyAccess;
+
+public interface DeviceProperties extends PropertyAccess<Device> {
+
+ ModelKeyProvider<Device> id();
+
+ ValueProvider<Device, String> uniqueId();
+
+ ValueProvider<Device, String> name();
+
+}
diff --git a/src/org/traccar/web/client/model/PositionProperties.java b/src/org/traccar/web/client/model/PositionProperties.java
new file mode 100644
index 00000000..111614a3
--- /dev/null
+++ b/src/org/traccar/web/client/model/PositionProperties.java
@@ -0,0 +1,33 @@
+package org.traccar.web.client.model;
+
+import java.util.Date;
+
+import org.traccar.web.shared.model.Position;
+
+import com.sencha.gxt.core.client.ValueProvider;
+import com.sencha.gxt.data.shared.ModelKeyProvider;
+import com.sencha.gxt.data.shared.PropertyAccess;
+
+public interface PositionProperties extends PropertyAccess<Position> {
+
+ ModelKeyProvider<Position> id();
+
+ ValueProvider<Position, Date> time();
+
+ ValueProvider<Position, Boolean> valid();
+
+ ValueProvider<Position, Double> latitude();
+
+ ValueProvider<Position, Double> longitude();
+
+ ValueProvider<Position, Double> altitude();
+
+ ValueProvider<Position, Double> speed();
+
+ ValueProvider<Position, Double> course();
+
+ ValueProvider<Position, Double> power();
+
+ ValueProvider<Position, String> address();
+
+}
diff --git a/src/org/traccar/web/client/view/ApplicationView.java b/src/org/traccar/web/client/view/ApplicationView.java
new file mode 100644
index 00000000..bff8854f
--- /dev/null
+++ b/src/org/traccar/web/client/view/ApplicationView.java
@@ -0,0 +1,33 @@
+package org.traccar.web.client.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+import com.sencha.gxt.widget.core.client.ContentPanel;
+
+public class ApplicationView extends Composite {
+
+ private static ApplicationViewUiBinder uiBinder = GWT.create(ApplicationViewUiBinder.class);
+
+ interface ApplicationViewUiBinder extends UiBinder<Widget, ApplicationView> {
+ }
+
+ @UiField(provided = true)
+ ContentPanel devicePanel;
+
+ @UiField(provided = true)
+ ContentPanel mapPanel;
+
+ @UiField(provided = true)
+ ContentPanel archivePanel;
+
+ public ApplicationView(ContentPanel deviceView, ContentPanel mapView, ContentPanel archiveView) {
+ devicePanel = deviceView;
+ mapPanel = mapView;
+ archivePanel = archiveView;
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+}
diff --git a/src/org/traccar/web/client/view/ApplicationView.ui.xml b/src/org/traccar/web/client/view/ApplicationView.ui.xml
new file mode 100644
index 00000000..2faae1e7
--- /dev/null
+++ b/src/org/traccar/web/client/view/ApplicationView.ui.xml
@@ -0,0 +1,42 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+ xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui"
+ xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
+ xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client">
+
+ <ui:with type="com.sencha.gxt.core.client.util.Margins" field="westMargins">
+ <ui:attributes top="5" right="5" bottom="0" left="5" />
+ </ui:with>
+ <ui:with type="com.sencha.gxt.core.client.util.Margins" field="centerMargins">
+ <ui:attributes top="5" right="5" bottom="0" left="0" />
+ </ui:with>
+ <ui:with type="com.sencha.gxt.core.client.util.Margins" field="southMargins">
+ <ui:attributes top="5" right="5" bottom="5" left="5" />
+ </ui:with>
+
+ <ui:with type="com.sencha.gxt.widget.core.client.container.BorderLayoutContainer.BorderLayoutData" field="westData">
+ <ui:attributes size="0.15" maxSize="2147483647" margins="{westMargins}" split="true" />
+ </ui:with>
+ <ui:with type="com.sencha.gxt.widget.core.client.container.MarginData" field="centerData">
+ <ui:attributes margins="{centerMargins}" />
+ </ui:with>
+ <ui:with type="com.sencha.gxt.widget.core.client.container.BorderLayoutContainer.BorderLayoutData" field="southData">
+ <ui:attributes size="0.25" maxSize="2147483647" margins="{southMargins}" split="true" />
+ </ui:with>
+
+ <container:Viewport>
+ <container:BorderLayoutContainer>
+ <container:west layoutData="{westData}">
+ <gxt:ContentPanel ui:field="devicePanel" />
+ </container:west>
+ <container:center layoutData="{centerData}">
+ <gxt:ContentPanel ui:field="mapPanel" />
+ </container:center>
+ <container:south layoutData="{southData}">
+ <gxt:ContentPanel ui:field="archivePanel" />
+ </container:south>
+ </container:BorderLayoutContainer>
+ </container:Viewport>
+
+</ui:UiBinder>
diff --git a/src/org/traccar/web/client/view/ArchiveView.java b/src/org/traccar/web/client/view/ArchiveView.java
new file mode 100644
index 00000000..79621ba9
--- /dev/null
+++ b/src/org/traccar/web/client/view/ArchiveView.java
@@ -0,0 +1,79 @@
+package org.traccar.web.client.view;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.traccar.web.client.model.PositionProperties;
+import org.traccar.web.shared.model.Position;
+
+import com.google.gwt.cell.client.DateCell;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+import com.sencha.gxt.data.shared.ListStore;
+import com.sencha.gxt.widget.core.client.ContentPanel;
+import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
+import com.sencha.gxt.widget.core.client.grid.ColumnModel;
+import com.sencha.gxt.widget.core.client.grid.Grid;
+
+public class ArchiveView extends Composite {
+
+ private static ArchiveViewUiBinder uiBinder = GWT.create(ArchiveViewUiBinder.class);
+
+ interface ArchiveViewUiBinder extends UiBinder<Widget, ArchiveView> {
+ }
+
+ public interface ArchiveHandler {
+ public void onSelected(Position position);
+ }
+
+ private ArchiveHandler archiveHandler;
+
+ @UiField
+ ContentPanel contentPanel;
+
+ public ContentPanel getView() {
+ return contentPanel;
+ }
+
+ @UiField(provided = true)
+ ColumnModel<Position> columnModel;
+
+ @UiField(provided = true)
+ ListStore<Position> store;
+
+ @UiField
+ Grid<Position> grid;
+
+ public ArchiveView(ArchiveHandler archiveHandler) {
+ this.archiveHandler = archiveHandler;
+
+ PositionProperties positionProperties = GWT.create(PositionProperties.class);
+
+ List<ColumnConfig<Position, ?>> columnConfigList = new LinkedList<ColumnConfig<Position, ?>>();
+
+ columnConfigList.add(new ColumnConfig<Position, Boolean>(positionProperties.valid(), 0, "Valid"));
+
+ ColumnConfig<Position, Date> columnConfig = new ColumnConfig<Position, Date>(positionProperties.time(), 0, "Time");
+ columnConfig.setCell(new DateCell(DateTimeFormat.getFormat("yyyy-MM-dd HH:mm:ss")));
+ columnConfigList.add(columnConfig);
+
+ columnConfigList.add(new ColumnConfig<Position, Double>(positionProperties.latitude(), 0, "Latitude"));
+ columnConfigList.add(new ColumnConfig<Position, Double>(positionProperties.longitude(), 0, "Longitude"));
+ columnConfigList.add(new ColumnConfig<Position, Double>(positionProperties.altitude(), 0, "Altitude"));
+ columnConfigList.add(new ColumnConfig<Position, Double>(positionProperties.speed(), 0, "Speed"));
+ columnConfigList.add(new ColumnConfig<Position, Double>(positionProperties.course(), 0, "Course"));
+ columnConfigList.add(new ColumnConfig<Position, Double>(positionProperties.power(), 0, "Power"));
+
+ columnModel = new ColumnModel<Position>(columnConfigList);
+
+ store = new ListStore<Position>(positionProperties.id());
+
+ uiBinder.createAndBindUi(this);
+ }
+
+}
diff --git a/src/org/traccar/web/client/view/ArchiveView.ui.xml b/src/org/traccar/web/client/view/ArchiveView.ui.xml
new file mode 100644
index 00000000..370eb024
--- /dev/null
+++ b/src/org/traccar/web/client/view/ArchiveView.ui.xml
@@ -0,0 +1,60 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+ xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui"
+ xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
+ xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
+ xmlns:toolbar="urn:import:com.sencha.gxt.widget.core.client.toolbar"
+ xmlns:grid="urn:import:com.sencha.gxt.widget.core.client.grid"
+ xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button"
+ xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form">
+
+ <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="toolBarRowData">
+ <ui:attributes width="1" height="-1" />
+ </ui:with>
+ <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="layoutData">
+ <ui:attributes width="1" height="1" />
+ </ui:with>
+
+ <ui:with type="com.sencha.gxt.data.shared.ListStore" field="store" />
+ <ui:with type="com.sencha.gxt.widget.core.client.grid.ColumnModel" field="columnModel" />
+
+ <ui:with type="com.sencha.gxt.widget.core.client.grid.GridView" field="view">
+ <ui:attributes stripeRows="true" autoFill="true" />
+ </ui:with>
+
+ <gxt:ContentPanel ui:field="contentPanel" headingText="Archive">
+ <container:VerticalLayoutContainer>
+
+ <container:child layoutData="{toolBarRowData}">
+ <toolbar:ToolBar>
+
+ <toolbar:LabelToolItem label="Device:" />
+ <form:TimeField /> <!-- put combobox here -->
+ <toolbar:SeparatorToolItem />
+
+ <toolbar:LabelToolItem label="From:" />
+ <form:DateField width="125" />
+ <toolbar:LabelToolItem width="5" />
+ <form:TimeField width="75" />
+ <toolbar:SeparatorToolItem />
+
+ <toolbar:LabelToolItem label="To:" />
+ <form:DateField width="125" />
+ <toolbar:LabelToolItem width="5" />
+ <form:TimeField width="75" />
+ <toolbar:SeparatorToolItem />
+
+ <button:TextButton ui:field="loadButton" text="Load" />
+
+ </toolbar:ToolBar>
+ </container:child>
+
+ <container:child layoutData="{layoutData}">
+ <grid:Grid ui:field="grid" store="{store}" cm="{columnModel}" view="{view}" />
+ </container:child>
+
+ </container:VerticalLayoutContainer>
+ </gxt:ContentPanel>
+
+</ui:UiBinder>
diff --git a/src/org/traccar/web/client/view/DeviceDialog.java b/src/org/traccar/web/client/view/DeviceDialog.java
new file mode 100644
index 00000000..891dd4ef
--- /dev/null
+++ b/src/org/traccar/web/client/view/DeviceDialog.java
@@ -0,0 +1,69 @@
+package org.traccar.web.client.view;
+
+import org.traccar.web.shared.model.Device;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.SimpleBeanEditorDriver;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Widget;
+import com.sencha.gxt.widget.core.client.Window;
+import com.sencha.gxt.widget.core.client.event.SelectEvent;
+import com.sencha.gxt.widget.core.client.form.TextField;
+
+public class DeviceDialog implements Editor<Device> {
+
+ private static DeviceDialogUiBinder uiBinder = GWT.create(DeviceDialogUiBinder.class);
+
+ interface DeviceDialogUiBinder extends UiBinder<Widget, DeviceDialog> {
+ }
+
+ private DeviceDriver driver = GWT.create(DeviceDriver.class);
+
+ interface DeviceDriver extends SimpleBeanEditorDriver<Device, DeviceDialog> {
+ }
+
+ public interface DeviceHandler {
+ public void onSave(Device device);
+ }
+
+ private DeviceHandler deviceHandler;
+
+ @UiField
+ Window window;
+
+ @UiField
+ TextField name;
+
+ @UiField
+ TextField uniqueId;
+
+ public DeviceDialog(Device device, DeviceHandler deviceHandler) {
+ this.deviceHandler = deviceHandler;
+ uiBinder.createAndBindUi(this);
+ driver.initialize(this);
+ driver.edit(device);
+ }
+
+ public void show() {
+ window.show();
+ }
+
+ public void hide() {
+ window.hide();
+ }
+
+ @UiHandler("saveButton")
+ public void onLoginClicked(SelectEvent event) {
+ window.hide();
+ deviceHandler.onSave(driver.flush());
+ }
+
+ @UiHandler("cancelButton")
+ public void onRegisterClicked(SelectEvent event) {
+ window.hide();
+ }
+
+}
diff --git a/src/org/traccar/web/client/view/DeviceDialog.ui.xml b/src/org/traccar/web/client/view/DeviceDialog.ui.xml
new file mode 100644
index 00000000..4f7ccc56
--- /dev/null
+++ b/src/org/traccar/web/client/view/DeviceDialog.ui.xml
@@ -0,0 +1,40 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+ xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui"
+ xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
+ xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
+ xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form"
+ xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button">
+
+ <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="verticalLayoutData">
+ <ui:attributes width="1" height="-1" />
+ </ui:with>
+
+ <gxt:Window ui:field="window" pixelSize="300, 130" modal="true" headingText="Device" focusWidget="{saveButton}">
+ <container:VerticalLayoutContainer>
+ <container:child layoutData="{verticalLayoutData}">
+ <form:FieldLabel text="Name">
+ <form:widget>
+ <form:TextField ui:field="name" />
+ </form:widget>
+ </form:FieldLabel>
+ </container:child>
+ <container:child layoutData="{verticalLayoutData}">
+ <form:FieldLabel text="Unique Identifier">
+ <form:widget>
+ <form:TextField ui:field="uniqueId" />
+ </form:widget>
+ </form:FieldLabel>
+ </container:child>
+ </container:VerticalLayoutContainer>
+
+ <gxt:button>
+ <button:TextButton ui:field="saveButton" text="Save" />
+ </gxt:button>
+ <gxt:button>
+ <button:TextButton ui:field="cancelButton" text="Cancel" />
+ </gxt:button>
+ </gxt:Window>
+
+</ui:UiBinder>
diff --git a/src/org/traccar/web/client/view/DeviceView.java b/src/org/traccar/web/client/view/DeviceView.java
new file mode 100644
index 00000000..157973e0
--- /dev/null
+++ b/src/org/traccar/web/client/view/DeviceView.java
@@ -0,0 +1,128 @@
+package org.traccar.web.client.view;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.traccar.web.client.model.DeviceProperties;
+import org.traccar.web.shared.model.Device;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+import com.sencha.gxt.core.client.Style.SelectionMode;
+import com.sencha.gxt.data.shared.ListStore;
+import com.sencha.gxt.widget.core.client.ContentPanel;
+import com.sencha.gxt.widget.core.client.button.TextButton;
+import com.sencha.gxt.widget.core.client.event.SelectEvent;
+import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
+import com.sencha.gxt.widget.core.client.grid.ColumnModel;
+import com.sencha.gxt.widget.core.client.grid.Grid;
+import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
+
+public class DeviceView extends Composite implements SelectionChangedEvent.SelectionChangedHandler<Device> {
+
+ private static DeviceViewUiBinder uiBinder = GWT.create(DeviceViewUiBinder.class);
+
+ interface DeviceViewUiBinder extends UiBinder<Widget, DeviceView> {
+ }
+
+ public interface DeviceHandler {
+ public void onSelected(Device device);
+ public void onAdd();
+ public void onEdit(Device device);
+ public void onRemove(Device device);
+ }
+
+ private DeviceHandler deviceHandler;
+
+ @UiField
+ ContentPanel contentPanel;
+
+ public ContentPanel getView() {
+ return contentPanel;
+ }
+
+ @UiField
+ TextButton addButton;
+
+ @UiField
+ TextButton editButton;
+
+ @UiField
+ TextButton removeButton;
+
+ @UiField(provided = true)
+ ColumnModel<Device> columnModel;
+
+ @UiField(provided = true)
+ ListStore<Device> store;
+
+ @UiField
+ Grid<Device> grid;
+
+ public DeviceView(DeviceHandler deviceHandler) {
+ this.deviceHandler = deviceHandler;
+
+ DeviceProperties deviceProperties = GWT.create(DeviceProperties.class);
+
+ List<ColumnConfig<Device, ?>> columnConfigList = new LinkedList<ColumnConfig<Device, ?>>();
+ columnConfigList.add(new ColumnConfig<Device, String>(deviceProperties.name(), 0, "Name"));
+ columnConfigList.add(new ColumnConfig<Device, String>(deviceProperties.uniqueId(), 0, "Unique Identifier"));
+ columnModel = new ColumnModel<Device>(columnConfigList);
+
+ store = new ListStore<Device>(deviceProperties.id());
+
+ uiBinder.createAndBindUi(this);
+
+ grid.getSelectionModel().addSelectionChangedHandler(this);
+ grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
+ }
+
+ @Override
+ public void onSelectionChanged(SelectionChangedEvent<Device> event) {
+ editButton.setEnabled(!event.getSelection().isEmpty());
+ removeButton.setEnabled(!event.getSelection().isEmpty());
+
+ if (event.getSelection().isEmpty()) {
+ deviceHandler.onSelected(null);
+ } else {
+ deviceHandler.onSelected(event.getSelection().get(0));
+ }
+ }
+
+ public void load(Collection<Device> devices) {
+ store.addAll(devices);
+ }
+
+ @UiHandler("addButton")
+ public void onAddClicked(SelectEvent event) {
+ deviceHandler.onAdd();
+ }
+
+ public void add(Device device) {
+ store.add(store.size(), device);
+ }
+
+ @UiHandler("editButton")
+ public void onEditClicked(SelectEvent event) {
+ deviceHandler.onEdit(grid.getSelectionModel().getSelectedItem());
+ }
+
+ public void update(Device device) {
+ store.update(device);
+ }
+
+ @UiHandler("removeButton")
+ public void onRemoveClicked(SelectEvent event) {
+ deviceHandler.onRemove(grid.getSelectionModel().getSelectedItem());
+ }
+
+ public void remove(Device device) {
+ store.remove(device);
+ }
+
+}
diff --git a/src/org/traccar/web/client/view/DeviceView.ui.xml b/src/org/traccar/web/client/view/DeviceView.ui.xml
new file mode 100644
index 00000000..d9988d5e
--- /dev/null
+++ b/src/org/traccar/web/client/view/DeviceView.ui.xml
@@ -0,0 +1,46 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+ xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui"
+ xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
+ xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
+ xmlns:toolbar="urn:import:com.sencha.gxt.widget.core.client.toolbar"
+ xmlns:grid="urn:import:com.sencha.gxt.widget.core.client.grid"
+ xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button">
+
+ <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="toolBarRowData">
+ <ui:attributes width="1" height="-1" />
+ </ui:with>
+ <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="layoutData">
+ <ui:attributes width="1" height="1" />
+ </ui:with>
+
+ <ui:with type="com.sencha.gxt.data.shared.ListStore" field="store" />
+ <ui:with type="com.sencha.gxt.widget.core.client.grid.ColumnModel" field="columnModel" />
+
+ <ui:with type="com.sencha.gxt.widget.core.client.grid.GridView" field="view">
+ <ui:attributes stripeRows="true" autoFill="true" />
+ </ui:with>
+
+ <gxt:ContentPanel ui:field="contentPanel" headingText="Devices">
+ <container:VerticalLayoutContainer>
+
+ <container:child layoutData="{toolBarRowData}">
+ <toolbar:ToolBar>
+ <button:TextButton ui:field="addButton" text="Add" />
+ <button:TextButton ui:field="editButton" text="Edit" enabled="false" />
+ <button:TextButton ui:field="removeButton" text="Remove" enabled="false" />
+ <toolbar:FillToolItem />
+ <toolbar:SeparatorToolItem />
+ <button:TextButton ui:field="settingsButton" text="Settings" enabled="false" />
+ </toolbar:ToolBar>
+ </container:child>
+
+ <container:child layoutData="{layoutData}">
+ <grid:Grid ui:field="grid" store="{store}" cm="{columnModel}" view="{view}" />
+ </container:child>
+
+ </container:VerticalLayoutContainer>
+ </gxt:ContentPanel>
+
+</ui:UiBinder>
diff --git a/src/org/traccar/web/client/view/LoginDialog.java b/src/org/traccar/web/client/view/LoginDialog.java
new file mode 100644
index 00000000..44474179
--- /dev/null
+++ b/src/org/traccar/web/client/view/LoginDialog.java
@@ -0,0 +1,59 @@
+package org.traccar.web.client.view;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Widget;
+import com.sencha.gxt.widget.core.client.Window;
+import com.sencha.gxt.widget.core.client.event.SelectEvent;
+import com.sencha.gxt.widget.core.client.form.PasswordField;
+import com.sencha.gxt.widget.core.client.form.TextField;
+
+public class LoginDialog {
+
+ private static LoginDialogUiBinder uiBinder = GWT.create(LoginDialogUiBinder.class);
+
+ interface LoginDialogUiBinder extends UiBinder<Widget, LoginDialog> {
+ }
+
+ public interface LoginHandler {
+ public void onLogin(String login, String password);
+ public void onRegister(String login, String password);
+ }
+
+ private LoginHandler loginHandler;
+
+ @UiField
+ Window window;
+
+ @UiField
+ TextField login;
+
+ @UiField
+ PasswordField password;
+
+ public LoginDialog(LoginHandler loginHandler) {
+ this.loginHandler = loginHandler;
+ uiBinder.createAndBindUi(this);
+ }
+
+ public void show() {
+ window.show();
+ }
+
+ public void hide() {
+ window.hide();
+ }
+
+ @UiHandler("loginButton")
+ public void onLoginClicked(SelectEvent event) {
+ loginHandler.onLogin(login.getText(), password.getText());
+ }
+
+ @UiHandler("registerButton")
+ public void onRegisterClicked(SelectEvent event) {
+ loginHandler.onRegister(login.getText(), password.getText());
+ }
+
+}
diff --git a/src/org/traccar/web/client/view/LoginDialog.ui.xml b/src/org/traccar/web/client/view/LoginDialog.ui.xml
new file mode 100644
index 00000000..8c31b308
--- /dev/null
+++ b/src/org/traccar/web/client/view/LoginDialog.ui.xml
@@ -0,0 +1,40 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+ xmlns:ui="urn:ui:com.google.gwt.uibinder"
+ xmlns:g="urn:import:com.google.gwt.user.client.ui"
+ xmlns:gxt="urn:import:com.sencha.gxt.widget.core.client"
+ xmlns:container="urn:import:com.sencha.gxt.widget.core.client.container"
+ xmlns:form="urn:import:com.sencha.gxt.widget.core.client.form"
+ xmlns:button="urn:import:com.sencha.gxt.widget.core.client.button">
+
+ <ui:with type="com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData" field="verticalLayoutData">
+ <ui:attributes width="1" height="-1" />
+ </ui:with>
+
+ <gxt:Window ui:field="window" pixelSize="300, 130" modal="true" closable="false" headingText="Authentication" focusWidget="{loginButton}">
+ <container:VerticalLayoutContainer>
+ <container:child layoutData="{verticalLayoutData}">
+ <form:FieldLabel text="User">
+ <form:widget>
+ <form:TextField ui:field="login" />
+ </form:widget>
+ </form:FieldLabel>
+ </container:child>
+ <container:child layoutData="{verticalLayoutData}">
+ <form:FieldLabel text="Password">
+ <form:widget>
+ <form:PasswordField ui:field="password" />
+ </form:widget>
+ </form:FieldLabel>
+ </container:child>
+ </container:VerticalLayoutContainer>
+
+ <gxt:button>
+ <button:TextButton ui:field="loginButton" text="Login" />
+ </gxt:button>
+ <gxt:button>
+ <button:TextButton ui:field="registerButton" text="Register" />
+ </gxt:button>
+ </gxt:Window>
+
+</ui:UiBinder>
diff --git a/src/org/traccar/web/client/view/MapView.java b/src/org/traccar/web/client/view/MapView.java
new file mode 100644
index 00000000..af38c443
--- /dev/null
+++ b/src/org/traccar/web/client/view/MapView.java
@@ -0,0 +1,149 @@
+package org.traccar.web.client.view;
+
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.List;
+
+import org.gwtopenmaps.openlayers.client.Icon;
+import org.gwtopenmaps.openlayers.client.LonLat;
+import org.gwtopenmaps.openlayers.client.Map;
+import org.gwtopenmaps.openlayers.client.MapOptions;
+import org.gwtopenmaps.openlayers.client.MapWidget;
+import org.gwtopenmaps.openlayers.client.Marker;
+import org.gwtopenmaps.openlayers.client.Pixel;
+import org.gwtopenmaps.openlayers.client.Projection;
+import org.gwtopenmaps.openlayers.client.Size;
+import org.gwtopenmaps.openlayers.client.control.ScaleLine;
+import org.gwtopenmaps.openlayers.client.layer.Markers;
+import org.gwtopenmaps.openlayers.client.layer.OSM;
+import org.traccar.web.shared.model.Device;
+import org.traccar.web.shared.model.Position;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.user.client.Command;
+import com.sencha.gxt.widget.core.client.ContentPanel;
+
+class MarkerIconFactory {
+
+ private static final Size iconSize = new Size(21, 25);
+ private static final Pixel iconOffset = new Pixel(-10.5f, -25.0f);
+
+ private static final String iconUrl = "http://www.openlayers.org/api/img/";
+ private static final String iconRed = iconUrl + "marker.png";
+ private static final String iconBlue = iconUrl + "marker-blue.png";
+ private static final String iconGreen = iconUrl + "marker-green.png";
+ private static final String iconGold = iconUrl + "marker-gold.png";
+
+ public static Icon getLocationIcon() {
+ return new Icon(iconRed, iconSize, iconOffset);
+ }
+
+ public static Icon getSelectedLocationIcon() {
+ return new Icon(iconGreen, iconSize, iconOffset);
+ }
+
+ public static Icon getArchiveIcon() {
+ return new Icon(iconBlue, iconSize, iconOffset);
+ }
+
+ public static Icon getSelectedArchiveIcon() {
+ return new Icon(iconGold, iconSize, iconOffset);
+ }
+
+}
+
+public class MapView {
+
+ private ContentPanel contentPanel;
+
+ public ContentPanel getView() {
+ return contentPanel;
+ }
+
+ private MapWidget mapWidget;
+ private Map map;
+ private Markers markerLayer;
+
+ private LonLat createPoint(double longitude, double latitude) {
+ LonLat point = new LonLat(longitude, latitude);
+ point.transform(new Projection("EPSG:4326").getProjectionCode(), map.getProjection());
+ return point;
+ }
+
+ public MapView() {
+ contentPanel = new ContentPanel();
+ contentPanel.setHeadingText("Map");
+
+ MapOptions defaultMapOptions = new MapOptions();
+ defaultMapOptions.setNumZoomLevels(16);
+
+ mapWidget = new MapWidget("100%", "100%", defaultMapOptions);
+ map = mapWidget.getMap();
+
+ OSM mapLayer = OSM.Mapnik("Mapnik");
+ mapLayer.setIsBaseLayer(true);
+
+ markerLayer = new Markers("Markers");
+
+ map.addLayer(mapLayer);
+ map.addLayer(markerLayer);
+ map.addControl(new ScaleLine());
+ map.setCenter(createPoint(30, 60), 1);
+
+ contentPanel.add(mapWidget);
+
+ // Update map size
+ contentPanel.addResizeHandler(new ResizeHandler() {
+ @Override
+ public void onResize(ResizeEvent event) {
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ map.updateSize();
+ }
+ });
+ }
+ });
+ }
+
+ private AbstractMap<Long, Marker> markerMap = new HashMap<Long, Marker>();
+ private Device selectedDevice;
+
+ public void showPositions(List<Position> positions) {
+ markerMap.clear();
+ markerLayer.clearMarkers();
+ for (Position position : positions) {
+ Marker marker = new Marker(
+ createPoint(position.getLongitude(), position.getLatitude()), MarkerIconFactory.getLocationIcon());
+ markerMap.put(position.getDevice().getId(), marker);
+ markerLayer.addMarker(marker);
+ }
+ if (selectedDevice != null) {
+ select(selectedDevice, false);
+ }
+ }
+
+ private void changeMarkerIcon(Marker marker, Icon icon) {
+ Marker newMarker = new Marker(marker.getLonLat(), icon);
+ markerLayer.removeMarker(marker);
+ markerLayer.addMarker(newMarker);
+ }
+
+ public void select(Device device, boolean center) {
+ if (selectedDevice != null) {
+ changeMarkerIcon(markerMap.get(selectedDevice.getId()), MarkerIconFactory.getLocationIcon());
+ selectedDevice = null;
+ }
+ if (device != null && markerMap.containsKey(device.getId())) {
+ Marker marker = markerMap.get(device.getId());
+ if (center) {
+ map.panTo(marker.getLonLat());
+ }
+ changeMarkerIcon(marker, MarkerIconFactory.getSelectedLocationIcon());
+ selectedDevice = device;
+ }
+ }
+
+}
diff --git a/src/org/traccar/web/server/model/DataServiceImpl.java b/src/org/traccar/web/server/model/DataServiceImpl.java
new file mode 100644
index 00000000..cfcc6226
--- /dev/null
+++ b/src/org/traccar/web/server/model/DataServiceImpl.java
@@ -0,0 +1,172 @@
+package org.traccar.web.server.model;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.persistence.EntityManager;
+import javax.persistence.Persistence;
+import javax.persistence.TypedQuery;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+
+import org.traccar.web.client.model.DataService;
+import org.traccar.web.shared.model.Device;
+import org.traccar.web.shared.model.Position;
+import org.traccar.web.shared.model.User;
+
+import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+
+public class DataServiceImpl extends RemoteServiceServlet implements DataService {
+
+ private static final long serialVersionUID = 1;
+
+ private static final String PERSISTENCE_DATASTORE = "java:/DefaultDS";
+ private static final String PERSISTENCE_UNIT_DEBUG = "debug";
+ private static final String PERSISTENCE_UNIT_RELEASE = "release";
+ private static final String ATTRIBUTE_USER = "user";
+
+ private EntityManager entityManager;
+
+ @Override
+ public void init() throws ServletException {
+ super.init();
+
+ String persistenceUnit;
+ try {
+ Context context = new InitialContext();
+ context.lookup(PERSISTENCE_DATASTORE);
+ persistenceUnit = PERSISTENCE_UNIT_RELEASE;
+ } catch (NamingException e) {
+ persistenceUnit = PERSISTENCE_UNIT_DEBUG;
+ }
+
+ entityManager = Persistence.createEntityManagerFactory(persistenceUnit).createEntityManager();
+ }
+
+ @Override
+ public void destroy() {
+ entityManager.close();
+ super.destroy();
+ }
+
+ private void setUser(User user) {
+ HttpSession session = getThreadLocalRequest().getSession();
+ session.setAttribute(ATTRIBUTE_USER, user);
+ }
+
+ private User getUser() {
+ HttpSession session = getThreadLocalRequest().getSession();
+ return (User) session.getAttribute(ATTRIBUTE_USER);
+ }
+
+ @Override
+ public boolean authenticated() {
+ return (getUser() != null);
+ }
+
+ @Override
+ public boolean authenticate(String login, String password) {
+ TypedQuery<User> query = entityManager.createQuery(
+ "SELECT x FROM User x WHERE x.login = :login", User.class);
+ query.setParameter("login", login);
+ List<User> results = query.getResultList();
+
+ if (!results.isEmpty() && password.equals(results.get(0).getPassword())) {
+ setUser(results.get(0));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean register(String login, String password) {
+ User user = new User();
+ user.setLogin(login);
+ user.setPassword(password);
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(user);
+ } finally {
+ entityManager.getTransaction().commit();
+ }
+ setUser(user);
+ return true;
+ }
+
+ @Override
+ public List<Device> getDevices() {
+ User user = getUser();
+ List<Device> devices = new LinkedList<Device>();
+ devices.addAll(user.getDevices());
+ return devices;
+ }
+
+ @Override
+ public Device addDevice(Device device) {
+ User user = getUser();
+ entityManager.getTransaction().begin();
+ try {
+ entityManager.persist(device);
+ user.getDevices().add(device);
+ } finally {
+ entityManager.getTransaction().commit();
+ }
+ return device;
+ }
+
+ @Override
+ public Device updateDevice(Device device) {
+ entityManager.getTransaction().begin();
+ try {
+ device = entityManager.merge(device);
+ } finally {
+ entityManager.getTransaction().commit();
+ }
+ return device;
+ }
+
+ @Override
+ public Device removeDevice(Device device) {
+ User user = getUser();
+ entityManager.getTransaction().begin();
+ try {
+ device = entityManager.merge(device);
+ user.getDevices().remove(device);
+ entityManager.remove(device);
+ } finally {
+ entityManager.getTransaction().commit();
+ }
+ return device;
+ }
+
+ @Override
+ public List<Position> getPositions(Device device, Date from, Date to) {
+ List<Position> positions = new LinkedList<Position>();
+ TypedQuery<Position> query = entityManager.createQuery(
+ "SELECT x FROM Position x WHERE x.device = :device AND x.time BETWEEN :from AND :to", Position.class);
+ query.setParameter("device", device);
+ query.setParameter("from", from);
+ query.setParameter("to", to);
+ positions.addAll(query.getResultList());
+ return positions;
+ }
+
+ @Override
+ public List<Position> getLatestPositions() {
+ List<Position> positions = new LinkedList<Position>();
+ User user = getUser();
+ if (!user.getDevices().isEmpty()) {
+ TypedQuery<Position> query = entityManager.createQuery(
+ "SELECT x FROM Position x WHERE (x.device, x.time) IN (" +
+ "SELECT y.device, MAX(y.time) FROM Position y WHERE y.device IN :devices GROUP BY y.device)", Position.class);
+ query.setParameter("devices", user.getDevices());
+ positions.addAll(query.getResultList());
+ }
+ return positions;
+ }
+
+}
diff --git a/src/org/traccar/web/shared/model/Device.java b/src/org/traccar/web/shared/model/Device.java
new file mode 100644
index 00000000..8ae67c34
--- /dev/null
+++ b/src/org/traccar/web/shared/model/Device.java
@@ -0,0 +1,56 @@
+package org.traccar.web.shared.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "devices")
+public class Device implements Serializable {
+
+ private static final long serialVersionUID = 1;
+
+ public Device() {
+ }
+
+ public Device(Device device) {
+ id = device.id;
+ uniqueId = device.uniqueId;
+ name = device.name;
+ }
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ public long getId() {
+ return id;
+ }
+
+ @Column(unique = true)
+ private String uniqueId;
+
+ public void setUniqueId(String uniqueId) {
+ this.uniqueId = uniqueId;
+ }
+
+ public String getUniqueId() {
+ return uniqueId;
+ }
+
+ @Column(unique = true)
+ private String name;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/src/org/traccar/web/shared/model/Position.java b/src/org/traccar/web/shared/model/Position.java
new file mode 100644
index 00000000..13827301
--- /dev/null
+++ b/src/org/traccar/web/shared/model/Position.java
@@ -0,0 +1,115 @@
+package org.traccar.web.shared.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+import org.hibernate.annotations.Index;
+
+@Entity
+@Table(name = "positions")
+public class Position implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 1;
+
+ public Position() {
+ }
+
+ public Position(Position position) {
+ id = position.id;
+ device = position.device;
+ time = position.time;
+ valid = position.valid;
+ latitude = position.latitude;
+ longitude = position.longitude;
+ altitude = position.altitude;
+ speed = position.speed;
+ course = position.course;
+ power = position.power;
+ address = position.address;
+ other = position.other;
+ }
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ public long getId() {
+ return id;
+ }
+
+ @ManyToOne
+ @Index(name = "positionsIndex")
+ private Device device;
+
+ public Device getDevice() {
+ return device;
+ }
+
+ @Index(name = "positionsIndex")
+ private Date time;
+
+ public Date getTime() {
+ return time;
+ }
+
+ private boolean valid;
+
+ public boolean getValid() {
+ return valid;
+ }
+
+ private double latitude;
+
+ public double getLatitude() {
+ return latitude;
+ }
+
+ private double longitude;
+
+ public double getLongitude() {
+ return longitude;
+ }
+
+ private double altitude;
+
+ public double getAltitude() {
+ return altitude;
+ }
+
+ private double speed;
+
+ public double getSpeed() {
+ return speed;
+ }
+
+ private double course;
+
+ public double getCourse() {
+ return course;
+ }
+
+ private double power;
+
+ public double getPower() {
+ return power;
+ }
+
+ private String address;
+
+ public String getAddress() {
+ return address;
+ }
+
+ private String other;
+
+ public String getOther() {
+ return other;
+ }
+
+}
diff --git a/src/org/traccar/web/shared/model/User.java b/src/org/traccar/web/shared/model/User.java
new file mode 100644
index 00000000..9064f190
--- /dev/null
+++ b/src/org/traccar/web/shared/model/User.java
@@ -0,0 +1,65 @@
+package org.traccar.web.shared.model;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+@Entity
+@Table(name="users")
+public class User implements Serializable, Cloneable {
+
+ private static final long serialVersionUID = 1;
+
+ public User() {
+ }
+
+ public User(User user) {
+ id = user.id;
+ login = user.login;
+ password = user.password;
+ }
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ public long getId() {
+ return id;
+ }
+
+ @Column(unique = true)
+ private String login;
+
+ public void setLogin(String login) {
+ this.login = login;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+
+ private String password;
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @OneToMany
+ private List<Device> devices = new LinkedList<Device>();
+
+ public List<Device> getDevices() {
+ return devices;
+ }
+
+}