Mercurial > gemma
changeset 1559:5d84dcb79a54
layout importqueue
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Wed, 12 Dec 2018 09:48:37 +0100 |
parents | 0ded4c56978e |
children | 70421380142d |
files | client/src/components/Importqueue.vue client/src/components/Importqueuedetail.vue client/src/components/importqueue/Importqueue.vue client/src/components/importqueue/Importqueuedetail.vue client/src/router.js |
diffstat | 5 files changed, 635 insertions(+), 638 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/Importqueue.vue Wed Dec 12 09:22:20 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,362 +0,0 @@ -<template> - <div class="d-flex flex-row"> - <div :class="spacerStyle"></div> - <div class="mt-3 importqueuecard flex-grow-1"> - <div class="card shadow-xs"> - <h6 - class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center" - > - <font-awesome-icon icon="tasks" class="mr-2"></font-awesome-icon> - <translate class="headline">Importqueue</translate> - </h6> - <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"> - <font-awesome-icon icon="search"></font-awesome-icon> - </span> - </div> - <input - v-model="searchQuery" - type="text" - class="form-control" - placeholder - aria-label="Search" - aria-describedby="search" - /> - </div> - <div class="filters"> - <button - @click="setFilter('successful')" - :class="successfulStyle" - > - <translate>Successful</translate> - </button> - <button @click="setFilter('failed')" :class="failedStyle"> - <translate>Failed</translate> - </button> - <button @click="setFilter('pending')" :class="pendingStyle"> - <translate>Pending</translate> - </button> - <button @click="setFilter('rejected')" :class="rejectedStyle"> - <translate>Rejected</translate> - </button> - <button @click="setFilter('accepted')" :class="acceptedStyle"> - <translate>Accepted</translate> - </button> - </div> - </div> - <div class="text-left d-flex flex-row w-50 border-bottom"> - <div class="header py-1 jobid mr-2"> - <translate>Id</translate> - </div> - <div class="header py-1 enqueued mr-2"> - <translate>Enqueued</translate> - </div> - <div class="header py-1 kind mr-2"> - <translate>Kind</translate> - </div> - <div class="header py-1 user mr-2"> - <translate>User</translate> - </div> - <div class="header py-1 signer mr-2"> - <translate>Signer</translate> - </div> - <div class="header py-1 state mr-2"> - <translate>State</translate> - </div> - </div> - <div class="text-left" v-for="job in filteredImports" :key="job.id"> - <Importqueuedetail :job="job"></Importqueuedetail> - </div> - <div> - <button @click="refresh" class="btn btn-info refresh"> - <translate>Refresh</translate> - </button> - </div> - </div> - </div> - </div> - </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@intevation.de> - */ -import { displayError } from "../lib/errors.js"; -import { mapState } from "vuex"; -import { HTTP } from "../lib/http.js"; -import Importqueuedetail from "./Importqueuedetail"; - -export default { - name: "importqueue", - components: { - Importqueuedetail - }, - data() { - return { - searchQuery: "", - successful: false, - failed: false, - pending: false, - rejected: false, - accepted: false - }; - }, - mounted() { - this.loadQueue(); - }, - methods: { - setFilter(name) { - this[name] = !this[name]; - const allSet = - this.successful && - this.failed && - this.pending && - this.accepted && - this.rejected; - if (allSet) { - this.successful = false; - this.failed = false; - this.pending = false; - this.accepted = false; - this.rejected = false; - } - }, - loadQueue() { - this.$store.dispatch("imports/getImports").catch(error => { - const { status, data } = error.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - }, - refresh() { - this.loadQueue(); - }, - showDetails(id) { - HTTP.get("/imports/" + id, { - headers: { "X-Gemma-Auth": localStorage.getItem("token") } - }) - .then(response => { - const { entries } = response.data; - this.entries = entries; - this.$modal.show("details"); - }) - .catch(error => { - const { status, data } = error.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - }, - close() { - this.$modal.hide("details"); - } - }, - computed: { - ...mapState("imports", ["imports"]), - ...mapState("application", ["showSidebar"]), - sortIcon() { - return this.sortAsc ? "sort-amount-down" : "sort-amount-up"; - }, - filteredImports() { - const filtered = this.imports - .filter(element => { - if (!this.searchQuery) return true; - return [(element.kind, element.user, element.enqueued)].some(x => { - return x.toLowerCase().includes(this.searchQuery.toLowerCase()); - }); - }) - .filter(y => { - if ( - !this.successful && - !this.failed && - !this.pending && - !this.accepted && - !this.rejected - ) - return true; - let filterCriteria = []; - if (this.successful) filterCriteria.push("successful"); - if (this.failed) filterCriteria.push("failed"); - if (this.pending) filterCriteria.push("pending"); - if (this.accepted) filterCriteria.push("accepted"); - if (this.rejected) filterCriteria.push("rejected"); - const result = filterCriteria.map(selectedState => { - return y.state === selectedState; - }); - return result.some(x => x); - }); - return filtered; - }, - 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 - }; - }, - rejectedStyle() { - return { - btn: true, - "btn-light": !this.rejected, - "btn-dark": this.rejected - }; - }, - acceptedStyle() { - return { - btn: true, - "btn-light": !this.accepted, - "btn-dark": this.accepted - }; - } - } -}; -</script> - -<style lang="scss" scoped> -.jobid { - width: 80px; -} - -.enqueued { - width: 120px; -} - -.user { - width: 80px; -} - -.signer { - width: 80px; -} - -.kind { - width: 80px; -} - -.state { - width: 80px; -} - -.header { - font-weight: bold; - font-size: 0.9em; -} - -.details thead { - display: block; -} -.details tbody { - display: block; -} - -.details tbody { - height: 260px; - overflow-y: auto; - overflow-x: hidden; -} - -.closebutton { - top: $small-offset; -} - -.refresh { - position: absolute; - right: $offset; - bottom: $offset; -} - -.spacer { - height: 100vh; -} - -.spacer-collapsed { - min-width: $icon-width + $offset; - transition: $transition-fast; -} - -.spacer-expanded { - min-width: $sidebar-width; -} - -.importqueuecard { - width: 97%; - margin-left: $offset; - margin-right: $offset; - 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: 45%; -} -</style>
--- a/client/src/components/Importqueuedetail.vue Wed Dec 12 09:22:20 2018 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,275 +0,0 @@ -<template> - <div class="entry d-flex flex-column py-1 border-bottom w-50"> - <div class="d-flex flex-row position-relative"> - <div @click="showDetails(job.id)" class="jobid ml-2 mt-2 mr-2"> - {{ job.id }} - </div> - <div @click="showDetails(job.id)" class="enqueued mt-2 mr-2"> - {{ formatDate(job.enqueued) }} - </div> - <div @click="showDetails(job.id)" class="kind mt-2 mr-2"> - {{ job.kind }} - </div> - <div @click="showDetails(job.id)" class="user mt-2 mr-2"> - {{ job.user }} - </div> - <div @click="showDetails(job.id)" class="signer mt-2 mr-2"> - {{ job.signer }} - </div> - <div @click="showDetails(job.id)" class="state mt-2 mr-2"> - {{ job.state }} - </div> - <div - @click="showDetails(job.id)" - class="btn btn-sm h-100 rounded-0 btn-info detailsbutton" - > - <font-awesome-icon - v-if="show" - icon="angle-up" - fixed-width - ></font-awesome-icon> - <font-awesome-icon - v-else - icon="angle-down" - fixed-width - ></font-awesome-icon> - </div> - </div> - <div class="detailstable d-flex flex-row"> - <div :class="collapse"> - <table class="table table-responsive"> - <thead> - <tr> - <th class="first pb-0"> - <small class="condensed"><translate>Kind</translate></small> - </th> - <th class="second pb-0"> - <a href="#" @click="sortAsc = !sortAsc" class="sort-link" - ><small class="condensed"><translate>Date</translate></small> - <small class="condensed" - ><font-awesome-icon - :icon="sortIcon" - class="ml-1" - ></font-awesome-icon></small - ></a> - </th> - <th class="third pb-0"> - <small class="condensed"><translate>Message</translate></small> - </th> - </tr> - </thead> - <tbody> - <tr - v-for="(entry, index) in sortedEntries" - :key="index" - class="detailsrow" - > - <td class="first"> - <span class="condensed">{{ entry.kind }}</span> - </td> - <td class="second"> - <span class="condensed">{{ formatDate(entry.time) }}</span> - </td> - <td class="third"> - <span class="condensed">{{ entry.message }}</span> - </td> - </tr> - </tbody> - </table> - </div> - </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): - * Thomas Junk <thomas.junk@intevation.de> - */ - -import { HTTP } from "../lib/http.js"; -import { displayError } from "../lib/errors.js"; -import locale2 from "locale2"; - -export default { - name: "importqueuedetail", - props: ["job"], - data() { - return { - show: false, - entries: [], - sortAsc: true - }; - }, - methods: { - formatDate(date) { - return date - ? new Date(date).toLocaleDateString(locale2, { - day: "2-digit", - month: "2-digit", - year: "numeric" - }) - : ""; - }, - showDetails(id) { - if (this.show) { - this.show = false; - return; - } - if (this.entries.length === 0) { - HTTP.get("/imports/" + id, { - headers: { "X-Gemma-Auth": localStorage.getItem("token") } - }) - .then(response => { - const { entries } = response.data; - this.entries = entries; - this.show = true; - }) - .catch(error => { - const { status, data } = error.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); - } else { - this.show = true; - } - } - }, - computed: { - sortedEntries() { - let sorted = this.entries.slice(); - sorted.sort((r1, r2) => { - let d1 = new Date(r1.time); - let d2 = new Date(r2.time); - if (d2 < d1) { - return !this.sortAsc ? -1 : 1; - } - if (d2 > d1) { - return !this.sortAsc ? 1 : -1; - } - return 0; - }); - return sorted; - }, - sortIcon() { - return this.sortAsc ? "sort-amount-down" : "sort-amount-up"; - }, - icon() { - return { - "angle-up": !this.show, - "angle-down": this.show - }; - }, - collapse() { - return { - details: true, - collapse: true, - show: this.show, - "w-100": true - }; - } - } -}; -</script> - -<style lang="scss" scoped> -.condensed { - font-stretch: condensed; -} - -.entry { - background-color: white; - cursor: pointer; -} - -.entry:hover { - background-color: #f0f0f0; - transition: 1s; -} - -.detailstable { - margin-left: $offset; - margin-right: $large-offset; -} - -.detailsbutton { - position: absolute; - top: 0; - right: 0; - height: 100%; -} -.jobid { - width: 80px; -} - -.enqueued { - width: 120px; -} - -.user { - width: 80px; -} - -.signer { - width: 80px; -} - -.kind { - width: 80px; -} - -.state { - width: 80px; -} - -.details { - width: 50%; -} - -.detailsrow { - line-height: 0.1em; -} - -.first { - width: 65px; - padding-left: 0px; - border-top: 0px; - padding-bottom: $small-offset; -} - -.second { - width: 100px; - padding-left: 0px; - border-top: 0px; - padding-bottom: $small-offset; -} - -.third { - width: 600px; - padding-left: 0px; - border-top: 0px; - padding-bottom: $small-offset; -} - -thead, -tbody { - display: block; -} - -tbody { - height: 150px; - overflow-y: auto; - overflow-x: hidden; -} -</style>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importqueue/Importqueue.vue Wed Dec 12 09:48:37 2018 +0100 @@ -0,0 +1,362 @@ +<template> + <div class="d-flex flex-row"> + <div :class="spacerStyle"></div> + <div class="mt-3 importqueuecard flex-grow-1"> + <div class="card shadow-xs"> + <h6 + class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center" + > + <font-awesome-icon icon="tasks" class="mr-2"></font-awesome-icon> + <translate class="headline">Importqueue</translate> + </h6> + <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"> + <font-awesome-icon icon="search"></font-awesome-icon> + </span> + </div> + <input + v-model="searchQuery" + type="text" + class="form-control" + placeholder + aria-label="Search" + aria-describedby="search" + /> + </div> + <div class="filters"> + <button + @click="setFilter('successful')" + :class="successfulStyle" + > + <translate>Successful</translate> + </button> + <button @click="setFilter('failed')" :class="failedStyle"> + <translate>Failed</translate> + </button> + <button @click="setFilter('pending')" :class="pendingStyle"> + <translate>Pending</translate> + </button> + <button @click="setFilter('rejected')" :class="rejectedStyle"> + <translate>Rejected</translate> + </button> + <button @click="setFilter('accepted')" :class="acceptedStyle"> + <translate>Accepted</translate> + </button> + </div> + </div> + <div class="text-left d-flex flex-row w-50 border-bottom"> + <div class="header py-1 jobid mr-2"> + <translate>Id</translate> + </div> + <div class="header py-1 enqueued mr-2"> + <translate>Enqueued</translate> + </div> + <div class="header py-1 kind mr-2"> + <translate>Kind</translate> + </div> + <div class="header py-1 user mr-2"> + <translate>User</translate> + </div> + <div class="header py-1 signer mr-2"> + <translate>Signer</translate> + </div> + <div class="header py-1 state mr-2"> + <translate>State</translate> + </div> + </div> + <div class="text-left" v-for="job in filteredImports" :key="job.id"> + <Importqueuedetail :job="job"></Importqueuedetail> + </div> + <div> + <button @click="refresh" class="btn btn-info refresh"> + <translate>Refresh</translate> + </button> + </div> + </div> + </div> + </div> + </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@intevation.de> + */ +import { displayError } from "../../lib/errors.js"; +import { mapState } from "vuex"; +import { HTTP } from "../../lib/http.js"; +import Importqueuedetail from "./Importqueuedetail"; + +export default { + name: "importqueue", + components: { + Importqueuedetail + }, + data() { + return { + searchQuery: "", + successful: false, + failed: false, + pending: false, + rejected: false, + accepted: false + }; + }, + mounted() { + this.loadQueue(); + }, + methods: { + setFilter(name) { + this[name] = !this[name]; + const allSet = + this.successful && + this.failed && + this.pending && + this.accepted && + this.rejected; + if (allSet) { + this.successful = false; + this.failed = false; + this.pending = false; + this.accepted = false; + this.rejected = false; + } + }, + loadQueue() { + this.$store.dispatch("imports/getImports").catch(error => { + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + }, + refresh() { + this.loadQueue(); + }, + showDetails(id) { + HTTP.get("/imports/" + id, { + headers: { "X-Gemma-Auth": localStorage.getItem("token") } + }) + .then(response => { + const { entries } = response.data; + this.entries = entries; + this.$modal.show("details"); + }) + .catch(error => { + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + }, + close() { + this.$modal.hide("details"); + } + }, + computed: { + ...mapState("imports", ["imports"]), + ...mapState("application", ["showSidebar"]), + sortIcon() { + return this.sortAsc ? "sort-amount-down" : "sort-amount-up"; + }, + filteredImports() { + const filtered = this.imports + .filter(element => { + if (!this.searchQuery) return true; + return [(element.kind, element.user, element.enqueued)].some(x => { + return x.toLowerCase().includes(this.searchQuery.toLowerCase()); + }); + }) + .filter(y => { + if ( + !this.successful && + !this.failed && + !this.pending && + !this.accepted && + !this.rejected + ) + return true; + let filterCriteria = []; + if (this.successful) filterCriteria.push("successful"); + if (this.failed) filterCriteria.push("failed"); + if (this.pending) filterCriteria.push("pending"); + if (this.accepted) filterCriteria.push("accepted"); + if (this.rejected) filterCriteria.push("rejected"); + const result = filterCriteria.map(selectedState => { + return y.state === selectedState; + }); + return result.some(x => x); + }); + return filtered; + }, + 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 + }; + }, + rejectedStyle() { + return { + btn: true, + "btn-light": !this.rejected, + "btn-dark": this.rejected + }; + }, + acceptedStyle() { + return { + btn: true, + "btn-light": !this.accepted, + "btn-dark": this.accepted + }; + } + } +}; +</script> + +<style lang="scss" scoped> +.jobid { + width: 80px; +} + +.enqueued { + width: 120px; +} + +.user { + width: 80px; +} + +.signer { + width: 80px; +} + +.kind { + width: 80px; +} + +.state { + width: 80px; +} + +.header { + font-weight: bold; + font-size: 0.9em; +} + +.details thead { + display: block; +} +.details tbody { + display: block; +} + +.details tbody { + height: 260px; + overflow-y: auto; + overflow-x: hidden; +} + +.closebutton { + top: $small-offset; +} + +.refresh { + position: absolute; + right: $offset; + bottom: $offset; +} + +.spacer { + height: 100vh; +} + +.spacer-collapsed { + min-width: $icon-width + $offset; + transition: $transition-fast; +} + +.spacer-expanded { + min-width: $sidebar-width; +} + +.importqueuecard { + width: 97%; + margin-left: $offset; + margin-right: $offset; + 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: 45%; +} +</style>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importqueue/Importqueuedetail.vue Wed Dec 12 09:48:37 2018 +0100 @@ -0,0 +1,272 @@ +<template> + <div class="entry d-flex flex-column py-1 border-bottom w-50"> + <div class="d-flex flex-row position-relative"> + <div @click="showDetails(job.id)" class="jobid ml-2 mt-1 mr-2"> + {{ job.id }} + </div> + <div @click="showDetails(job.id)" class="enqueued mt-1 mr-2"> + {{ formatDate(job.enqueued) }} + </div> + <div @click="showDetails(job.id)" class="kind mt-1 mr-2"> + {{ job.kind }} + </div> + <div @click="showDetails(job.id)" class="user mt-1 mr-2"> + {{ job.user }} + </div> + <div @click="showDetails(job.id)" class="signer mt-1 mr-2"> + {{ job.signer }} + </div> + <div @click="showDetails(job.id)" class="state mt-1 mr-2"> + {{ job.state }} + </div> + <div @click="showDetails(job.id)" class="mt-1 text-info detailsbutton"> + <font-awesome-icon + v-if="show" + icon="angle-up" + fixed-width + ></font-awesome-icon> + <font-awesome-icon + v-else + icon="angle-down" + fixed-width + ></font-awesome-icon> + </div> + </div> + <div class="detailstable d-flex flex-row"> + <div :class="collapse"> + <table class="table table-responsive"> + <thead> + <tr> + <th class="first pb-0"> + <small class="condensed"><translate>Kind</translate></small> + </th> + <th class="second pb-0"> + <a href="#" @click="sortAsc = !sortAsc" class="sort-link" + ><small class="condensed"><translate>Date</translate></small> + <small class="condensed" + ><font-awesome-icon + :icon="sortIcon" + class="ml-1" + ></font-awesome-icon></small + ></a> + </th> + <th class="third pb-0"> + <small class="condensed"><translate>Message</translate></small> + </th> + </tr> + </thead> + <tbody> + <tr + v-for="(entry, index) in sortedEntries" + :key="index" + class="detailsrow" + > + <td class="first"> + <span class="condensed">{{ entry.kind }}</span> + </td> + <td class="second"> + <span class="condensed">{{ formatDate(entry.time) }}</span> + </td> + <td class="third"> + <span class="condensed">{{ entry.message }}</span> + </td> + </tr> + </tbody> + </table> + </div> + </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): + * Thomas Junk <thomas.junk@intevation.de> + */ + +import { HTTP } from "../../lib/http.js"; +import { displayError } from "../../lib/errors.js"; +import locale2 from "locale2"; + +export default { + name: "importqueuedetail", + props: ["job"], + data() { + return { + show: false, + entries: [], + sortAsc: true + }; + }, + methods: { + formatDate(date) { + return date + ? new Date(date).toLocaleDateString(locale2, { + day: "2-digit", + month: "2-digit", + year: "numeric" + }) + : ""; + }, + showDetails(id) { + if (this.show) { + this.show = false; + return; + } + if (this.entries.length === 0) { + HTTP.get("/imports/" + id, { + headers: { "X-Gemma-Auth": localStorage.getItem("token") } + }) + .then(response => { + const { entries } = response.data; + this.entries = entries; + this.show = true; + }) + .catch(error => { + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }); + } else { + this.show = true; + } + } + }, + computed: { + sortedEntries() { + let sorted = this.entries.slice(); + sorted.sort((r1, r2) => { + let d1 = new Date(r1.time); + let d2 = new Date(r2.time); + if (d2 < d1) { + return !this.sortAsc ? -1 : 1; + } + if (d2 > d1) { + return !this.sortAsc ? 1 : -1; + } + return 0; + }); + return sorted; + }, + sortIcon() { + return this.sortAsc ? "sort-amount-down" : "sort-amount-up"; + }, + icon() { + return { + "angle-up": !this.show, + "angle-down": this.show + }; + }, + collapse() { + return { + details: true, + collapse: true, + show: this.show, + "w-100": true + }; + } + } +}; +</script> + +<style lang="scss" scoped> +.condensed { + font-stretch: condensed; +} + +.entry { + background-color: white; + cursor: pointer; +} + +.entry:hover { + background-color: #efefef; + transition: 1.5s; +} + +.detailstable { + margin-left: $offset; + margin-right: $large-offset; +} + +.detailsbutton { + position: absolute; + top: 0; + right: 0; + height: 100%; +} +.jobid { + width: 80px; +} + +.enqueued { + width: 120px; +} + +.user { + width: 80px; +} + +.signer { + width: 80px; +} + +.kind { + width: 80px; +} + +.state { + width: 80px; +} + +.details { + width: 50%; +} + +.detailsrow { + line-height: 0.1em; +} + +.first { + width: 65px; + padding-left: 0px; + border-top: 0px; + padding-bottom: $small-offset; +} + +.second { + width: 100px; + padding-left: 0px; + border-top: 0px; + padding-bottom: $small-offset; +} + +.third { + width: 600px; + padding-left: 0px; + border-top: 0px; + padding-bottom: $small-offset; +} + +thead, +tbody { + display: block; +} + +tbody { + height: 150px; + overflow-y: auto; + overflow-x: hidden; +} +</style>
--- a/client/src/router.js Wed Dec 12 09:22:20 2018 +0100 +++ b/client/src/router.js Wed Dec 12 09:48:37 2018 +0100 @@ -24,7 +24,7 @@ const Usermanagement = () => import("./components/usermanagement/Usermanagement.vue"); const Logs = () => import("./components/Logs.vue"); -const Importqueue = () => import("./components/Importqueue.vue"); +const Importqueue = () => import("./components/importqueue/Importqueue.vue"); const Importschedule = () => import("./components/importschedule/Importschedule.vue"); const Systemconfiguration = () =>