changeset 1276:aec9ed491dad

more cleanup in client/src
author Markus Kottlaender <markus@intevation.de>
date Thu, 22 Nov 2018 07:40:23 +0100
parents 9e23a2b02b32
children 10b01a298745
files client/src/components/App.vue client/src/components/admin/Importqueue.vue client/src/components/admin/Logs.vue client/src/components/admin/Systemconfiguration.vue client/src/components/admin/logs.vue client/src/components/admin/systemconfiguration.vue client/src/components/map/Bottlenecks.vue client/src/components/map/Contextbox.vue client/src/components/map/Staging.vue client/src/components/map/contextbox/Bottlenecks.vue client/src/components/map/contextbox/Contextbox.vue client/src/components/map/contextbox/Staging.vue client/src/components/map/contextbox/imports/Imports.vue client/src/components/map/imports/Importqueue.vue client/src/components/map/imports/Imports.vue client/src/router.js
diffstat 16 files changed, 1206 insertions(+), 1206 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/components/App.vue	Thu Nov 22 07:28:21 2018 +0100
+++ b/client/src/components/App.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -89,7 +89,7 @@
     Layers: () => import("./map/layers/Layers"),
     Sidebar: () => import("./Sidebar"),
     Search: () => import("./map/Search"),
-    Contextbox: () => import("./map/Contextbox"),
+    Contextbox: () => import("./map/contextbox/Contextbox"),
     Toolbar: () => import("./map/toolbar/Toolbar")
   }
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/admin/Importqueue.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -0,0 +1,170 @@
+<template>
+    <div class="d-flex flex-row">
+        <div :class="spacerStyle"></div>
+        <div class="mt-3 mx-auto">
+            <div class="card importqueuecard">
+                <div class="card-header shadow-sm text-white bg-info mb-3">Importqueue</div>
+                <div class="card-body importcardbody">
+                    <div class="card-body importcardbody">
+                        <div class="searchandfilter d-flex flex-row">
+                            <div class="searchgroup input-group">
+                                <div class="input-group-prepend">
+                                    <span class="input-group-text" id="search">
+                                        <i class="fa fa-search"></i>
+                                    </span>
+                                </div>
+                                <input
+                                    type="text"
+                                    class="form-control"
+                                    placeholder=""
+                                    aria-label="Search"
+                                    aria-describedby="search"
+                                >
+                            </div>
+                            <div class="filters">
+                                <button
+                                    @click="setFilter('successful')"
+                                    :class="successfulStyle"
+                                >Successful</button>
+                                <button @click="setFilter('failed')" :class="failedStyle">Failed</button>
+                                <button @click="setFilter('pending')" :class="pendingStyle">Pending</button>
+                            </div>
+                        </div>
+                        <table class="table">
+                            <thead>
+                                <tr>
+                                    <th>Enqueued</th>
+                                    <th>Kind</th>
+                                    <th>User</th>
+                                    <th>State</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr v-for="job in imports" :key="job.id">
+                                    <td>{{job.enqueued}}</td>
+                                    <td>{{job.kind}}</td>
+                                    <td>{{job.user}}</td>
+                                    <td>{{job.state}}</td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import { displayError } from "../../lib/errors.js";
+import { mapState } from "vuex";
+
+export default {
+  name: "importqueue",
+  data() {
+    return {
+      successful: false,
+      failed: false,
+      pending: false
+    };
+  },
+  methods: {
+    setFilter(name) {
+      this[name] = !this[name];
+      const allSet = this.successful && this.failed && this.pending;
+      if (allSet) {
+        this.all = false;
+        this.successful = false;
+        this.failed = false;
+        this.pending = false;
+      }
+    }
+  },
+  computed: {
+    ...mapState("imports", ["imports"]),
+    ...mapState("application", ["showSidebar"]),
+    spacerStyle() {
+      return [
+        "spacer ml-3",
+        {
+          "spacer-expanded": this.showSidebar,
+          "spacer-collapsed": !this.showSidebar
+        }
+      ];
+    },
+    successfulStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.successful,
+        "btn-dark": this.successful
+      };
+    },
+    pendingStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.pending,
+        "btn-dark": this.pending
+      };
+    },
+    failedStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.failed,
+        "btn-dark": this.failed
+      };
+    }
+  },
+  mounted() {
+    this.$store.dispatch("imports/getImports").catch(error => {
+      const { status, data } = error.response;
+      displayError({
+        title: "Backend Error",
+        message: `${status}: ${data.message || data}`
+      });
+    });
+  }
+};
+</script>
+
+<style lang="sass" scoped>
+.spacer
+  height: 100vh
+
+.spacer-collapsed
+  min-width: $icon-width + $offset
+  transition: $transition-fast
+
+.spacer-expanded
+  min-width: $sidebar-width + $offset
+
+.importqueuecard
+  width: 80vw
+  min-height: 20rem
+
+.card-body
+  width: 100%
+  margin-left: auto
+  margin-right: auto
+
+.searchandfilter
+  position: relative
+  margin-bottom: $xx-large-offset
+
+.filters
+  position: absolute
+  right: 0
+
+.filters button
+  margin-right: $small-offset
+
+.table td,
+.table th
+  border-top: 0 !important
+  text-align: left
+  padding: $small-offset !important
+
+.searchgroup
+  position: absolute
+  left: 0
+  width: 50%
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/admin/Logs.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -0,0 +1,158 @@
+<template>
+    <div class="main d-flex flex-column">
+        <div class="d-flex flex-row">
+            <div :class="spacer"></div>
+            <div class="logoutput text-left bg-white shadow mt-3 mx-3">
+                <pre id="code" v-highlightjs="logs"><code class="bash hljs hljs-string"></code></pre>
+            </div>
+        </div>
+        <div class="d-flex flex-row logmenu">
+            <div class="d-flex align-self-center">
+                <ul class="nav nav-pills">
+                    <li class="nav-item">
+                        <a
+                            @click="fetch('system/log/apache2/access.log', 'accesslog')"
+                            :class="accesslogStyle"
+                            href="#"
+                        >Accesslog</a>
+                    </li>
+                    <li class="nav-item">
+                        <a
+                            @click="fetch('system/log/apache2/error.log', 'errorlog')"
+                            :class="errorlogStyle"
+                            href="#"
+                        >Errorlog</a>
+                    </li>
+                </ul>
+            </div>
+            <div class="statuscontainer d-flex flex-row">
+                <div class="statusline ml-3 mt-1 align-self-center">
+                    <h3>Last refresh: {{refreshed}}</h3>
+                </div>
+                <div class="refresh">
+                    <button class="btn btn-dark" @click="fetch(currentFile, currentLog)">Refresh</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style lang="sass" scoped>
+.statuscontainer
+  width: 87%
+  position: relative
+  
+.logmenu
+  margin-left: 5rem
+  min-width: 60vw
+  
+#code
+  overflow: auto
+  
+.refresh
+  position: absolute
+  right: 0
+  
+.logoutput
+  width: 95%
+  height: 85vh
+  overflow: auto
+  transition: $transition-fast
+
+.spacer
+  height: 90vh
+
+.spacer-collapsed
+  min-width: $icon-width + $offset
+  transition: $transition-fast
+
+.spacer-expanded
+  min-width: $sidebar-width + $offset
+
+.statusline
+  position: absolute
+  right: 0
+  margin-right: 7rem
+</style>
+
+<script>
+/*
+ * This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ * 
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ * 
+ * Copyright (C) 2018 by via donau 
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ * 
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { mapState } from "vuex";
+import { HTTP } from "../../lib/http.js";
+import "../../../node_modules/highlight.js/styles/paraiso-dark.css";
+import Vue from "vue";
+import VueHighlightJS from "vue-highlightjs";
+Vue.use(VueHighlightJS);
+
+const ACCESSLOG = "accesslog";
+const ERRORLOG = "errorlog";
+
+export default {
+  name: "logs",
+  mounted() {
+    this.fetch("system/log/apache2/access.log", ACCESSLOG);
+  },
+  data() {
+    return {
+      logs: null,
+      currentLog: null,
+      currentFile: null,
+      refreshed: null
+    };
+  },
+  methods: {
+    fetch(file, type) {
+      HTTP.get(file, {
+        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+      })
+        .then(response => {
+          this.logs = response.data.content;
+          this.currentLog = type;
+          this.refreshed = new Date().toLocaleString();
+          this.currentFile = file;
+        })
+        .catch();
+    },
+    disallow(e) {
+      e.target.blur();
+    }
+  },
+  computed: {
+    ...mapState("application", ["showSidebar"]),
+    accesslogStyle() {
+      return {
+        active: this.currentLog == ACCESSLOG,
+        "nav-link": true
+      };
+    },
+    errorlogStyle() {
+      return {
+        active: this.currentLog == ERRORLOG,
+        "nav-link": true
+      };
+    },
+    spacer() {
+      return [
+        "spacer ml-3",
+        {
+          "spacer-expanded": this.showSidebar,
+          "spacer-collapsed": !this.showSidebar
+        }
+      ];
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/admin/Systemconfiguration.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -0,0 +1,144 @@
+<template>
+    <div class="d-flex flex-row">
+        <div class="card sysconfig mt-3 mx-auto">
+            <div class="card-header shadow-sm text-white bg-info mb-6">
+                Systemconfiguration
+            </div>
+            <div class="card-body config">
+                <section class="configsection">
+                    <h4 class="card-title">Bottleneck Areas stroke-color</h4>
+                    <compact-picker v-model="strokeColor" />
+                </section>
+                <section>
+                    <h4 class="card-title">Bottleneck Areas fill-color</h4>
+                    <chrome-picker v-model="fillColor" />
+                </section>
+                <div class="sendbutton">
+                    <a @click.prevent="submit" class="btn btn-info">Send</a>
+                </div>
+            </div> <!-- card-body -->
+        </div>
+    </div>
+</template>
+
+<style scoped lang="sass">
+.config
+  text-align: left
+  
+.configsection
+  margin-bottom: $large-offset
+  
+.sendbutton
+  position: absolute
+  right: $offset
+  bottom: $offset
+  
+.inputs
+  margin-left: auto
+  margin-right: auto
+  
+.sysconfig
+  width: 30vw
+</style>
+
+<script>
+/*
+ * This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ * 
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ * 
+ * Copyright (C) 2018 by via donau 
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ * 
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { Chrome } from "vue-color";
+import { Compact } from "vue-color";
+
+import { HTTP } from "../../lib/http";
+import { displayError } from "../../lib/errors.js";
+export default {
+  name: "systemconfiguration",
+  data() {
+    return {
+      sent: false,
+      strokeColor: { r: 0, g: 0, b: 0, a: 1.0 },
+      fillColor: { r: 0, g: 0, b: 0, a: 1.0 },
+      currentConfig: null
+    };
+  },
+  components: { "chrome-picker": Chrome, "compact-picker": Compact },
+  methods: {
+    submit() {
+      HTTP.put("/system/style/Bottlenecks/stroke", this.strokeColor.rgba, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "application/json"
+        }
+      })
+        .then()
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: "Backend Error",
+            message: `${status}: ${data.message || data}`
+          });
+        });
+
+      HTTP.put("/system/style/Bottlenecks/fill", this.fillColor.rgba, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "application/json"
+        }
+      })
+        .then()
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: "Backend Error",
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  },
+  mounted() {
+    HTTP.get("/system/style/Bottlenecks/stroke", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "application/json"
+      }
+    })
+      .then(response => {
+        this.strokeColor = response.data.colour;
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: "Backend Error",
+          message: `${status}: ${data.message || data}`
+        });
+      });
+
+    HTTP.get("/system/style/Bottlenecks/fill", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "application/json"
+      }
+    })
+      .then(response => {
+        this.fillColor = response.data.colour;
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: "Backend Error",
+          message: `${status}: ${data.message || data}`
+        });
+      });
+  }
+};
+</script>
--- a/client/src/components/admin/logs.vue	Thu Nov 22 07:28:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
-<template>
-    <div class="main d-flex flex-column">
-        <div class="d-flex flex-row">
-            <div :class="spacer"></div>
-            <div class="logoutput text-left bg-white shadow mt-3 mx-3">
-                <pre id="code" v-highlightjs="logs"><code class="bash hljs hljs-string"></code></pre>
-            </div>
-        </div>
-        <div class="d-flex flex-row logmenu">
-            <div class="d-flex align-self-center">
-                <ul class="nav nav-pills">
-                    <li class="nav-item">
-                        <a
-                            @click="fetch('system/log/apache2/access.log', 'accesslog')"
-                            :class="accesslogStyle"
-                            href="#"
-                        >Accesslog</a>
-                    </li>
-                    <li class="nav-item">
-                        <a
-                            @click="fetch('system/log/apache2/error.log', 'errorlog')"
-                            :class="errorlogStyle"
-                            href="#"
-                        >Errorlog</a>
-                    </li>
-                </ul>
-            </div>
-            <div class="statuscontainer d-flex flex-row">
-                <div class="statusline ml-3 mt-1 align-self-center">
-                    <h3>Last refresh: {{refreshed}}</h3>
-                </div>
-                <div class="refresh">
-                    <button class="btn btn-dark" @click="fetch(currentFile, currentLog)">Refresh</button>
-                </div>
-            </div>
-        </div>
-    </div>
-</template>
-
-<style lang="sass" scoped>
-.statuscontainer
-  width: 87%
-  position: relative
-  
-.logmenu
-  margin-left: 5rem
-  min-width: 60vw
-  
-#code
-  overflow: auto
-  
-.refresh
-  position: absolute
-  right: 0
-  
-.logoutput
-  width: 95%
-  height: 85vh
-  overflow: auto
-  transition: $transition-fast
-
-.spacer
-  height: 90vh
-
-.spacer-collapsed
-  min-width: $icon-width + $offset
-  transition: $transition-fast
-
-.spacer-expanded
-  min-width: $sidebar-width + $offset
-
-.statusline
-  position: absolute
-  right: 0
-  margin-right: 7rem
-</style>
-
-<script>
-/*
- * This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- * 
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- * 
- * Copyright (C) 2018 by via donau 
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- * 
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import { mapState } from "vuex";
-import { HTTP } from "../../lib/http.js";
-import "../../../node_modules/highlight.js/styles/paraiso-dark.css";
-import Vue from "vue";
-import VueHighlightJS from "vue-highlightjs";
-Vue.use(VueHighlightJS);
-
-const ACCESSLOG = "accesslog";
-const ERRORLOG = "errorlog";
-
-export default {
-  name: "logs",
-  mounted() {
-    this.fetch("system/log/apache2/access.log", ACCESSLOG);
-  },
-  data() {
-    return {
-      logs: null,
-      currentLog: null,
-      currentFile: null,
-      refreshed: null
-    };
-  },
-  methods: {
-    fetch(file, type) {
-      HTTP.get(file, {
-        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-      })
-        .then(response => {
-          this.logs = response.data.content;
-          this.currentLog = type;
-          this.refreshed = new Date().toLocaleString();
-          this.currentFile = file;
-        })
-        .catch();
-    },
-    disallow(e) {
-      e.target.blur();
-    }
-  },
-  computed: {
-    ...mapState("application", ["showSidebar"]),
-    accesslogStyle() {
-      return {
-        active: this.currentLog == ACCESSLOG,
-        "nav-link": true
-      };
-    },
-    errorlogStyle() {
-      return {
-        active: this.currentLog == ERRORLOG,
-        "nav-link": true
-      };
-    },
-    spacer() {
-      return [
-        "spacer ml-3",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    }
-  }
-};
-</script>
--- a/client/src/components/admin/systemconfiguration.vue	Thu Nov 22 07:28:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-<template>
-    <div class="d-flex flex-row">
-        <div class="card sysconfig mt-3 mx-auto">
-            <div class="card-header shadow-sm text-white bg-info mb-6">
-                Systemconfiguration
-            </div>
-            <div class="card-body config">
-                <section class="configsection">
-                    <h4 class="card-title">Bottleneck Areas stroke-color</h4>
-                    <compact-picker v-model="strokeColor" />
-                </section>
-                <section>
-                    <h4 class="card-title">Bottleneck Areas fill-color</h4>
-                    <chrome-picker v-model="fillColor" />
-                </section>
-                <div class="sendbutton">
-                    <a @click.prevent="submit" class="btn btn-info">Send</a>
-                </div>
-            </div> <!-- card-body -->
-        </div>
-    </div>
-</template>
-
-<style scoped lang="sass">
-.config
-  text-align: left
-  
-.configsection
-  margin-bottom: $large-offset
-  
-.sendbutton
-  position: absolute
-  right: $offset
-  bottom: $offset
-  
-.inputs
-  margin-left: auto
-  margin-right: auto
-  
-.sysconfig
-  width: 30vw
-</style>
-
-<script>
-/*
- * This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- * 
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- * 
- * Copyright (C) 2018 by via donau 
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- * 
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import { Chrome } from "vue-color";
-import { Compact } from "vue-color";
-
-import { HTTP } from "../../lib/http";
-import { displayError } from "../../lib/errors.js";
-export default {
-  name: "systemconfiguration",
-  data() {
-    return {
-      sent: false,
-      strokeColor: { r: 0, g: 0, b: 0, a: 1.0 },
-      fillColor: { r: 0, g: 0, b: 0, a: 1.0 },
-      currentConfig: null
-    };
-  },
-  components: { "chrome-picker": Chrome, "compact-picker": Compact },
-  methods: {
-    submit() {
-      HTTP.put("/system/style/Bottlenecks/stroke", this.strokeColor.rgba, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "application/json"
-        }
-      })
-        .then()
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: "Backend Error",
-            message: `${status}: ${data.message || data}`
-          });
-        });
-
-      HTTP.put("/system/style/Bottlenecks/fill", this.fillColor.rgba, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "application/json"
-        }
-      })
-        .then()
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: "Backend Error",
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  },
-  mounted() {
-    HTTP.get("/system/style/Bottlenecks/stroke", {
-      headers: {
-        "X-Gemma-Auth": localStorage.getItem("token"),
-        "Content-type": "application/json"
-      }
-    })
-      .then(response => {
-        this.strokeColor = response.data.colour;
-      })
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: "Backend Error",
-          message: `${status}: ${data.message || data}`
-        });
-      });
-
-    HTTP.get("/system/style/Bottlenecks/fill", {
-      headers: {
-        "X-Gemma-Auth": localStorage.getItem("token"),
-        "Content-type": "application/json"
-      }
-    })
-      .then(response => {
-        this.fillColor = response.data.colour;
-      })
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: "Backend Error",
-          message: `${status}: ${data.message || data}`
-        });
-      });
-  }
-};
-</script>
--- a/client/src/components/map/Bottlenecks.vue	Thu Nov 22 07:28:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,233 +0,0 @@
-<template>
-    <div>
-        <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-          <i class="fa fa-ship mr-2"></i>
-          Bottlenecks
-        </h6>
-        <div class="row p-2 text-left small">
-            <div class="col-5">
-                <a href="#" @click="sortBy('name')" class="sort-link">Name</a>
-                <i :class="sortClass" v-if="sortColumn === 'name'"></i>
-            </div>
-            <div class="col-2">
-                <a
-                    href="#"
-                    @click="sortBy('latestMeasurement')"
-                    class="sort-link"
-                >Latest Measurement</a>
-                <i :class="sortClass" v-if="sortColumn === 'latestMeasurement'"></i>
-            </div>
-            <div class="col-3">
-                <a href="#" @click="sortBy('chainage')" class="sort-link">Chainage</a>
-                <i :class="sortClass" v-if="sortColumn === 'chainage'"></i>
-            </div>
-            <div class="col-2"></div>
-        </div>
-        <div class="bottleneck-list small text-left" v-if="filteredAndSortedBottlenecks().length">
-            <div
-                v-for="bottleneck in filteredAndSortedBottlenecks()"
-                :key="bottleneck.properties.name"
-                class="border-top row mx-0 py-2"
-            >
-                <div class="col-5 text-left">
-                    <a
-                        href="#"
-                        class="d-block"
-                        @click="moveToBottleneck(bottleneck)"
-                    >{{ bottleneck.properties.name }}</a>
-                </div>
-                <div class="col-2">{{ displayCurrentSurvey(bottleneck.properties.current) }}</div>
-                <div
-                    class="col-3"
-                >{{ displayCurrentChainage(bottleneck.properties.from, bottleneck.properties.from) }}</div>
-                <div class="col-2 text-right">
-                    <button
-                        type="button"
-                        class="btn btn-sm btn-outline-secondary"
-                        @click="toggleBottleneck(bottleneck.properties.name)"
-                    >
-                        <i class="fa fa-angle-down"></i>
-                    </button>
-                </div>
-                <div
-                    :class="['col-12', 'surveys', {open: openBottleneck === bottleneck.properties.name}]"
-                >
-                    <a
-                        href="#"
-                        class="d-block p-2"
-                        v-for="(survey, index) in openBottleneckSurveys"
-                        :key="index"
-                        @click="selectSurvey(survey, bottleneck)"
-                    >{{ survey.date_info }}</a>
-                </div>
-            </div>
-        </div>
-        <div v-else class="small text-center py-3 border-top">
-            No results.
-        </div>
-    </div>
-</template>
-
-<script>
-/*
- * This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-import { HTTP } from "../../lib/http";
-import { displayError } from "../../lib/errors.js";
-
-export default {
-  name: "bottlenecks",
-  data() {
-    return {
-      sortColumn: "name",
-      sortDirection: "ASC",
-      openBottleneck: null,
-      openBottleneckSurveys: null
-    };
-  },
-  computed: {
-    ...mapState("application", ["searchQuery", "showSearchbarLastState"]),
-    ...mapState("bottlenecks", ["bottlenecks"]),
-    sortClass() {
-      return [
-        "fa ml-1",
-        {
-          "fa-sort-amount-asc": this.sortDirection === "ASC",
-          "fa-sort-amount-desc": this.sortDirection === "DESC"
-        }
-      ];
-    }
-  },
-  methods: {
-    filteredAndSortedBottlenecks() {
-      return this.bottlenecks
-        .filter(bn => {
-          return bn.properties.name
-            .toLowerCase()
-            .includes(this.searchQuery.toLowerCase());
-        })
-        .sort((bnA, bnB) => {
-          switch (this.sortColumn) {
-            case "name":
-              if (
-                bnA.properties.name.toLowerCase() <
-                bnB.properties.name.toLowerCase()
-              )
-                return this.sortDirection === "ASC" ? -1 : 1;
-              if (
-                bnA.properties.name.toLowerCase() >
-                bnB.properties.name.toLowerCase()
-              )
-                return this.sortDirection === "ASC" ? 1 : -1;
-              return 0;
-
-            case "latestMeasurement": {
-              if (
-                (bnA.properties.current || "") < (bnB.properties.current || "")
-              )
-                return this.sortDirection === "ASC" ? -1 : 1;
-              if (
-                (bnA.properties.current || "") > (bnB.properties.current || "")
-              )
-                return this.sortDirection === "ASC" ? 1 : -1;
-              return 0;
-            }
-
-            case "chainage":
-              if (bnA.properties.from < bnB.properties.from)
-                return this.sortDirection === "ASC" ? -1 : 1;
-              if (bnA.properties.from > bnB.properties.from)
-                return this.sortDirection === "ASC" ? 1 : -1;
-              return 0;
-
-            default:
-              return 0;
-          }
-        });
-    },
-    selectSurvey(survey, bottleneck) {
-      this.$store.dispatch(
-        "bottlenecks/setSelectedBottleneck",
-        bottleneck.properties.name
-      );
-      this.$store.commit("bottlenecks/setSelectedSurvey", survey);
-      this.moveToBottleneck(bottleneck);
-    },
-    moveToBottleneck(bottleneck) {
-      this.$store.commit("map/moveMap", {
-        coordinates: bottleneck.geometry.coordinates,
-        zoom: 17,
-        preventZoomOut: true
-      });
-    },
-    sortBy(column) {
-      this.sortColumn = column;
-      this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC";
-    },
-    toggleBottleneck(name) {
-      this.openBottleneckSurveys = null;
-      if (name === this.openBottleneck) {
-        this.openBottleneck = null;
-      } else {
-        this.openBottleneck = name;
-
-        HTTP.get("/surveys/" + name, {
-          headers: {
-            "X-Gemma-Auth": localStorage.getItem("token"),
-            "Content-type": "text/xml; charset=UTF-8"
-          }
-        })
-          .then(response => {
-            this.openBottleneckSurveys = response.data.surveys;
-          })
-          .catch(error => {
-            const { status, data } = error.response;
-            displayError({
-              title: "Backend Error",
-              message: `${status}: ${data.message || data}`
-            });
-          });
-      }
-    },
-    displayCurrentSurvey(current) {
-      return current ? current.substr(0, current.length - 1) : "";
-    },
-    displayCurrentChainage(from, to) {
-      return from / 10 + " - " + to / 10;
-    }
-  },
-  mounted() {
-    this.$store.dispatch("bottlenecks/loadBottlenecks");
-  }
-};
-</script>
-
-<style lang="sass" scoped>
-.bottleneck-list
-  overflow-y: auto
-  max-height: 500px
-
-.surveys
-  max-height: 0
-  overflow: hidden
-  transition: max-height 0.3s ease
-
-.surveys.open
-  max-height: 999px
-
-.sort-link
-  color: #444
-  font-weight: bold
-</style>
--- a/client/src/components/map/Contextbox.vue	Thu Nov 22 07:28:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-<template>
-    <div :class="style">
-        <div @click="close" class="ui-element close-contextbox text-muted">
-            <i class="fa fa-close"></i>
-        </div>
-        <Bottlenecks v-if="showInContextBox === 'bottlenecks'"></Bottlenecks>
-        <Imports v-if="showInContextBox === 'imports'"></Imports>
-        <Staging v-if="showInContextBox === 'staging'"></Staging>
-    </div>
-</template>
-
-<script>
-/*
- * This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "contextbox",
-  components: {
-    Bottlenecks: () => import("./Bottlenecks"),
-    Imports: () => import("./imports/Imports.vue"),
-    Staging: () => import("./Staging.vue")
-  },
-  computed: {
-    ...mapState("application", ["showSearchbarLastState", "showInContextBox"]),
-    style() {
-      return [
-        "ui-element shadow-xs contextbox ml-3",
-        {
-          contextboxcollapsed: !this.showInContextBox,
-          contextboxextended: this.showInContextBox,
-          "rounded-bottom": this.showInContextBox !== "imports",
-          rounded: this.showInContextBox === "imports"
-        }
-      ];
-    }
-  },
-  methods: {
-    close() {
-      this.$store.commit("application/showInContextBox", null);
-      this.$store.commit(
-        "application/showSearchbar",
-        this.showSearchbarLastState
-      );
-    }
-  }
-};
-</script>
-
-<style lang="sass" scoped>
-.contextbox
-  position: relative
-  background-color: #ffffff
-  opacity: $slight-transparent
-  transition: left 0.3s ease
-  overflow: hidden
-  background: #fff
-
-.contextboxcollapsed
-  width: 0
-  height: 0
-  transition: $transition-fast
-
-.contextboxextended
-  min-width: 600px
-
-.close-contextbox
-  position: absolute
-  z-index: 2
-  right: 0
-  top: 7px
-  height: $icon-width
-  width: $icon-height
-  display: none
-
-.contextboxextended .close-contextbox
-  display: block
-</style>
--- a/client/src/components/map/Staging.vue	Thu Nov 22 07:28:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-<template>
-    <div>
-        <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-          <i class="fa fa-list-ol mr-2"></i>
-          Staging Area
-        </h6>
-        <table class="table mb-0">
-            <thead>
-                <tr>
-                    <th>Name</th>
-                    <th>Datatype</th>
-                    <th>Importdate</th>
-                    <th>ImportID</th>
-                    <th>&nbsp;</th>
-                </tr>
-            </thead>
-            <tbody v-if="filteredData.length">
-                <tr v-for="data in filteredData" :key="data.id">
-                    <td>
-                        <a @click="zoomTo(data.location)" href="#">{{ data.name }}</a>
-                    </td>
-                    <td>{{ data.type }}</td>
-                    <td>{{ data.date }}</td>
-                    <td>{{ data.importID }}</td>
-                    <td>
-                        <button class="btn btn-outline-info">
-                            <i class="fa fa-thumbs-up"></i>
-                        </button>
-                        &nbsp;
-                        <button class="btn btn-outline-info">
-                            <i class="fa fa-thumbs-down"></i>
-                        </button>
-                    </td>
-                </tr>
-            </tbody>
-            <tbody v-else>
-                <tr>
-                    <td class="text-center" colspan="6">No results.</td>
-                </tr>
-            </tbody>
-        </table>
-        <div class="p-3" v-if="filteredData.length">
-            <button class="btn btn-info">Confirm</button>
-        </div>
-    </div>
-</template>
-
-<script>
-import { mapState } from "vuex";
-
-const demodata = [
-  {
-    id: 1,
-    name: "B1",
-    date: "2018-11-19 10:23",
-    location: [16.5364, 48.1471],
-    status: "Not approved",
-    importID: "123456789",
-    type: "bottleneck"
-  },
-  {
-    id: 2,
-    name: "B2",
-    date: "2018-11-19 10:24",
-    location: [16.5364, 48.1472],
-    status: "Not approved",
-    importID: "123456789",
-    type: "bottleneck"
-  },
-  {
-    id: 3,
-    name: "s1",
-    date: "2018-11-13 10:25",
-    location: [16.5364, 48.1473],
-    status: "Not approved",
-    importID: "987654321",
-    type: "soundingresult"
-  },
-  {
-    id: 4,
-    name: "s2",
-    date: "2018-11-13 10:26",
-    location: [16.5364, 48.1474],
-    status: "Not approved",
-    importID: "987654321",
-    type: "soundingresult"
-  }
-];
-
-export default {
-  computed: {
-    ...mapState("application", ["searchQuery"]),
-    filteredData() {
-      return demodata.filter(data => {
-        const nameFound = data.name
-          .toLowerCase()
-          .includes(this.searchQuery.toLowerCase());
-        const dateFound = data.date
-          .toLowerCase()
-          .includes(this.searchQuery.toLowerCase());
-        const locationFound = data.location.find(coord => {
-          return coord
-            .toString()
-            .toLowerCase()
-            .includes(this.searchQuery.toLowerCase());
-        });
-        const statusFound = data.status
-          .toLowerCase()
-          .includes(this.searchQuery.toLowerCase());
-        const importIDFound = data.importID
-          .toLowerCase()
-          .includes(this.searchQuery.toLowerCase());
-        const typeFound = data.type
-          .toLowerCase()
-          .includes(this.searchQuery.toLowerCase());
-
-        return (
-          nameFound ||
-          dateFound ||
-          locationFound ||
-          statusFound ||
-          importIDFound ||
-          typeFound
-        );
-      });
-    }
-  },
-  methods: {
-    zoomTo(coordinates) {
-      this.$store.commit("map/moveMap", {
-        coordinates: coordinates,
-        zoom: 17,
-        preventZoomOut: true
-      });
-    }
-  }
-};
-</script>
-
-<style lang="sass" scoped>
-.table th,
-td
-  font-size: 0.9rem
-  border-top: 0px !important
-  border-bottom-width: 1px
-  text-align: left
-  padding: 0.5rem !important
-</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/contextbox/Bottlenecks.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -0,0 +1,233 @@
+<template>
+    <div>
+        <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+          <i class="fa fa-ship mr-2"></i>
+          Bottlenecks
+        </h6>
+        <div class="row p-2 text-left small">
+            <div class="col-5">
+                <a href="#" @click="sortBy('name')" class="sort-link">Name</a>
+                <i :class="sortClass" v-if="sortColumn === 'name'"></i>
+            </div>
+            <div class="col-2">
+                <a
+                    href="#"
+                    @click="sortBy('latestMeasurement')"
+                    class="sort-link"
+                >Latest Measurement</a>
+                <i :class="sortClass" v-if="sortColumn === 'latestMeasurement'"></i>
+            </div>
+            <div class="col-3">
+                <a href="#" @click="sortBy('chainage')" class="sort-link">Chainage</a>
+                <i :class="sortClass" v-if="sortColumn === 'chainage'"></i>
+            </div>
+            <div class="col-2"></div>
+        </div>
+        <div class="bottleneck-list small text-left" v-if="filteredAndSortedBottlenecks().length">
+            <div
+                v-for="bottleneck in filteredAndSortedBottlenecks()"
+                :key="bottleneck.properties.name"
+                class="border-top row mx-0 py-2"
+            >
+                <div class="col-5 text-left">
+                    <a
+                        href="#"
+                        class="d-block"
+                        @click="moveToBottleneck(bottleneck)"
+                    >{{ bottleneck.properties.name }}</a>
+                </div>
+                <div class="col-2">{{ displayCurrentSurvey(bottleneck.properties.current) }}</div>
+                <div
+                    class="col-3"
+                >{{ displayCurrentChainage(bottleneck.properties.from, bottleneck.properties.from) }}</div>
+                <div class="col-2 text-right">
+                    <button
+                        type="button"
+                        class="btn btn-sm btn-outline-secondary"
+                        @click="toggleBottleneck(bottleneck.properties.name)"
+                    >
+                        <i class="fa fa-angle-down"></i>
+                    </button>
+                </div>
+                <div
+                    :class="['col-12', 'surveys', {open: openBottleneck === bottleneck.properties.name}]"
+                >
+                    <a
+                        href="#"
+                        class="d-block p-2"
+                        v-for="(survey, index) in openBottleneckSurveys"
+                        :key="index"
+                        @click="selectSurvey(survey, bottleneck)"
+                    >{{ survey.date_info }}</a>
+                </div>
+            </div>
+        </div>
+        <div v-else class="small text-center py-3 border-top">
+            No results.
+        </div>
+    </div>
+</template>
+
+<script>
+/*
+ * This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+import { HTTP } from "../../../lib/http";
+import { displayError } from "../../../lib/errors.js";
+
+export default {
+  name: "bottlenecks",
+  data() {
+    return {
+      sortColumn: "name",
+      sortDirection: "ASC",
+      openBottleneck: null,
+      openBottleneckSurveys: null
+    };
+  },
+  computed: {
+    ...mapState("application", ["searchQuery", "showSearchbarLastState"]),
+    ...mapState("bottlenecks", ["bottlenecks"]),
+    sortClass() {
+      return [
+        "fa ml-1",
+        {
+          "fa-sort-amount-asc": this.sortDirection === "ASC",
+          "fa-sort-amount-desc": this.sortDirection === "DESC"
+        }
+      ];
+    }
+  },
+  methods: {
+    filteredAndSortedBottlenecks() {
+      return this.bottlenecks
+        .filter(bn => {
+          return bn.properties.name
+            .toLowerCase()
+            .includes(this.searchQuery.toLowerCase());
+        })
+        .sort((bnA, bnB) => {
+          switch (this.sortColumn) {
+            case "name":
+              if (
+                bnA.properties.name.toLowerCase() <
+                bnB.properties.name.toLowerCase()
+              )
+                return this.sortDirection === "ASC" ? -1 : 1;
+              if (
+                bnA.properties.name.toLowerCase() >
+                bnB.properties.name.toLowerCase()
+              )
+                return this.sortDirection === "ASC" ? 1 : -1;
+              return 0;
+
+            case "latestMeasurement": {
+              if (
+                (bnA.properties.current || "") < (bnB.properties.current || "")
+              )
+                return this.sortDirection === "ASC" ? -1 : 1;
+              if (
+                (bnA.properties.current || "") > (bnB.properties.current || "")
+              )
+                return this.sortDirection === "ASC" ? 1 : -1;
+              return 0;
+            }
+
+            case "chainage":
+              if (bnA.properties.from < bnB.properties.from)
+                return this.sortDirection === "ASC" ? -1 : 1;
+              if (bnA.properties.from > bnB.properties.from)
+                return this.sortDirection === "ASC" ? 1 : -1;
+              return 0;
+
+            default:
+              return 0;
+          }
+        });
+    },
+    selectSurvey(survey, bottleneck) {
+      this.$store.dispatch(
+        "bottlenecks/setSelectedBottleneck",
+        bottleneck.properties.name
+      );
+      this.$store.commit("bottlenecks/setSelectedSurvey", survey);
+      this.moveToBottleneck(bottleneck);
+    },
+    moveToBottleneck(bottleneck) {
+      this.$store.commit("map/moveMap", {
+        coordinates: bottleneck.geometry.coordinates,
+        zoom: 17,
+        preventZoomOut: true
+      });
+    },
+    sortBy(column) {
+      this.sortColumn = column;
+      this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC";
+    },
+    toggleBottleneck(name) {
+      this.openBottleneckSurveys = null;
+      if (name === this.openBottleneck) {
+        this.openBottleneck = null;
+      } else {
+        this.openBottleneck = name;
+
+        HTTP.get("/surveys/" + name, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.openBottleneckSurveys = response.data.surveys;
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: "Backend Error",
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    },
+    displayCurrentSurvey(current) {
+      return current ? current.substr(0, current.length - 1) : "";
+    },
+    displayCurrentChainage(from, to) {
+      return from / 10 + " - " + to / 10;
+    }
+  },
+  mounted() {
+    this.$store.dispatch("bottlenecks/loadBottlenecks");
+  }
+};
+</script>
+
+<style lang="sass" scoped>
+.bottleneck-list
+  overflow-y: auto
+  max-height: 500px
+
+.surveys
+  max-height: 0
+  overflow: hidden
+  transition: max-height 0.3s ease
+
+.surveys.open
+  max-height: 999px
+
+.sort-link
+  color: #444
+  font-weight: bold
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/contextbox/Contextbox.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -0,0 +1,90 @@
+<template>
+    <div :class="style">
+        <div @click="close" class="ui-element close-contextbox text-muted">
+            <i class="fa fa-close"></i>
+        </div>
+        <Bottlenecks v-if="showInContextBox === 'bottlenecks'"></Bottlenecks>
+        <Imports v-if="showInContextBox === 'imports'"></Imports>
+        <Staging v-if="showInContextBox === 'staging'"></Staging>
+    </div>
+</template>
+
+<script>
+/*
+ * This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "contextbox",
+  components: {
+    Bottlenecks: () => import("./Bottlenecks"),
+    Imports: () => import("./imports/Imports.vue"),
+    Staging: () => import("./Staging.vue")
+  },
+  computed: {
+    ...mapState("application", ["showSearchbarLastState", "showInContextBox"]),
+    style() {
+      return [
+        "ui-element shadow-xs contextbox ml-3",
+        {
+          contextboxcollapsed: !this.showInContextBox,
+          contextboxextended: this.showInContextBox,
+          "rounded-bottom": this.showInContextBox !== "imports",
+          rounded: this.showInContextBox === "imports"
+        }
+      ];
+    }
+  },
+  methods: {
+    close() {
+      this.$store.commit("application/showInContextBox", null);
+      this.$store.commit(
+        "application/showSearchbar",
+        this.showSearchbarLastState
+      );
+    }
+  }
+};
+</script>
+
+<style lang="sass" scoped>
+.contextbox
+  position: relative
+  background-color: #ffffff
+  opacity: $slight-transparent
+  transition: left 0.3s ease
+  overflow: hidden
+  background: #fff
+
+.contextboxcollapsed
+  width: 0
+  height: 0
+  transition: $transition-fast
+
+.contextboxextended
+  min-width: 600px
+
+.close-contextbox
+  position: absolute
+  z-index: 2
+  right: 0
+  top: 7px
+  height: $icon-width
+  width: $icon-height
+  display: none
+
+.contextboxextended .close-contextbox
+  display: block
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/contextbox/Staging.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -0,0 +1,148 @@
+<template>
+    <div>
+        <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+          <i class="fa fa-list-ol mr-2"></i>
+          Staging Area
+        </h6>
+        <table class="table mb-0">
+            <thead>
+                <tr>
+                    <th>Name</th>
+                    <th>Datatype</th>
+                    <th>Importdate</th>
+                    <th>ImportID</th>
+                    <th>&nbsp;</th>
+                </tr>
+            </thead>
+            <tbody v-if="filteredData.length">
+                <tr v-for="data in filteredData" :key="data.id">
+                    <td>
+                        <a @click="zoomTo(data.location)" href="#">{{ data.name }}</a>
+                    </td>
+                    <td>{{ data.type }}</td>
+                    <td>{{ data.date }}</td>
+                    <td>{{ data.importID }}</td>
+                    <td>
+                        <button class="btn btn-outline-info">
+                            <i class="fa fa-thumbs-up"></i>
+                        </button>
+                        &nbsp;
+                        <button class="btn btn-outline-info">
+                            <i class="fa fa-thumbs-down"></i>
+                        </button>
+                    </td>
+                </tr>
+            </tbody>
+            <tbody v-else>
+                <tr>
+                    <td class="text-center" colspan="6">No results.</td>
+                </tr>
+            </tbody>
+        </table>
+        <div class="p-3" v-if="filteredData.length">
+            <button class="btn btn-info">Confirm</button>
+        </div>
+    </div>
+</template>
+
+<script>
+import { mapState } from "vuex";
+
+const demodata = [
+  {
+    id: 1,
+    name: "B1",
+    date: "2018-11-19 10:23",
+    location: [16.5364, 48.1471],
+    status: "Not approved",
+    importID: "123456789",
+    type: "bottleneck"
+  },
+  {
+    id: 2,
+    name: "B2",
+    date: "2018-11-19 10:24",
+    location: [16.5364, 48.1472],
+    status: "Not approved",
+    importID: "123456789",
+    type: "bottleneck"
+  },
+  {
+    id: 3,
+    name: "s1",
+    date: "2018-11-13 10:25",
+    location: [16.5364, 48.1473],
+    status: "Not approved",
+    importID: "987654321",
+    type: "soundingresult"
+  },
+  {
+    id: 4,
+    name: "s2",
+    date: "2018-11-13 10:26",
+    location: [16.5364, 48.1474],
+    status: "Not approved",
+    importID: "987654321",
+    type: "soundingresult"
+  }
+];
+
+export default {
+  computed: {
+    ...mapState("application", ["searchQuery"]),
+    filteredData() {
+      return demodata.filter(data => {
+        const nameFound = data.name
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+        const dateFound = data.date
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+        const locationFound = data.location.find(coord => {
+          return coord
+            .toString()
+            .toLowerCase()
+            .includes(this.searchQuery.toLowerCase());
+        });
+        const statusFound = data.status
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+        const importIDFound = data.importID
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+        const typeFound = data.type
+          .toLowerCase()
+          .includes(this.searchQuery.toLowerCase());
+
+        return (
+          nameFound ||
+          dateFound ||
+          locationFound ||
+          statusFound ||
+          importIDFound ||
+          typeFound
+        );
+      });
+    }
+  },
+  methods: {
+    zoomTo(coordinates) {
+      this.$store.commit("map/moveMap", {
+        coordinates: coordinates,
+        zoom: 17,
+        preventZoomOut: true
+      });
+    }
+  }
+};
+</script>
+
+<style lang="sass" scoped>
+.table th,
+td
+  font-size: 0.9rem
+  border-top: 0px !important
+  border-bottom-width: 1px
+  text-align: left
+  padding: 0.5rem !important
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/map/contextbox/imports/Imports.vue	Thu Nov 22 07:40:23 2018 +0100
@@ -0,0 +1,259 @@
+<template>
+    <div>
+        <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+          <i class="fa fa-upload mr-2"></i>
+          Import Soundingresults
+        </h6>
+        <div v-if="editState" class="ml-auto mr-auto mt-4 w-90">
+            <div class="d-flex flex-row input-group mb-4">
+                <div class="offset-r">
+                    <label for="bottleneck" class="label-text" id="bottlenecklabel">Bottleneck</label>
+                </div>
+                <input
+                    id="bottleneck"
+                    type="text"
+                    class="form-control"
+                    placeholder="Name of Bottleneck"
+                    aria-label="bottleneck"
+                    aria-describedby="bottlenecklabel"
+                    v-model="bottleneck"
+                >
+            </div>
+            <div class="d-flex flex-row input-group mb-4">
+                <div class="offset-r">
+                    <label class="label-text" for="importdate" id="importdatelabel">Date</label>
+                </div>
+                <input
+                    id="importdate"
+                    type="date"
+                    class="form-control"
+                    placeholder="Date of import"
+                    aria-label="bottleneck"
+                    aria-describedby="bottlenecklabel"
+                    v-model="importDate"
+                >
+            </div>
+            <div class="d-flex flex-row input-group mb-4">
+                <div class="offset-r">
+                    <label class="label-text" for="depthreference">Depth reference</label>
+                </div>
+                <select v-model="depthReference" class="custom-select" id="depthreference">
+                    <option
+                        v-for="option in this.$options.depthReferenceOptions"
+                        :key="option"
+                    >{{option}}</option>
+                </select>
+            </div>
+        </div>
+        <div class="w-90 ml-auto mr-auto mt-4 mb-4">
+            <div v-if="uploadState" class="d-flex flex-row input-group mb-4">
+                <div class="custom-file">
+                    <input
+                        type="file"
+                        @change="fileSelected"
+                        class="custom-file-input"
+                        id="uploadFile"
+                    >
+                    <label class="custom-file-label" for="uploadFile">{{uploadLabel}}</label>
+                </div>
+            </div>
+            <div class="buttons text-right">
+                <a
+                    v-if="editState"
+                    download="meta.json"
+                    :href="dataLink "
+                    class="btn btn-outline-info pull-left"
+                >Download Meta.json</a>
+                <button
+                    v-if="editState"
+                    @click="deleteTempData"
+                    class="btn btn-danger"
+                    type="button"
+                >Cancel Upload</button>
+                <button
+                    :disabled="disableUpload"
+                    @click="submit"
+                    class="btn btn-info"
+                    type="button"
+                >{{uploadState?"Upload":"Confirm"}}</button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import { HTTP } from "../../../../lib/http";
+import { displayError, displayInfo } from "../../../../lib/errors.js";
+
+const defaultLabel = "Choose .zip-file";
+const IMPORTSTATE = { UPLOAD: "UPLOAD", EDIT: "EDIT" };
+
+export default {
+  name: "imports",
+  data() {
+    return {
+      importState: IMPORTSTATE.UPLOAD,
+      depthReference: "",
+      bottleneck: "",
+      importDate: "",
+      uploadLabel: defaultLabel,
+      uploadFile: null,
+      disableUpload: false,
+      token: null
+    };
+  },
+  methods: {
+    initialState() {
+      this.importState = IMPORTSTATE.UPLOAD;
+      this.depthReference = "";
+      this.bottleneck = "";
+      this.importDate = "";
+      this.uploadLabel = defaultLabel;
+      this.uploadFile = null;
+      this.disableUpload = false;
+      this.token = null;
+    },
+    fileSelected(e) {
+      const files = e.target.files || e.dataTransfer.files;
+      if (!files) return;
+      this.uploadLabel = files[0].name;
+      this.uploadFile = files[0];
+    },
+    deleteTempData() {
+      HTTP.delete("/imports/soundingresult-upload/" + this.token, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token")
+        }
+      })
+        .then(() => {
+          this.initialState();
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: "Backend Error",
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    submit() {
+      if (!this.uploadFile || this.disableUpload) return;
+      if (this.importState === IMPORTSTATE.UPLOAD) {
+        this.upload();
+      } else {
+        this.confirm();
+      }
+    },
+    upload() {
+      let formData = new FormData();
+      formData.append("soundingresult", this.uploadFile);
+      HTTP.post("/imports/soundingresult-upload", formData, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-Type": "multipart/form-data"
+        }
+      })
+        .then(response => {
+          const { bottleneck, date } = response.data.meta;
+          const depthReference = response.data.meta["depth-reference"];
+          this.importState = IMPORTSTATE.EDIT;
+          this.bottleneck = bottleneck;
+          this.depthReference = depthReference;
+          this.importDate = new Date(date).toISOString().split("T")[0];
+          this.token = response.data.token;
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          const messages = data.messages ? data.messages.join(", ") : "";
+          displayError({
+            title: "Backend Error",
+            message: `${status}: ${messages}`
+          });
+        });
+    },
+    confirm() {
+      let formData = new FormData();
+      formData.append("token", this.token);
+      ["bottleneck", "importDate", "depthReference"].forEach(x => {
+        if (this[x]) formData.append(x, this[x]);
+      });
+      HTTP.post("/imports/soundingresult", formData, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-Type": "multipart/form-data"
+        }
+      })
+        .then(() => {
+          displayInfo({
+            title: "Import",
+            message: "Starting import for " + this.bottleneck
+          });
+          this.initialState();
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: "Backend Error",
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  },
+  computed: {
+    editState() {
+      return this.importState === IMPORTSTATE.EDIT;
+    },
+    uploadState() {
+      return this.importState === IMPORTSTATE.UPLOAD;
+    },
+    dataLink() {
+      return (
+        "data:text/json;charset=utf-8," +
+        encodeURIComponent(
+          JSON.stringify({
+            depthReference: this.depthReference,
+            bottleneck: this.bottleneck,
+            date: this.importDate
+          })
+        )
+      );
+    }
+  },
+  depthReferenceOptions: [
+    "",
+    "NAP",
+    "KP",
+    "FZP",
+    "ADR",
+    "TAW",
+    "PUL",
+    "NGM",
+    "ETRS",
+    "POT",
+    "LDC",
+    "HDC",
+    "ZPG",
+    "GLW",
+    "HSW",
+    "LNW",
+    "HNW",
+    "IGN",
+    "WGS",
+    "RN",
+    "HBO"
+  ]
+};
+</script>
+
+<style lang="sass" scoped>
+.offset-r
+  margin-right: $large-offset
+
+.buttons button
+  margin-left: $offset !important
+
+.label-text
+  width: 10rem
+  text-align: left
+  line-height: 2.25rem
+</style>
--- a/client/src/components/map/imports/Importqueue.vue	Thu Nov 22 07:28:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +0,0 @@
-<template>
-    <div class="d-flex flex-row">
-        <div :class="spacerStyle"></div>
-        <div class="mt-3 mx-auto">
-            <div class="card importqueuecard">
-                <div class="card-header shadow-sm text-white bg-info mb-3">Importqueue</div>
-                <div class="card-body importcardbody">
-                    <div class="card-body importcardbody">
-                        <div class="searchandfilter d-flex flex-row">
-                            <div class="searchgroup input-group">
-                                <div class="input-group-prepend">
-                                    <span class="input-group-text" id="search">
-                                        <i class="fa fa-search"></i>
-                                    </span>
-                                </div>
-                                <input
-                                    type="text"
-                                    class="form-control"
-                                    placeholder=""
-                                    aria-label="Search"
-                                    aria-describedby="search"
-                                >
-                            </div>
-                            <div class="filters">
-                                <button
-                                    @click="setFilter('successful')"
-                                    :class="successfulStyle"
-                                >Successful</button>
-                                <button @click="setFilter('failed')" :class="failedStyle">Failed</button>
-                                <button @click="setFilter('pending')" :class="pendingStyle">Pending</button>
-                            </div>
-                        </div>
-                        <table class="table">
-                            <thead>
-                                <tr>
-                                    <th>Enqueued</th>
-                                    <th>Kind</th>
-                                    <th>User</th>
-                                    <th>State</th>
-                                </tr>
-                            </thead>
-                            <tbody>
-                                <tr v-for="job in imports" :key="job.id">
-                                    <td>{{job.enqueued}}</td>
-                                    <td>{{job.kind}}</td>
-                                    <td>{{job.user}}</td>
-                                    <td>{{job.state}}</td>
-                                </tr>
-                            </tbody>
-                        </table>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script>
-import { displayError } from "../../../lib/errors.js";
-import { mapState } from "vuex";
-
-export default {
-  name: "importqueue",
-  data() {
-    return {
-      successful: false,
-      failed: false,
-      pending: false
-    };
-  },
-  methods: {
-    setFilter(name) {
-      this[name] = !this[name];
-      const allSet = this.successful && this.failed && this.pending;
-      if (allSet) {
-        this.all = false;
-        this.successful = false;
-        this.failed = false;
-        this.pending = false;
-      }
-    }
-  },
-  computed: {
-    ...mapState("imports", ["imports"]),
-    ...mapState("application", ["showSidebar"]),
-    spacerStyle() {
-      return [
-        "spacer ml-3",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    },
-    successfulStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.successful,
-        "btn-dark": this.successful
-      };
-    },
-    pendingStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.pending,
-        "btn-dark": this.pending
-      };
-    },
-    failedStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.failed,
-        "btn-dark": this.failed
-      };
-    }
-  },
-  mounted() {
-    this.$store.dispatch("imports/getImports").catch(error => {
-      const { status, data } = error.response;
-      displayError({
-        title: "Backend Error",
-        message: `${status}: ${data.message || data}`
-      });
-    });
-  }
-};
-</script>
-
-<style lang="sass" scoped>
-.spacer
-  height: 100vh
-
-.spacer-collapsed
-  min-width: $icon-width + $offset
-  transition: $transition-fast
-
-.spacer-expanded
-  min-width: $sidebar-width + $offset
-
-.importqueuecard
-  width: 80vw
-  min-height: 20rem
-
-.card-body
-  width: 100%
-  margin-left: auto
-  margin-right: auto
-
-.searchandfilter
-  position: relative
-  margin-bottom: $xx-large-offset
-
-.filters
-  position: absolute
-  right: 0
-
-.filters button
-  margin-right: $small-offset
-
-.table td,
-.table th
-  border-top: 0 !important
-  text-align: left
-  padding: $small-offset !important
-
-.searchgroup
-  position: absolute
-  left: 0
-  width: 50%
-</style>
--- a/client/src/components/map/imports/Imports.vue	Thu Nov 22 07:28:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,259 +0,0 @@
-<template>
-    <div>
-        <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-          <i class="fa fa-upload mr-2"></i>
-          Import Soundingresults
-        </h6>
-        <div v-if="editState" class="ml-auto mr-auto mt-4 w-90">
-            <div class="d-flex flex-row input-group mb-4">
-                <div class="offset-r">
-                    <label for="bottleneck" class="label-text" id="bottlenecklabel">Bottleneck</label>
-                </div>
-                <input
-                    id="bottleneck"
-                    type="text"
-                    class="form-control"
-                    placeholder="Name of Bottleneck"
-                    aria-label="bottleneck"
-                    aria-describedby="bottlenecklabel"
-                    v-model="bottleneck"
-                >
-            </div>
-            <div class="d-flex flex-row input-group mb-4">
-                <div class="offset-r">
-                    <label class="label-text" for="importdate" id="importdatelabel">Date</label>
-                </div>
-                <input
-                    id="importdate"
-                    type="date"
-                    class="form-control"
-                    placeholder="Date of import"
-                    aria-label="bottleneck"
-                    aria-describedby="bottlenecklabel"
-                    v-model="importDate"
-                >
-            </div>
-            <div class="d-flex flex-row input-group mb-4">
-                <div class="offset-r">
-                    <label class="label-text" for="depthreference">Depth reference</label>
-                </div>
-                <select v-model="depthReference" class="custom-select" id="depthreference">
-                    <option
-                        v-for="option in this.$options.depthReferenceOptions"
-                        :key="option"
-                    >{{option}}</option>
-                </select>
-            </div>
-        </div>
-        <div class="w-90 ml-auto mr-auto mt-4 mb-4">
-            <div v-if="uploadState" class="d-flex flex-row input-group mb-4">
-                <div class="custom-file">
-                    <input
-                        type="file"
-                        @change="fileSelected"
-                        class="custom-file-input"
-                        id="uploadFile"
-                    >
-                    <label class="custom-file-label" for="uploadFile">{{uploadLabel}}</label>
-                </div>
-            </div>
-            <div class="buttons text-right">
-                <a
-                    v-if="editState"
-                    download="meta.json"
-                    :href="dataLink "
-                    class="btn btn-outline-info pull-left"
-                >Download Meta.json</a>
-                <button
-                    v-if="editState"
-                    @click="deleteTempData"
-                    class="btn btn-danger"
-                    type="button"
-                >Cancel Upload</button>
-                <button
-                    :disabled="disableUpload"
-                    @click="submit"
-                    class="btn btn-info"
-                    type="button"
-                >{{uploadState?"Upload":"Confirm"}}</button>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script>
-import { HTTP } from "../../../lib/http";
-import { displayError, displayInfo } from "../../../lib/errors.js";
-
-const defaultLabel = "Choose .zip-file";
-const IMPORTSTATE = { UPLOAD: "UPLOAD", EDIT: "EDIT" };
-
-export default {
-  name: "imports",
-  data() {
-    return {
-      importState: IMPORTSTATE.UPLOAD,
-      depthReference: "",
-      bottleneck: "",
-      importDate: "",
-      uploadLabel: defaultLabel,
-      uploadFile: null,
-      disableUpload: false,
-      token: null
-    };
-  },
-  methods: {
-    initialState() {
-      this.importState = IMPORTSTATE.UPLOAD;
-      this.depthReference = "";
-      this.bottleneck = "";
-      this.importDate = "";
-      this.uploadLabel = defaultLabel;
-      this.uploadFile = null;
-      this.disableUpload = false;
-      this.token = null;
-    },
-    fileSelected(e) {
-      const files = e.target.files || e.dataTransfer.files;
-      if (!files) return;
-      this.uploadLabel = files[0].name;
-      this.uploadFile = files[0];
-    },
-    deleteTempData() {
-      HTTP.delete("/imports/soundingresult-upload/" + this.token, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token")
-        }
-      })
-        .then(() => {
-          this.initialState();
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: "Backend Error",
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    },
-    submit() {
-      if (!this.uploadFile || this.disableUpload) return;
-      if (this.importState === IMPORTSTATE.UPLOAD) {
-        this.upload();
-      } else {
-        this.confirm();
-      }
-    },
-    upload() {
-      let formData = new FormData();
-      formData.append("soundingresult", this.uploadFile);
-      HTTP.post("/imports/soundingresult-upload", formData, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-Type": "multipart/form-data"
-        }
-      })
-        .then(response => {
-          const { bottleneck, date } = response.data.meta;
-          const depthReference = response.data.meta["depth-reference"];
-          this.importState = IMPORTSTATE.EDIT;
-          this.bottleneck = bottleneck;
-          this.depthReference = depthReference;
-          this.importDate = new Date(date).toISOString().split("T")[0];
-          this.token = response.data.token;
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          const messages = data.messages ? data.messages.join(", ") : "";
-          displayError({
-            title: "Backend Error",
-            message: `${status}: ${messages}`
-          });
-        });
-    },
-    confirm() {
-      let formData = new FormData();
-      formData.append("token", this.token);
-      ["bottleneck", "importDate", "depthReference"].forEach(x => {
-        if (this[x]) formData.append(x, this[x]);
-      });
-      HTTP.post("/imports/soundingresult", formData, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-Type": "multipart/form-data"
-        }
-      })
-        .then(() => {
-          displayInfo({
-            title: "Import",
-            message: "Starting import for " + this.bottleneck
-          });
-          this.initialState();
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: "Backend Error",
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  },
-  computed: {
-    editState() {
-      return this.importState === IMPORTSTATE.EDIT;
-    },
-    uploadState() {
-      return this.importState === IMPORTSTATE.UPLOAD;
-    },
-    dataLink() {
-      return (
-        "data:text/json;charset=utf-8," +
-        encodeURIComponent(
-          JSON.stringify({
-            depthReference: this.depthReference,
-            bottleneck: this.bottleneck,
-            date: this.importDate
-          })
-        )
-      );
-    }
-  },
-  depthReferenceOptions: [
-    "",
-    "NAP",
-    "KP",
-    "FZP",
-    "ADR",
-    "TAW",
-    "PUL",
-    "NGM",
-    "ETRS",
-    "POT",
-    "LDC",
-    "HDC",
-    "ZPG",
-    "GLW",
-    "HSW",
-    "LNW",
-    "HNW",
-    "IGN",
-    "WGS",
-    "RN",
-    "HBO"
-  ]
-};
-</script>
-
-<style lang="sass" scoped>
-.offset-r
-  margin-right: $large-offset
-
-.buttons button
-  margin-left: $offset !important
-
-.label-text
-  width: 10rem
-  text-align: left
-  line-height: 2.25rem
-</style>
--- a/client/src/router.js	Thu Nov 22 07:28:21 2018 +0100
+++ b/client/src/router.js	Thu Nov 22 07:40:23 2018 +0100
@@ -22,10 +22,10 @@
 const Main = () => import("./components/map/Main.vue");
 const Usermanagement = () =>
   import("./components/admin/usermanagement/Usermanagement.vue");
-const Logs = () => import("./components/admin/logs.vue");
-const Importqueue = () => import("./components/map/imports/Importqueue.vue");
+const Logs = () => import("./components/admin/Logs.vue");
+const Importqueue = () => import("./components/admin/Importqueue.vue");
 const Systemconfiguration = () =>
-  import("./components/admin/systemconfiguration.vue");
+  import("./components/admin/Systemconfiguration.vue");
 
 Vue.use(Router);