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>