Mercurial > gemma
changeset 2422:77baf4f0ee1e
logs->importlogs due to .hgignore
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Thu, 28 Feb 2019 14:51:03 +0100 |
parents | e61ca8310dc9 |
children | 3423cd4b3136 |
files | client/src/components/importoverview/ImportOverview.vue client/src/components/importoverview/importlogs/LogDetail.vue client/src/components/importoverview/importlogs/Logs.vue |
diffstat | 3 files changed, 490 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/components/importoverview/ImportOverview.vue Thu Feb 28 14:04:05 2019 +0100 +++ b/client/src/components/importoverview/ImportOverview.vue Thu Feb 28 14:51:03 2019 +0100 @@ -70,7 +70,7 @@ name: "importoverview", components: { Staging: () => import("./staging/Staging.vue"), - Logs: () => import("./logs/Logs.vue") + Logs: () => import("./importlogs/Logs.vue") }, computed: { ...mapState("imports", ["stagingVisible", "logsVisible"])
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/importlogs/LogDetail.vue Thu Feb 28 14:51:03 2019 +0100 @@ -0,0 +1,359 @@ +<template> + <div class="entry d-flex flex-column py-1 border-bottom"> + <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"> + {{ formatDateTime(job.enqueued) }} + </div> + <div @click="showDetails(job.id)" class="kind mt-1 mr-2"> + {{ job.kind.toUpperCase() }} + </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"> + <span :class="{ 'text-danger': job.state.toUpperCase() == 'FAILED' }" + >{{ job.state + }}<font-awesome-icon + v-if="job.warnings" + class="ml-1 text-warning" + icon="exclamation-triangle" + fixed-width + ></font-awesome-icon + ></span> + </div> + <div @click="showDetails(job.id)" class="mt-1 text-info detailsbutton"> + <font-awesome-icon + class="pointer" + v-if="show" + icon="angle-up" + fixed-width + ></font-awesome-icon> + <font-awesome-icon + class="pointer" + v-if="loading" + icon="spinner" + fixed-width + ></font-awesome-icon> + <font-awesome-icon + class="pointer" + v-if="!show && !loading" + 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="type pb-0"> + <small class="condensed"><translate>Kind</translate></small> + </th> + <th class="datetime 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="message 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="type"> + <span + :class="[ + 'condensed', + { + 'text-danger': entry.kind.toUpperCase() == 'ERROR', + 'text-warning': entry.kind.toUpperCase() == 'WARN' + } + ]" + >{{ entry.kind.toUpperCase() }}</span + > + </td> + <td class="datetime"> + <span + :class="[ + 'condensed', + { + 'text-danger': entry.kind.toUpperCase() == 'ERROR', + 'text-warning': entry.kind.toUpperCase() == 'WARN' + } + ]" + >{{ formatDateTime(entry.time) }}</span + > + </td> + <td class="message"> + <span + :class="[ + 'condensed', + { + 'text-danger': entry.kind.toUpperCase() == 'ERROR', + 'text-warning': entry.kind.toUpperCase() == 'WARN' + } + ]" + >{{ 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", "reload"], + data() { + return { + loading: false, + show: false, + entries: [], + sortAsc: true + }; + }, + mounted() { + this.openSpecificDetail(); + }, + watch: { + $route() { + this.openSpecificDetail(); + }, + reload() { + if (this.reload) { + this.entries = []; + this.show = false; + } + } + }, + methods: { + openSpecificDetail() { + const { id } = this.$route.params; + if (id == this.job.id) { + this.showDetails(id); + } else { + this.show = false; + } + }, + formatDate(date) { + return date + ? new Date(date).toLocaleDateString(locale2, { + day: "2-digit", + month: "2-digit", + year: "numeric" + }) + : ""; + }, + formatDateTime(date) { + if (!date) return ""; + const d = new Date(date); + return ( + d.toLocaleDateString(locale2, { + day: "2-digit", + month: "2-digit", + year: "numeric" + }) + + " - " + + d.toLocaleTimeString(locale2, { + hour12: false + }) + ); + }, + showDetails(id) { + if (this.show) { + this.show = false; + return; + } + if (this.entries.length === 0) { + this.loading = true; + HTTP.get("/imports/" + id, { + headers: { "X-Gemma-Auth": localStorage.getItem("token") } + }) + .then(response => { + const { entries } = response.data; + this.entries = entries; + this.show = true; + this.loading = false; + }) + .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; + width: 100%; +} + +.entry:hover { + background-color: #efefef; + transition: 1.6s; +} + +.detailstable { + margin-left: $offset; + margin-right: $large-offset; +} + +.detailsbutton { + position: absolute; + top: 0; + right: 0; + height: 100%; +} +.jobid { + width: 15%; +} + +.enqueued { + width: 15%; +} + +.user { + width: 15%; +} + +.signer { + width: 15%; +} + +.kind { + width: 10%; +} + +.state { + width: 15%; +} + +.details { + width: 50%; +} + +.detailsrow { + line-height: 0.1em; +} + +.type { + width: 65px; + white-space: nowrap; + padding-left: 0px; + border-top: 0px; + padding-bottom: $small-offset; +} + +.datetime { + width: 200px; + white-space: nowrap; + padding-left: 0px; + border-top: 0px; + padding-bottom: $small-offset; +} + +.message { + min-width: 700px; + white-space: nowrap; + padding-left: 0px; + border-top: 0px; + padding-bottom: $small-offset; +} + +thead, +tbody { + display: block; +} + +tbody { + height: 150px; + overflow-y: auto; + overflow-x: auto; +} +</style>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/importlogs/Logs.vue Thu Feb 28 14:51:03 2019 +0100 @@ -0,0 +1,130 @@ +<template> + <div class="w-95"> + <div class="text-left"><h2>Logs</h2></div> + <div class="d-flex justify-content-between flex-row"> + <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> + <button @click="setFilter('warning')" :class="warningStyle"> + <translate>Warning</translate> + </button> + </div> + <div class="logdetails"> + <div v-for="job in imports" :key="job.id" class="d-flex flex-row"> + <LogDetail :job="job"></LogDetail> + </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 { mapState } from "vuex"; + +export default { + name: "logsection", + components: { + LogDetail: () => import("./LogDetail.vue") + }, + data() { + return { + failed: false, + pending: false, + rejected: false, + accepted: false, + warning: false + }; + }, + computed: { + ...mapState("imports", ["imports"]), + pendingStyle() { + return { + btn: true, + "btn-sm": true, + "btn-light": !this.pending, + "btn-info": this.pending + }; + }, + failedStyle() { + return { + btn: true, + "btn-sm": true, + "btn-light": !this.failed, + "btn-info": this.failed + }; + }, + rejectedStyle() { + return { + btn: true, + "btn-sm": true, + "btn-light": !this.rejected, + "btn-info": this.rejected + }; + }, + acceptedStyle() { + return { + btn: true, + "btn-sm": true, + "btn-light": !this.accepted, + "btn-info": this.accepted + }; + }, + warningStyle() { + return { + btn: true, + "btn-sm": true, + "btn-light": !this.warning, + "btn-info": this.warning + }; + } + }, + methods: { + setFilter(name) { + this[name] = !this[name]; + const allSet = + this.failed && + this.pending && + this.accepted && + this.rejected && + this.warning; + if (allSet) { + this.warning = false; + this.successful = false; + this.failed = false; + this.pending = false; + this.accepted = false; + this.rejected = false; + } + } + } +}; +</script> + +<style lang="scss" scoped> +.logdetails { + overflow-y: auto; + height: 300px; +} +</style>