changeset 1178:1ffb4b9ca5ee

merge
author Markus Kottlaender <markus@intevation.de>
date Thu, 15 Nov 2018 09:30:05 +0100
parents 48ae4458710d (current diff) 2014711330dd (diff)
children 320e2720fe3d
files client/src/imports/Importqueue.vue client/src/imports/Job.vue
diffstat 9 files changed, 228 insertions(+), 102 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/application/Sidebar.vue	Thu Nov 15 09:27:16 2018 +0100
+++ b/client/src/application/Sidebar.vue	Thu Nov 15 09:30:05 2018 +0100
@@ -1,8 +1,11 @@
 <template>
     <div :class="sidebarStyle">
-      <div @click="$store.commit('application/showSidebar', !showSidebar)" class="menubutton position-absolute d-flex justify-content-center">
-          <i class="ui-element d-print-none fa fa-bars"></i>
-      </div>
+        <div
+            @click="$store.commit('application/showSidebar', !showSidebar)"
+            class="menubutton position-absolute d-flex justify-content-center"
+        >
+            <i class="ui-element d-print-none fa fa-bars"></i>
+        </div>
         <div :class="menuStyle">
             <div class="menupoints" v-if="this.showSidebar">
                 <router-link to="/" class="text-body d-flex flex-row nav-link">
@@ -20,22 +23,31 @@
                     <router-link class="text-body d-flex flex-row nav-link" to="usermanagement">
                         <i class="fa fa-address-card-o align-self-center navicon"></i>Users
                     </router-link>
+                </div>
+                <div v-if="isWaterwayAdmin">
                     <router-link class="text-body d-flex flex-row nav-link" to="imports">
-                        <i class="fa fa-exchange align-self-center navicon"></i>Imports
+                        <i class="fa fa-upload align-self-center navicon"></i>Imports
                     </router-link>
+                </div>
+                <div v-if="isSysAdmin">
                     <router-link
                         class="text-body d-flex flex-row nav-link"
                         to="systemconfiguration"
                     >
                         <i class="fa fa-wrench align-self-center navicon"></i>Systemconfiguration
                     </router-link>
+                    <div class="nav-link d-flex menupadding text-muted">Systeminformation</div>
                     <router-link class="text-body d-flex flex-row nav-link" to="logs">
-                        <i class="fa fa-book align-self-center navicon"></i>Systeminformation
+                        <i class="fa fa-book align-self-center navicon"></i>Logs
+                    </router-link>
+                    <router-link class="text-body d-flex flex-row nav-link" to="importqueue">
+                        <i class="fa fa-exchange align-self-center navicon"></i>Importqueue
                     </router-link>
                 </div>
                 <hr>
                 <a href="#" @click="logoff" class="text-body d-flex flex-row nav-link">
-                    <i class="fa fa-power-off align-self-center navicon"></i> Logout {{ user }}
+                    <i class="fa fa-power-off align-self-center navicon"></i>
+                    Logout {{ user }}
                 </a>
             </div>
         </div>
@@ -64,7 +76,7 @@
   name: "sidebar",
   props: ["routeName"],
   computed: {
-    ...mapGetters("user", ["isSysAdmin"]),
+    ...mapGetters("user", ["isSysAdmin", "isWaterwayAdmin"]),
     ...mapState("user", ["user"]),
     ...mapState("application", ["showSidebar", "showBottlenecks"]),
     menuStyle() {
--- a/client/src/application/assets/application.scss	Thu Nov 15 09:27:16 2018 +0100
+++ b/client/src/application/assets/application.scss	Thu Nov 15 09:30:05 2018 +0100
@@ -24,7 +24,7 @@
 $identify-width: 20rem;
 $offset: 1rem;
 $searchbar-width: 50vw;
-$sidebar-height: 27rem;
+$sidebar-height: 32rem;
 $sidebar-width: 15rem;
 $slight-transparent: 0.96;
 $small-offset: 0.5rem;
@@ -33,6 +33,7 @@
 $transition-slow: 3s;
 $transition: 0.5s;
 $x-large-offset: 3rem;
+$xx-large-offset: 5rem;
 $x-small-offset: 0.25rem;
 
 .debug {
--- a/client/src/drawtool/Drawtool.vue	Thu Nov 15 09:27:16 2018 +0100
+++ b/client/src/drawtool/Drawtool.vue	Thu Nov 15 09:30:05 2018 +0100
@@ -137,6 +137,7 @@
           value: Math.round(length * 10) / 10
         });
       }
