Mercurial > gemma
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;