+      this.$store.commit("application/showIdentify", true);
     },
     enableCutTool() {
       const cutVectorSrc = this.getLayerByName("Cut Tool").data.getSource();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/importqueue/Importqueue.vue	Thu Nov 15 09:30:05 2018 +0100
@@ -0,0 +1,169 @@
+<template>
+    <div class="importqueue d-flex flex-row">
+        <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('all')" :class="allStyle">All</button>
+                            <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>
+</template>
+
+<script>
+import { displayError } from "../application/lib/errors.js";
+import { mapState } from "vuex";
+
+export default {
+  name: "importqueue",
+  data() {
+    return {
+      all: false,
+      successful: false,
+      failed: false,
+      pending: false
+    };
+  },
+  methods: {
+    setFilter(name) {
+      this[name] = !this[name];
+      const allSet = this.successful && this.failed && this.pending;
+      if (name === "all" || allSet) {
+        this.all = false;
+        this.successful = false;
+        this.failed = false;
+        this.pending = false;
+      }
+    }
+  },
+  computed: {
+    ...mapState("imports", ["imports"]),
+    allStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.all,
+        "btn-dark": this.all
+      };
+    },
+    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="scss" scoped>
+.importqueue {
+  margin-top: $offset;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.importqueuecard {
+  width: 60vw;
+  min-height: 20rem;
+}
+
+.card-body {
+  width: 90%;
+  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/imports/Importqueue.vue	Thu Nov 15 09:27:16 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-<template>
-    <div>
-        <h1>Import</h1>
-        <div class="d-flex content flex-column">
-            <div class="jobcontainer">
-                <Job v-if="imports.queued" type="Running" :jobs="imports.queued"></Job>
-                <Job v-if="imports.successful" type="Done" :jobs="imports.successful"></Job>
-                <Job v-if="imports.failed" type="Failed" :jobs="imports.failed"></Job>
-                <Job v-if="imports.scheduled" type="Scheduled" :jobs="imports.scheduled"></Job>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script>
-import { displayError } from "../application/lib/errors.js";
-import { mapState } from "vuex";
-import Job from "./Job";
-
-export default {
-  name: "importqueue",
-  components: {
-    Job
-  },
-  computed: {
-    ...mapState("imports", ["imports"])
-  },
-  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="scss" scoped>
-.jobcontainer {
-  margin-left: auto;
-  margin-right: auto;
-}
-</style>
--- a/client/src/imports/Job.vue	Thu Nov 15 09:27:16 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-<template>
-    <div class="job">
-        <h3 class="header">{{type}}</h3>
-        <table class="table table-responsive">
-            <thead>
-                <tr>
-                    <th>Enqueued</th>
-                    <th>Kind</th>
-                    <th>User</th>
-                </tr>
-            </thead>
-            <tbody>
-                <tr v-for="job in jobs" :key="job.id">
-                    <td>{{job.enqueued}}</td>
-                    <td>{{job.kind}}</td>
-                    <td>{{job.user}}</td>
-                </tr>
-            </tbody>
-        </table>
-    </div>
-</template>
-
-<script>
-export default {
-  name: "job",
-  props: ["type", "jobs"]
-};
-</script>
-
-<style lang="scss" scoped>
-.job {
-  position: relative;
-  text-align: left;
-}
-</style>
--- a/client/src/router.js	Thu Nov 15 09:27:16 2018 +0100
+++ b/client/src/router.js	Thu Nov 15 09:30:05 2018 +0100
@@ -26,6 +26,7 @@
 const Usermanagement = () => import("./usermanagement/Usermanagement.vue");
 const Logs = () => import("./logs/logs.vue");
 const Imports = () => import("./imports/Imports.vue");
+const Importqueue = () => import("./importqueue/Importqueue.vue");
 const Systemconfiguration = () =>
   import("./systemconfiguration/systemconfiguration.vue");
 
@@ -94,8 +95,24 @@
         requiresAuth: true
       },
       beforeEnter: (to, from, next) => {
-        const isSysadmin = store.getters["user/isSysAdmin"];
-        if (!isSysadmin) {
+        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
+        if (!isWaterwayAdmin) {
+          next("/");
+        } else {
+          next();
+        }
+      }
+    },
+    {
+      path: "/importqueue",
+      name: "importqueue",
+      component: Importqueue,
+      meta: {
+        requiresAuth: true
+      },
+      beforeEnter: (to, from, next) => {
+        const isWaterwayAdmin = store.getters["user/isSysAdmin"];
+        if (!isWaterwayAdmin) {
           next("/");
         } else {
           next();
--- a/client/src/store/imports.js	Thu Nov 15 09:27:16 2018 +0100
+++ b/client/src/store/imports.js	Thu Nov 15 09:30:05 2018 +0100
@@ -1,14 +1,14 @@
 /*
  * 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 
+ *
+ * Copyright (C) 2018 by via donau
  *   – Österreichische Wasserstraßen-Gesellschaft mbH
  * Software engineering by Intevation GmbH
- * 
+ *
  * Author(s):
  * Thomas Junk <thomas.junk@intevation.de>
  */
@@ -22,12 +22,7 @@
   },
   mutations: {
     setImports: (state, imports) => {
-      const groupedImports = imports.reduce((o, n) => {
-        if (!o[n.state]) o[n.state] = [];
-        o[n.state].push(n);
-        return o;
-      }, {});
-      state.imports = groupedImports;
+      state.imports = imports;
     }
   },
   actions: {
--- a/schema/gemma.sql	Thu Nov 15 09:27:16 2018 +0100
+++ b/schema/gemma.sql	Thu Nov 15 09:30:05 2018 +0100
@@ -533,17 +533,28 @@
 CREATE TYPE waterway.log_type AS ENUM ('info', 'warn', 'error');
 
 CREATE TABLE waterway.import_logs (
-    import_id int NOT NULL REFERENCES waterway.imports(id),
+    import_id int NOT NULL REFERENCES waterway.imports(id) ON DELETE CASCADE,
     time timestamp NOT NULL DEFAULT now(),
     kind waterway.log_type NOT NULL DEFAULT 'info',
     msg TEXT NOT NULL
 );
 
 CREATE TABLE waterway.track_imports (
-    import_id int      NOT NULL REFERENCES waterway.imports(id),
+    import_id int      NOT NULL REFERENCES waterway.imports(id) ON DELETE CASCADE,
     relation  regclass NOT NULL,
     key       int      NOT NULL,
     UNIQUE (relation, key)
 );
 
+CREATE FUNCTION waterway.del_import() RETURNS trigger AS
+$$
+BEGIN
+    EXECUTE format('DELETE FROM %I WHERE id = $1', OLD.relation) USING OLD.key;
+END;
+$$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER delete_import AFTER DELETE ON waterway.track_imports
+   FOR EACH ROW EXECUTE PROCEDURE waterway.del_import();
+
 COMMIT;