Mercurial > gemma
changeset 2970:149a8f81f99e unified_import
merge with default
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Fri, 05 Apr 2019 10:04:45 +0200 |
parents | b92a8d088d8a (current diff) 78affd725ba5 (diff) |
children | 7a51fdfead2d |
files | client/src/components/Contextbox.vue client/src/components/importoverview/AdditionalDetail.vue client/src/components/importoverview/FairwayDimension.vue |
diffstat | 74 files changed, 1158 insertions(+), 942 deletions(-) [+] |
line wrap: on
line diff
--- a/client/public/index.html Tue Apr 02 10:07:48 2019 +0200 +++ b/client/public/index.html Fri Apr 05 10:04:45 2019 +0200 @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later License-Filename: LICENSES/AGPL-3.0.txt - Copyright (C) 2018 by via donau + Copyright (C) 2018, 2019 by via donau – Österreichische Wasserstraßen-Gesellschaft mbH Software engineering by Intevation GmbH -->
--- a/client/src/assets/application.scss Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/assets/application.scss Fri Apr 05 10:04:45 2019 +0200 @@ -107,7 +107,6 @@ .box-control { display: inline-block; - margin-left: 3px; color: #888; padding: 3px 7px; border-radius: 0.25rem; @@ -205,10 +204,6 @@ font-size: 0.75rem; } -.empty { - margin-right: 1.25rem; -} - .truncate { white-space: nowrap; overflow: hidden;
--- a/client/src/assets/tooltip.scss Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/assets/tooltip.scss Fri Apr 05 10:04:45 2019 +0200 @@ -17,10 +17,12 @@ } .tooltip .tooltip-inner { - background: black; - color: white; - border-radius: 16px; + background: white; + box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2); + color: #666; + border-radius: 0.25rem; padding: 5px 10px 4px; + font-size: 0.8rem; } .tooltip .tooltip-arrow { @@ -29,7 +31,7 @@ border-style: solid; position: absolute; margin: 5px; - border-color: black; + border-color: white; z-index: 1; } @@ -95,7 +97,7 @@ .tooltip.popover .popover-inner { background: #f9f9f9; - color: black; + color: white; padding: 24px; border-radius: 5px; box-shadow: 0 5px 30px rgba(black, 0.1);
--- a/client/src/components/Bottlenecks.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/Bottlenecks.vue Fri Apr 05 10:04:45 2019 +0200 @@ -47,31 +47,12 @@ }} </div> <div class="table-cell center" style="width: 30px"> - <a - class="text-info" + <UISpinnerButton @click="loadSurveys(bottleneck)" + :loading="loading === bottleneck" + :state="bottleneck === openBottleneck" v-if="bottleneck.properties.current" - > - <font-awesome-icon - class="pointer" - icon="spinner" - fixed-width - spin - v-if="loading === bottleneck" - ></font-awesome-icon> - <font-awesome-icon - class="pointer" - icon="angle-down" - fixed-width - v-if="loading !== bottleneck && openBottleneck !== bottleneck" - ></font-awesome-icon> - <font-awesome-icon - class="pointer" - icon="angle-up" - fixed-width - v-if="loading !== bottleneck && openBottleneck === bottleneck" - ></font-awesome-icon> - </a> + /> </div> </template> <template v-slot:expand="{ item: bottleneck }">
--- a/client/src/components/Contextbox.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/Contextbox.vue Fri Apr 05 10:04:45 2019 +0200 @@ -88,6 +88,7 @@ .contextboxextended { max-width: 660px; + max-height: 95vh; } .close-contextbox {
--- a/client/src/components/ImportApprovedGaugeMeasurement.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/ImportApprovedGaugeMeasurement.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,6 +1,6 @@ <template> <div class="d-flex flex-row"> - <Spacer></Spacer> + <Spacer /> <div class="card sysconfig mt-2 shadow-xs w-100 h-100 mr-2"> <UIBoxHeader icon="upload" :title="importGaugmeasurmentLabel" /> <div class="card-body stretches-card"> @@ -45,11 +45,7 @@ class="btn btn-info mt-4" type="button" > - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="play" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="play" /> <translate>Trigger import</translate> </button> </div>
--- a/client/src/components/ImportSoundingresults.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/ImportSoundingresults.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,7 +1,7 @@ <template> <div class="main d-flex flex-column"> <div class="d-flex flex-row"> - <Spacer></Spacer> + <Spacer /> <div class="card shadow-xs mt-2 mr-2 w-100 h-100"> <UIBoxHeader icon="upload" :title="importSoundingresultsLabel" /> <div v-if="editState"> @@ -160,14 +160,13 @@ import { HTTP } from "@/lib/http"; import { displayError, displayInfo } from "@/lib/errors.js"; import { mapState } from "vuex"; -import Spacer from "./Spacer"; const IMPORTSTATE = { UPLOAD: "UPLOAD", EDIT: "EDIT" }; export default { name: "imports", components: { - Spacer + Spacer: () => import("@/components/Spacer") }, data() { return {
--- a/client/src/components/ImportStretches.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/ImportStretches.vue Fri Apr 05 10:04:45 2019 +0200 @@ -5,263 +5,281 @@ :title="defineStretchesLabel" :closeCallback="$parent.close" /> - <div v-if="!edit" class="mb-3"> - <UITableHeader - :columns="[ - { id: 'properties.name', title: `${nameLabel}`, class: 'col-4' }, - { id: 'properties.date_info', title: `${dateLabel}`, class: 'col-2' }, - { - id: 'properties.source_organization', - title: `${sourceorganizationLabel}`, - class: 'col-3' - } - ]" - /> - <UITableBody - :data="filteredStretches() | sortTable(sortColumn, sortDirection)" - > - <template v-slot:row="{ item: stretch }"> - <div class="py-1 col-4 "> - <a - class="linkto text-info" - v-if="isInStaging(stretch.properties.name)" - @click="gotoStaging(getStagingLink(stretch.properties.name))" - > - {{ stretch.properties.name - }}<font-awesome-icon - class="ml-1 text-danger" - icon="exclamation-triangle" - fixed-width - ></font-awesome-icon - ><small class="ml-1">review</small> - </a> - <a v-else @click="moveMapToStretch(stretch)" href="#">{{ - stretch.properties.name - }}</a> - </div> - <div class="py-1 col-2"> - {{ stretch.properties.date_info | surveyDate }} - </div> - <div class="py-1 col-3"> - {{ stretch.properties.source_organization }} - </div> - <div class="py-1 col text-right"> - <button - class="btn btn-xs btn-dark mr-1" - @click="editStretch(stretch)" - > - <font-awesome-icon icon="pencil-alt" fixed-width /> - </button> - <button class="btn btn-xs btn-dark" @click="deleteStretch(stretch)"> - <font-awesome-icon icon="trash" fixed-width /> - </button> - </div> - </template> - </UITableBody> - </div> - <div v-if="edit"> - <div class="ml-3 mr-3"> - <div class="d-flex flex-row justify-content-between"> - <div class="mt-2 w-50 mr-2 text-left"> - <small class="text-muted"> <translate>ID</translate> </small> - <input - id="id" - type="text" - class="form-control" - placeholder="AT_Section_12" - aria-label="id" - v-model="id" - :disabled="editExistingStretch" - /> - <span class="text-left text-danger"> - <small v-if="idError && !id"> - <translate>Please enter an id</translate> - </small> - </span> - </div> - <div class="mt-2 w-50 ml-2 text-left"> - <div> - <small class="text-muted"> - <translate>Countrycode</translate> - </small> + <div class="position-relative"> + <UISpinnerOverlay v-if="loading" /> + <div v-if="!edit" class="mb-3"> + <UITableHeader + :columns="[ + { id: 'properties.name', title: `${nameLabel}`, class: 'col-4' }, + { + id: 'properties.date_info', + title: `${dateLabel}`, + class: 'col-2' + }, + { + id: 'properties.source_organization', + title: `${sourceorganizationLabel}`, + class: 'col-3' + } + ]" + /> + <UITableBody + :data="filteredStretches() | sortTable(sortColumn, sortDirection)" + > + <template v-slot:row="{ item: stretch }"> + <div class="py-1 col-4 "> + <a + class="linkto text-info" + v-if="isInStaging(stretch.properties.name)" + @click="gotoStaging(getStagingLink(stretch.properties.name))" + > + {{ stretch.properties.name + }}<font-awesome-icon + class="ml-1 text-danger" + icon="exclamation-triangle" + fixed-width + ></font-awesome-icon + ><small class="ml-1">review</small> + </a> + <a v-else @click="moveMapToStretch(stretch)" href="#">{{ + stretch.properties.name + }}</a> + </div> + <div class="py-1 col-2"> + {{ stretch.properties.date_info | surveyDate }} + </div> + <div class="py-1 col-3"> + {{ stretch.properties.source_organization }} + </div> + <div class="py-1 col text-right"> + <button + class="btn btn-xs btn-dark mr-1" + @click="editStretch(stretch)" + > + <font-awesome-icon icon="pencil-alt" fixed-width /> + </button> + <button + class="btn btn-xs btn-dark" + @click="deleteStretch(stretch)" + > + <font-awesome-icon icon="trash" fixed-width /> + </button> + </div> + </template> + </UITableBody> + </div> + <div v-if="edit"> + <div class="ml-3 mr-3"> + <div class="d-flex flex-row justify-content-between"> + <div class="mt-2 w-50 mr-2 text-left"> + <small class="text-muted"> <translate>ID</translate> </small> <input - id="countryCode" + id="id" type="text" class="form-control" - placeholder="AT" + placeholder="AT_Section_12" aria-label="id" - v-model="countryCode" + v-model="id" + :disabled="editExistingStretch" /> <span class="text-left text-danger"> - <small v-if="countryCodeError && !countryCode"> - <translate>Please enter a countrycode </translate> + <small v-if="idError && !id"> + <translate>Please enter an id</translate> + </small> + </span> + </div> + <div class="mt-2 w-50 ml-2 text-left"> + <div> + <small class="text-muted"> + <translate>Countrycode</translate> + </small> + <input + id="countryCode" + type="text" + class="form-control" + placeholder="AT" + aria-label="id" + v-model="countryCode" + /> + <span class="text-left text-danger"> + <small v-if="countryCodeError && !countryCode"> + <translate>Please enter a countrycode </translate> + </small> + </span> + </div> + <div class="w-50 ml-2"></div> + </div> + </div> + <div class="d-flex flex-column justify-content-between"> + <div class="mt-2 text-left"> + <small class="text-muted"> + <translate>Start rhm</translate> + </small> + <div class="d-flex flex-row"> + <input + id="startrhm" + type="text" + class="form-control" + placeholder="e.g. ATXXX000010000019900" + aria-label="startrhm" + v-model="startrhm" + /> + <span class="input-group-text"> + <font-awesome-icon + @click="togglePipette('start')" + :class="{ 'text-info': pipetteStart }" + icon="bullseye" + /> + </span> + </div> + <span class="text-left text-danger"> + <small v-if="startrhmError && !startrhm"> + <translate>Please enter a start point</translate> </small> </span> </div> - <div class="w-50 ml-2"></div> - </div> - </div> - <div class="d-flex flex-column justify-content-between"> - <div class="mt-2 text-left"> - <small class="text-muted"> <translate>Start rhm</translate> </small> - <div class="d-flex flex-row"> - <input - id="startrhm" - type="text" - class="form-control" - placeholder="e.g. ATXXX000010000019900" - aria-label="startrhm" - v-model="startrhm" - /> - <span class="input-group-text"> - <font-awesome-icon - @click="togglePipette('start')" - :class="{ 'text-info': pipetteStart }" - icon="bullseye" - ></font-awesome-icon> + <div class="mt-2 text-left"> + <small class="text-muted"> <translate>End rhm</translate> </small> + <div class="d-flex flex-row"> + <input + id="endrhm" + type="text" + class="form-control" + placeholder="e.g. ATXXX000010000019900" + aria-label="endrhm" + v-model="endrhm" + /> + <span class="input-group-text"> + <font-awesome-icon + @click="togglePipette('end')" + :class="{ 'text-info': pipetteEnd }" + icon="bullseye" + /> + </span> + </div> + <span class="text-left text-danger"> + <small v-if="endrhmError && !endrhm"> + <translate>Please enter an end point</translate> + </small> </span> </div> - <span class="text-left text-danger"> - <small v-if="startrhmError && !startrhm"> - <translate>Please enter a start point</translate> - </small> - </span> </div> - <div class="mt-2 text-left"> - <small class="text-muted"> <translate>End rhm</translate> </small> - <div class="d-flex flex-row"> + <div + v-if="!editExistingStretch" + class="d-flex flex-row justify-content-between" + > + <div class="mt-2 mr-2 w-50 text-left"> + <small class="text-muted"> + <translate + >Tolerance for snapping of waterway axis [m]</translate + > + </small> <input - id="endrhm" + class="form-control" + v-model.number="tolerance" + placeholder="" + type="number" + min="0" + step="any" + aria-label="tolerance" + id="tolerance" + /> + <span class="text-left text-danger"> + <small v-if="toleranceError && !tolerance"> + <translate>Please enter a tolerance value</translate> + </small> + </span> + </div> + </div> + <div class="d-flex flex-row justify-content-between"> + <div class="mt-2 mr-2 w-50 text-left"> + <small class="text-muted"> + <translate>Object name</translate> + </small> + <input + id="objbn" type="text" class="form-control" - placeholder="e.g. ATXXX000010000019900" - aria-label="endrhm" - v-model="endrhm" + placeholder="" + aria-label="objbn" + v-model="objbn" /> - <span class="input-group-text"> - <font-awesome-icon - @click="togglePipette('end')" - :class="{ 'text-info': pipetteEnd }" - icon="bullseye" - ></font-awesome-icon> + <span class="text-left text-danger"> + <small v-if="objbnError && !objbn"> + <translate>Please enter an objectname</translate> + </small> </span> </div> - <span class="text-left text-danger"> - <small v-if="endrhmError && !endrhm"> - <translate>Please enter an end point</translate> + <div class="mt-2 ml-2 w-50 text-left"> + <small class="text-muted"> + <translate>National Object name</translate> </small> - </span> + <input + id="nobjbn" + type="text" + class="form-control" + placeholder="" + aria-label="nobjbn" + v-model="nobjbn" + /> + </div> </div> - </div> - <div - v-if="!editExistingStretch" - class="d-flex flex-row justify-content-between" - > - <div class="mt-2 mr-2 w-50 text-left"> - <small class="text-muted"> - <translate>Tolerance for snapping of waterway axis [m]</translate> - </small> - <input - class="form-control" - v-model.number="tolerance" - placeholder="" - type="number" - min="0" - step="any" - aria-label="tolerance" - id="tolerance" - /> - <span class="text-left text-danger"> - <small v-if="toleranceError && !tolerance"> - <translate>Please enter a tolerance value</translate> + <div class="d-flex flex-row justify-content-between"> + <div class="mt-2 mr-2 w-50 text-left"> + <small class="text-muted"> + <translate>Date info</translate> </small> - </span> + <input + id="date_info" + type="date" + class="form-control" + placeholder="date_info" + aria-label="date_info" + v-model="date_info" + /> + <span class="text-left text-danger"> + <small v-if="date_infoError && !date_info"> + <translate>Please enter a date</translate> + </small> + </span> + </div> + <div class="mt-2 ml-2 w-50 text-left"> + <small class="text-muted"> <translate>Source</translate> </small> + <input + id="source" + type="text" + class="form-control" + placeholder="source" + aria-label="source" + v-model="source" + /> + <span class="text-left text-danger"> + <small v-if="sourceError && !source"> + <translate>Please enter a source</translate> + </small> + </span> + </div> </div> </div> - <div class="d-flex flex-row justify-content-between"> - <div class="mt-2 mr-2 w-50 text-left"> - <small class="text-muted"> - <translate>Object name</translate> - </small> - <input - id="objbn" - type="text" - class="form-control" - placeholder="" - aria-label="objbn" - v-model="objbn" - /> - <span class="text-left text-danger"> - <small v-if="objbnError && !objbn"> - <translate>Please enter an objectname</translate> - </small> - </span> - </div> - <div class="mt-2 ml-2 w-50 text-left"> - <small class="text-muted"> - <translate>National Object name</translate> - </small> - <input - id="nobjbn" - type="text" - class="form-control" - placeholder="" - aria-label="nobjbn" - v-model="nobjbn" - /> - </div> - </div> - <div class="d-flex flex-row justify-content-between"> - <div class="mt-2 mr-2 w-50 text-left"> - <small class="text-muted"> <translate>Date info</translate> </small> - <input - id="date_info" - type="date" - class="form-control" - placeholder="date_info" - aria-label="date_info" - v-model="date_info" - /> - <span class="text-left text-danger"> - <small v-if="date_infoError && !date_info"> - <translate>Please enter a date</translate> - </small> - </span> - </div> - <div class="mt-2 ml-2 w-50 text-left"> - <small class="text-muted"> <translate>Source</translate> </small> - <input - id="source" - type="text" - class="form-control" - placeholder="source" - aria-label="source" - v-model="source" - /> - <span class="text-left text-danger"> - <small v-if="sourceError && !source"> - <translate>Please enter a source</translate> - </small> - </span> - </div> + <div class="text-right mt-2 mr-3 mb-3"> + <button @click="edit = false" class="btn btn-warning mr-2"> + Back + </button> + <button + @click="save" + type="submit" + class="shadow-sm btn btn-info submit-button" + > + <translate>Submit</translate> + </button> </div> </div> - <div class="text-right mt-2 mr-3 mb-3"> - <button @click="edit = false" class="btn btn-warning mr-2">Back</button> - <button - @click="save" - type="submit" - class="shadow-sm btn btn-info submit-button" - > - <translate>Submit</translate> + <div class="text-right mr-3"> + <button v-if="!edit" @click="startEdit()" class="btn btn-info"> + <translate>New stretch</translate> </button> </div> </div> - <div class="text-right mr-3"> - <button v-if="!edit" @click="startEdit()" class="btn btn-info"> - <translate>New stretch</translate> - </button> - </div> </div> </template> @@ -285,7 +303,6 @@ import { LAYERS } from "@/store/map"; import { HTTP } from "@/lib/http"; import { sortTable } from "@/lib/mixins"; -import { Stroke, Style, Fill } from "ol/style.js"; export default { name: "importstretches", @@ -316,7 +333,8 @@ nobjbnError: false, date_infoError: false, sourceError: false, - countryCodeError: false + countryCodeError: false, + loading: false }; }, computed: { @@ -441,62 +459,14 @@ }); }, moveMapToStretch(stretch) { - //define new style for the selected stretch - var newStyle = new Style({ - stroke: new Stroke({ - color: "rgba(250, 240, 10, .9)", - width: 5 - }), - fill: new Fill({ - color: "rgba(250, 240, 0, .7)" - }) - }); - let stretchesLayer = this.getLayerByName(LAYERS.STRETCHES); - let oldStyle = stretchesLayer.data.getStyle(); + this.$store.commit("imports/selectedStretchId", stretch.id); this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES); - var stretches = stretchesLayer.data.getSource().getFeatures(); - // change the style when the the stretches are already loaded - if (stretches.length) { - this.setStretchStyle(stretches, stretch, oldStyle, newStyle); - } else { - // by choosing stretch first time wating to load the features then change the style - stretchesLayer.data.getSource().once("change", () => { - this.setStretchStyle( - stretchesLayer.data.getSource().getFeatures(), - stretch, - oldStyle, - newStyle - ); - }); - } this.$store.commit("map/moveToExtent", { feature: stretch, zoom: 17, preventZoomOut: true }); }, - // give the selected stretch new style and set the orginal style to the others - setStretchStyle(features, stretch, oldStyle, newStyle) { - features.forEach(f => { - if (f.id_ === stretch.id) { - f.setStyle(newStyle); - } else { - f.setStyle(oldStyle); - } - }); - }, - loadStretches() { - return new Promise((resolve, reject) => { - this.$store - .dispatch("imports/loadStretches") - .then(response => { - resolve(response); - }) - .catch(error => { - reject(error); - }); - }); - }, clean() { this.id = ""; this.edit = false; @@ -594,7 +564,7 @@ message: this.$gettext("Starting import of stretch") }); this.clean(); - this.loadStretches().then(() => { + this.$store.dispatch("imports/loadStretches").then(() => { this.edit = false; }); }) @@ -609,13 +579,17 @@ }, mounted() { this.edit = false; - this.$store.dispatch("imports/loadStretches").catch(error => { - const { status, data } = error.response; - displayError({ - title: this.$gettext("Backend Error"), - message: `${status}: ${data.message || data}` - }); - }); + this.loading = true; + this.$store + .dispatch("imports/loadStretches") + .catch(error => { + const { status, data } = error.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status}: ${data.message || data}` + }); + }) + .finally(() => (this.loading = false)); this.loadStagingData().catch(error => { const { status, data } = error.response; displayError({
--- a/client/src/components/ImportWaterwayProfiles.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/ImportWaterwayProfiles.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,6 +1,6 @@ <template> <div class="d-flex flex-row"> - <Spacer></Spacer> + <Spacer /> <div class="card sysconfig mt-2 shadow-xs w-100 h-100 mr-2"> <UIBoxHeader icon="upload" :title="importWaterwayProfilesLabel" /> <div class="card-body stretches-card"> @@ -87,11 +87,7 @@ class="btn btn-info mt-4" type="button" > - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="play" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="play" /> <translate>Trigger import</translate> </button> </div> @@ -121,6 +117,9 @@ export default { name: "importwaterwayprofiles", + components: { + Spacer: () => import("./Spacer") + }, data() { return { url: "https://service.d4d-portal.info/wamos/wfs/", @@ -176,9 +175,6 @@ }); }); } - }, - components: { - Spacer: () => import("./Spacer") } }; </script>
--- a/client/src/components/Logs.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/Logs.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,7 +1,7 @@ <template> <div class="main d-flex flex-column"> <div class="d-flex flex-row"> - <Spacer></Spacer> + <Spacer /> <div class="card logs shadow-xs mt-2 mr-2"> <UIBoxHeader icon="book" title="Logs" /> <div class="logoutput text-left bg-white"> @@ -14,8 +14,11 @@ <ul class="nav nav-pills"> <li class="nav-item"> <a + id="accesslog" :class="accesslogStyle" - @click="fetch('system/log/apache2/access.log', 'accesslog')" + @click.prevent=" + fetch('system/log/apache2/access.log', 'accesslog') + " href="#" > <translate>Accesslog</translate> @@ -23,8 +26,11 @@ </li> <li class="nav-item"> <a + id="errorlog" :class="errorlogStyle" - @click="fetch('system/log/apache2/error.log', 'errorlog')" + @click.prevent=" + fetch('system/log/apache2/error.log', 'errorlog') + " href="#" > <translate>Errorlog</translate> @@ -65,6 +71,7 @@ .logs { height: 85vh; + width: 100vw; } #code { @@ -117,6 +124,7 @@ import "../../node_modules/highlight.js/styles/paraiso-dark.css"; import Vue from "vue"; import VueHighlightJS from "vue-highlightjs"; +import { displayError } from "@/lib/errors.js"; Vue.use(VueHighlightJS); const ACCESSLOG = "accesslog"; @@ -149,7 +157,13 @@ this.refreshed = new Date().toLocaleString(); this.currentFile = file; }) - .catch(); + .catch(e => { + const { status, data } = e.response; + displayError({ + title: this.$gettext("Backend Error"), + message: `${status} ${data.message || data}` + }); + }); }, disallow(e) { e.target.blur();
--- a/client/src/components/Maplayer.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/Maplayer.vue Fri Apr 05 10:04:45 2019 +0200 @@ -67,6 +67,7 @@ ]), ...mapState("bottlenecks", ["selectedSurvey"]), ...mapState("application", ["showSplitscreen"]), + ...mapState("imports", ["selectedStretchId"]), hasActiveInteractions() { return ( (this.lineTool && this.lineTool.getActive()) || @@ -76,7 +77,12 @@ } }, methods: { - buildVectorLoader(featureRequestOptions, endpoint, vectorSource) { + buildVectorLoader( + featureRequestOptions, + endpoint, + vectorSource, + featurePostProcessor + ) { // build a function to be used for VectorSource.setLoader() // make use of WFS().writeGetFeature to build the request // and use our HTTP library to actually do it @@ -103,6 +109,9 @@ var features = new GeoJSON().readFeatures( JSON.stringify(response.data) ); + if (featurePostProcessor) { + features.map(f => featurePostProcessor(f)); + } vectorSource.addFeatures(features); // console.log( // "loaded", @@ -164,6 +173,16 @@ } else { this.updateBottleneckFilter("does_not_exist", "1999-10-01"); } + }, + selectedStretchId(id) { + this.getVSourceByName(LAYERS.STRETCHES) + .getFeatures() + .forEach(f => { + f.set("highlighted", false); + if (id === f.getId()) { + f.set("highlighted", true); + } + }); } }, mounted() { @@ -369,7 +388,13 @@ geometryName: "area" }, "/internal/wfs", - layer.data.getSource() + layer.data.getSource(), + f => { + if (f.getId() === this.selectedStretchId) { + f.set("highlighted", true); + } + return f; + } ) ); layer.data.setVisible(layer.isVisible);
--- a/client/src/components/PageNotFound.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/PageNotFound.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,10 +1,10 @@ <template> <div class="main d-flex flex-row" style="position: relative;"> - <Spacer></Spacer> + <Spacer /> <div class="my-auto mx-auto"> <h1> - <font-awesome-icon icon="frown-open" fixed-width></font-awesome-icon>We - are sorry. The ressource you requested could not be found. + <font-awesome-icon icon="frown-open" fixed-width /> + We are sorry. The ressource you requested could not be found. </h1> </div> </div>
--- a/client/src/components/Search.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/Search.vue Fri Apr 05 10:04:45 2019 +0200 @@ -2,7 +2,7 @@ <div :class="searchbarContainerStyle"> <div class="input-group-prepend m-0 d-print-none"> <span @click="toggleSearchbar" :class="searchButtonStyle" for="search"> - <font-awesome-icon icon="search"></font-awesome-icon> + <font-awesome-icon icon="search" /> </span> </div> <div
--- a/client/src/components/Sidebar.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/Sidebar.vue Fri Apr 05 10:04:45 2019 +0200 @@ -8,7 +8,7 @@ @click="$store.commit('application/showSidebar', !showSidebar)" class="menubutton ui-element d-print-none p-2 bg-white rounded position-absolute d-flex justify-content-center" > - <font-awesome-icon class="fa-fw" icon="bars"></font-awesome-icon> + <font-awesome-icon class="fa-fw" icon="bars" /> </div> <div class="menu text-nowrap text-left"> <router-link to="/"> @@ -16,15 +16,11 @@ class="fa-fw mr-2" fixed-width icon="map-marked-alt" - ></font-awesome-icon> + /> <span class="fix-trans-space" v-translate>Map</span> </router-link> <router-link to="/bottlenecks"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="ship" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="ship" /> <span class="fix-trans-space" v-translate>Bottlenecks</span> </router-link> <div v-if="isWaterwayAdmin"> @@ -33,7 +29,7 @@ class="fa-fw mr-2" fixed-width icon="clipboard-check" - ></font-awesome-icon> + /> <span class="fix-trans-space" v-translate>Import review</span> <span class="indicator" v-if="showSidebar && stagingNotifications"> {{ stagingNotifications }} @@ -42,11 +38,7 @@ </div> <div v-if="isSysAdmin"> <router-link to="/stretches"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="road" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="road" /> <span class="fix-trans-space" v-translate>Define stretches</span> </router-link> </div> @@ -54,37 +46,21 @@ <small class="text-muted pl-3"> <translate>Import</translate> </small> <hr class="m-0" /> <router-link to="/importsoundingresults"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="upload" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="upload" /> <span class="fix-trans-space" v-translate>Soundingresults</span> </router-link> <router-link to="/importapprovedgaugemeasurement"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="upload" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="upload" /> <span class="fix-trans-space" v-translate >Approved Gaugemeasurements</span > </router-link> <router-link to="/importwaterwayprofiles"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="upload" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="upload" /> <span class="fix-trans-space" v-translate>Waterway Profiles</span> </router-link> <router-link to="/importschedule"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="clock" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="clock" /> <translate class="fix-trans-space">Imports</translate> </router-link> <small class="text-muted pl-3"> @@ -98,37 +74,25 @@ class="fa-fw mr-2" fixed-width icon="users-cog" - ></font-awesome-icon> + /> <span class="fix-trans-space" v-translate>Users</span> </router-link> </div> <div v-if="isWaterwayAdmin"> <router-link to="/systemconfiguration"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="wrench" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="wrench" /> <span class="fix-trans-space" v-translate>Configuration</span> </router-link> </div> <div v-if="isSysAdmin"> <router-link to="/logs"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="book" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="book" /> <span class="fix-trans-space" v-translate>Logs</span> </router-link> </div> <hr class="m-0" /> <a @click="logoff" href="#" class="logout"> - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="power-off" - ></font-awesome-icon> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="power-off" /> <span class="fix-trans-space" v-translate>Logout</span> {{ user }} </a> </div>
--- a/client/src/components/Zoom.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/Zoom.vue Fri Apr 05 10:04:45 2019 +0200 @@ -4,19 +4,19 @@ class="zoom-button border-0 bg-white rounded-left ui-element" @click="zoomOut" > - <font-awesome-icon icon="minus"></font-awesome-icon> + <font-awesome-icon icon="minus" /> </button> <button class="zoom-button border-0 bg-white ui-element border-right" @click="refreshMap" > - <font-awesome-icon icon="redo"></font-awesome-icon> + <font-awesome-icon icon="redo" /> </button> <button class="zoom-button border-0 bg-white rounded-right ui-element border-right" @click="zoomIn" > - <font-awesome-icon icon="plus"></font-awesome-icon> + <font-awesome-icon icon="plus" /> </button> </div> </template>
--- a/client/src/components/fairway/Profiles.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/fairway/Profiles.vue Fri Apr 05 10:04:45 2019 +0200 @@ -12,7 +12,7 @@ :closeCallback="close" /> <div class="box-body"> - <SpinnerOverlay + <UISpinnerOverlay v-if="surveysLoading || profileLoading || differencesLoading" /> <select @@ -187,7 +187,7 @@ <button class="btn btn-info btn-sm w-100" @click="toggleCutTool"> <font-awesome-icon :icon="cutTool && cutTool.getActive() ? 'times' : 'plus'" - ></font-awesome-icon> + /> {{ cutTool && cutTool.getActive() ? "Cancel" : "New" }} </button> </div>
--- a/client/src/components/gauge/Gauges.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/gauge/Gauges.vue Fri Apr 05 10:04:45 2019 +0200 @@ -12,7 +12,7 @@ :closeCallback="close" /> <div class="box-body"> - <SpinnerOverlay v-if="loading" /> + <UISpinnerOverlay v-if="loading" /> <select @change="moveToGauge" v-model="selectedGaugeISRS"
--- a/client/src/components/importoverview/AdditionalDetail.vue Tue Apr 02 10:07:48 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -<template> - <div> - <FairwayDimensionDetail - :entry="entry" - :details="details" - v-if="isFairwayDimension" - ></FairwayDimensionDetail> - <ApprovedGaugeMeasurementDetail - :entry="entry" - :details="details" - v-if="isApprovedGaugeMeasurement" - ></ApprovedGaugeMeasurementDetail> - <BottleneckDetail - :details="details" - :entry="entry" - v-if="isBottleneck" - ></BottleneckDetail> - </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: "additionaldetail", - props: ["entry"], - components: { - BottleneckDetail: () => import("./BottleneckDetail.vue"), - ApprovedGaugeMeasurementDetail: () => - import("./ApprovedGaugeMeasurementDetail.vue"), - FairwayDimensionDetail: () => import("./FairwayDimension.vue") - }, - computed: { - ...mapState("imports", ["showLogs", "details"]), - kind() { - return this.entry.kind.toUpperCase(); - }, - isFairwayDimension() { - return this.kind === "FD"; - }, - isApprovedGaugeMeasurement() { - return this.kind === "AGM"; - }, - isBottleneck() { - return this.kind === "BN" || this.kind === "UBN"; - } - } -}; -</script> - -<style lang="scss" scoped></style>
--- a/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importoverview/ApprovedGaugeMeasurementDetail.vue Fri Apr 05 10:04:45 2019 +0200 @@ -7,23 +7,13 @@ }" > <div v-for="(result, index) in details.summary" :key="index"> - <div class="pl-2 d-flex flex-row"> - <div - @click="toggleDiff(index)" - class="small mt-auto mb-auto text-info text-left" - > - <font-awesome-icon - class="pointer" - v-if="showDiff == index" - icon="angle-down" - fixed-width - ></font-awesome-icon> - <font-awesome-icon - class="pointer" - v-if="showDiff != index" - icon="angle-right" - fixed-width - ></font-awesome-icon> + <div class="pl-2 d-flex"> + <div @click="toggleDiff(index)" class="my-auto text-left"> + <UISpinnerButton + :state="showDiff === index" + :icons="['angle-right', 'angle-down']" + classes="text-info" + /> </div> <span v-if="result.versions.length == 1" class="agmcode text-left" ><div> @@ -37,9 +27,9 @@ ><div>{{ result["measure-date"] | dateTime }}</div></span > </div> - <div v-if="showDiff == index" class="pl-3 d-flex flex-row"> + <div v-if="showDiff === index" class="pl-3 d-flex flex-row"> <div class="w-100"> - <div class="d-flex flex-row pl-3 text-left"> + <div class="d-flex flex-row pl-3 text-left w-95"> <div class="header border-bottom agmdetailskeys"> <small><translate>Value</translate></small> </div> @@ -54,7 +44,7 @@ </div> </div> <div - class="d-flex flex-row pl-3 text-left" + class="line d-flex flex-row pl-3 text-left w-95" v-for="(entry, index) in Object.keys(result.versions[0])" :key="index" > @@ -95,8 +85,14 @@ <style lang="sass" scoped> .diffs width: 100% - max-height: 20vh overflow-y: auto + > div + border-top: dashed 1px #dee2e6 + &:first-child + border-top: none + +.line:nth-child(odd) + background: #f8f8f8 .agmcode width: 35% @@ -135,7 +131,7 @@ props: ["entry"], data() { return { - showDiff: false + showDiff: 0 // open first item by default }; }, computed: {
--- a/client/src/components/importoverview/BottleneckDetail.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importoverview/BottleneckDetail.vue Fri Apr 05 10:04:45 2019 +0200 @@ -9,55 +9,57 @@ <div v-for="(bottleneck, index) in bottlenecks" :key="index" - class="d-flex flex-row" + class="d-flex flex-column w-100" > - <div class="d-flex flex-column"> - <div class="d-flex flex-row"> - <div - @click="showBottleneckDetails(index)" - class="mt-auto mb-auto text-info text-left" + <div class="d-flex flex-row pl-2"> + <div + @click="showBottleneckDetails(index)" + class="mt-auto mb-auto text-info text-left" + > + <UISpinnerButton + :state="showBottleneckDetail === index" + :icons="['angle-right', 'angle-down']" + class="text-info" + /> + </div> + <a @click="moveToBottleneck(index)" href="#"> + {{ bottleneck.properties.objnam }} + </a> + </div> + + <div class="ml-3 d-flex flex-row" v-if="showBottleneckDetail === index"> + <table> + <tr + v-for="(info, index) in Object.keys(bottleneck.properties)" + :key="index" + class="mr-1 condensed text-muted" > - <font-awesome-icon - class="pointer" - v-if="showBottleneckDetail === index" - icon="angle-down" - fixed-width - ></font-awesome-icon> - <font-awesome-icon - class="pointer" - v-if="!(showBottleneckDetail === index)" - icon="angle-right" - fixed-width - ></font-awesome-icon> - </div> - <a @click="moveToBottleneck(index)" href="#"> - {{ bottleneck.properties.objnam }} - </a> - </div> - - <div class="ml-3 d-flex flex-row" v-if="showBottleneckDetail === index"> - <table> - <tr - v-for="(info, index) in Object.keys(bottleneck.properties)" - :key="index" - class="mr-1 condensed text-muted" - > - <td class="text-left">{{ info }}</td> - <td class="pl-3 text-left"> - {{ bottleneck.properties[info] }} - </td> - </tr> - </table> - </div> + <td class="text-left">{{ info }}</td> + <td class="pl-3 text-left"> + {{ bottleneck.properties[info] }} + </td> + </tr> + </table> </div> </div> </div> </template> <style lang="sass" scoped> + +table + width: 100% + +tr:nth-child(even) + background: #f8f8f8 + .bottleneckdetails width: 100% overflow-y: auto + > div + border-top: dashed 1px #dee2e6 + &:first-child + border-top: none .split max-height: 35vh @@ -88,15 +90,13 @@ import { displayError } from "@/lib/errors.js"; import { mapState } from "vuex"; -const NO_BOTTLENECK = -1; - export default { name: "bottleneckdetails", props: ["entry"], data() { return { bottlenecks: [], - showBottleneckDetail: NO_BOTTLENECK + showBottleneckDetail: null }; }, mounted() { @@ -160,11 +160,11 @@ }); }, showBottleneckDetails(index) { - if (index == this.showBottleneckDetail) { - this.showBottleneckDetail = false; - return; + if (index === this.showBottleneckDetail) { + this.showBottleneckDetail = null; + } else { + this.showBottleneckDetail = index; } - this.showBottleneckDetail = index; } } };
--- a/client/src/components/importoverview/FairwayDimension.vue Tue Apr 02 10:07:48 2019 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -<template> - <div>Fairwaydimension</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> - */ -export default { - name: "fairwaydimensiondetails" -}; -</script> - -<style></style>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/importoverview/FairwayDimensionDetail.vue Fri Apr 05 10:04:45 2019 +0200 @@ -0,0 +1,20 @@ +<template> + <div>Fairwaydimension</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> + */ +export default {}; +</script>
--- a/client/src/components/importoverview/ImportOverview.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importoverview/ImportOverview.vue Fri Apr 05 10:04:45 2019 +0200 @@ -7,7 +7,7 @@ :actions="[{ callback: loadUpdatedLogs, icon: 'redo' }]" /> <div class="position-relative"> - <SpinnerOverlay v-if="loading" /> + <UISpinnerOverlay v-if="loading" /> <div class="border-bottom p-2 d-flex justify-content-between"> <Filters></Filters> <button @@ -78,7 +78,7 @@ <UITableBody :data="filteredImports() | sortTable(sortColumn, sortDirection)" :isActive="item => item.id === this.show" - maxHeight="73vh" + maxHeight="70vh" > <template v-slot:row="{ item: entry }"> <LogEntry :entry="entry"></LogEntry>
--- a/client/src/components/importoverview/LogDetail.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importoverview/LogDetail.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,78 +1,53 @@ <template> <div> <div - class="d-flex fex-row" + class="d-flex border-bottom" style="padding-left: 3px;" - v-if="hasAdditionalInfo || isStretch || isSoundingResult" + v-if="hasAdditionalInfo || isST || isSR" > <div v-if="hasAdditionalInfo"> - <font-awesome-icon - v-if="entry.id === showAdditional" - @click="toggleAdditionalInfo" - class="my-auto mr-1 text-info pointer" - icon="angle-down" - fixed-width - ></font-awesome-icon> - <font-awesome-icon - v-if="entry.id !== showAdditional" + <UISpinnerButton @click="toggleAdditionalInfo" - class="my-auto mr-1 text-info pointer" - icon="angle-right" - fixed-width - ></font-awesome-icon> + :state="entry.id === showAdditional" + :icons="['angle-right', 'angle-down']" + class="text-info d-inline-block" + /> <span class="text-info"><translate>Additional Info</translate></span> + <span class="text-info" v-if="isAGM && details.summary"> + ({{ details.summary.length }}) + </span> <span - class="text-info" - v-if="isApprovedGaugeMeasurement && details.summary" - > - ({{ details.summary.length }})</span - > - <span - v-if="isBottleneck && details.summary && details.summary.bottlenecks" + v-if="isBN && details.summary && details.summary.bottlenecks" class="text-info text-left" > - ({{ details.summary.bottlenecks.length }})</span - > - <span class="text-left" v-if="isFairwayDimension" - >{{ details.summary["source-organization"] }} (LOS: - {{ details.summary.los }})</span - > + ({{ details.summary.bottlenecks.length }}) + </span> + <span class="text-left" v-if="isFD"> + {{ details.summary["source-organization"] }} + (LOS: {{ details.summary.los }}) + </span> </div> - <StretchDetail - v-if="isStretch && isPending" - :entry="entry" - ></StretchDetail> - <SoundingResultDetail - :entry="entry" - v-if="isSoundingResult && isPending" - ></SoundingResultDetail> + <StretchDetail v-if="isST && isPending" :entry="entry" /> + <SoundingResultDetail :entry="entry" v-if="isSR && isPending" /> </div> - <AdditionalDetail - v-if="entry.id === showAdditional && isPending" - class="ml-2 d-flex flex-row" - :entry="entry" - ></AdditionalDetail> - <div class="d-flex fex-row" style="padding-left: 3px;"> - <font-awesome-icon - v-if="entry.id === showLogs" + <div + v-if="entry.id === showAdditional && isPending && (isFD || isAGM || isBN)" + class="d-flex border-bottom" + > + <FairwayDimensionDetail :entry="entry" v-if="isFD" /> + <ApprovedGaugeMeasurementDetail :entry="entry" v-if="isAGM" /> + <BottleneckDetail :entry="entry" v-if="isBN" /> + </div> + <div class="d-flex" style="padding-left: 3px;"> + <UISpinnerButton @click="toggleAdditionalLogging" - class="my-auto mr-1 text-info pointer" - icon="angle-down" - fixed-width - ></font-awesome-icon> - <font-awesome-icon - v-if="entry.id !== showLogs" - @click="toggleAdditionalLogging" - class="my-auto mr-1 text-info pointer" - icon="angle-right" - fixed-width - ></font-awesome-icon> + :state="entry.id === showLogs" + :icons="['angle-right', 'angle-down']" + classes="text-info" + /> <span class="text-info"><translate>Logs</translate></span> </div> - <AdditionalLog - v-if="entry.id === showLogs" - class="d-flex flex-row" - ></AdditionalLog> + <AdditionalLog v-if="entry.id === showLogs" class="d-flex flex-row" /> </div> </template> @@ -97,7 +72,10 @@ components: { SoundingResultDetail: () => import("./SoundingResultDetail.vue"), StretchDetail: () => import("./StretchDetails.vue"), - AdditionalDetail: () => import("./AdditionalDetail.vue"), + FairwayDimensionDetail: () => import("./FairwayDimensionDetail.vue"), + ApprovedGaugeMeasurementDetail: () => + import("./ApprovedGaugeMeasurementDetail.vue"), + BottleneckDetail: () => import("./BottleneckDetail.vue"), AdditionalLog: () => import("./AdditionalLog.vue") }, props: ["entry"], @@ -110,23 +88,21 @@ return this.entry.state == "pending"; }, hasAdditionalInfo() { - return ( - this.isPending && (this.isApprovedGaugeMeasurement || this.isBottleneck) - ); + return this.isPending && (this.isAGM || this.isBN); }, - isFairwayDimension() { + isFD() { return this.kind === "FD"; }, - isApprovedGaugeMeasurement() { + isAGM() { return this.kind === "AGM"; }, - isBottleneck() { + isBN() { return this.kind === "BN" || this.kind === "UBN"; }, - isStretch() { + isST() { return this.kind === "ST"; }, - isSoundingResult() { + isSR() { return this.kind === "SR"; } },
--- a/client/src/components/importoverview/LogEntry.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importoverview/LogEntry.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,12 +1,12 @@ <template> <div class="row w-100 no-gutters text-left"> <div style="width: 79px;" class="table-cell d-flex justify-content-between"> - <font-awesome-icon + <UISpinnerButton @click="toggleDetails" - :class="'pointer ' + (entry.id === show ? 'text-white' : 'text-info')" - :icon="entry.id === show ? 'angle-down' : 'angle-right'" - fixed-width - ></font-awesome-icon> + :loading="loading" + :state="entry.id === show" + :icons="['angle-right', 'angle-down']" + /> {{ entry.id }} </div> <div style="width: 53px;" class="table-cell center"> @@ -33,7 +33,7 @@ class="text-warning" icon="exclamation-triangle" fixed-width - ></font-awesome-icon> + /> </div> <div style="flex-grow: 1; padding: 0;" class="table-cell text-right"> <button @@ -41,20 +41,14 @@ @click="toggleApproval($options.STATES.APPROVED)" v-if="entry.state === 'pending'" > - <font-awesome-icon - class="small pointer" - icon="check" - ></font-awesome-icon> + <font-awesome-icon class="small pointer" icon="check" /> </button> <button :class="['action rejected', { active: isRejected }]" @click="toggleApproval($options.STATES.REJECTED)" v-if="entry.state === 'pending'" > - <font-awesome-icon - icon="times" - class="small pointer" - ></font-awesome-icon> + <font-awesome-icon icon="times" class="small pointer" /> </button> </div> </div> @@ -109,6 +103,11 @@ export default { STATES, props: ["entry"], + data() { + return { + loading: false + }; + }, computed: { ...mapState("imports", ["show"]), needsApproval() { @@ -135,6 +134,7 @@ this.$store.commit("imports/hideAdditionalInfo"); this.$store.commit("imports/hideAdditionalLogs"); } else { + this.loading = true; HTTP.get("/imports/" + this.entry.id, { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }) @@ -148,7 +148,8 @@ title: this.$gettext("Backend Error"), message: `${status}: ${data.message || data}` }); - }); + }) + .finally(() => (this.loading = false)); } } }
--- a/client/src/components/importoverview/SoundingResultDetail.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importoverview/SoundingResultDetail.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,6 +1,5 @@ <template> <div> - <span class="empty"></span> <a @click="zoomTo()" class="text-info pointer"> {{ details.summary.bottleneck }} </a>
--- a/client/src/components/importoverview/StretchDetails.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importoverview/StretchDetails.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,6 +1,5 @@ <template> <div> - <span class="empty"> </span> <a @click="zoomToStretch()" class="text-info pointer">{{ details.summary.stretch }}</a>
--- a/client/src/components/importschedule/Importschedule.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importschedule/Importschedule.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,6 +1,6 @@ <template> <div class="d-flex flex-row"> - <Spacer></Spacer> + <Spacer /> <div class="mt-2 w-100"> <div class="card flex-grow-1 schedulecard shadow-xs"> <UIBoxHeader icon="clock" :title="importScheduleLabel" /> @@ -8,7 +8,7 @@ <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> + <font-awesome-icon icon="search" /> </span> </div> <input @@ -47,7 +47,7 @@ class="fa-fw mr-2" fixed-width icon="check" - ></font-awesome-icon> + /> </div> <div class="table-cell col justify-content-end"> <button @@ -55,24 +55,21 @@ class="btn btn-xs btn-dark mr-1" :disabled="importScheduleDetailVisible" > - <font-awesome-icon - icon="pencil-alt" - fixed-width - ></font-awesome-icon> + <font-awesome-icon icon="pencil-alt" fixed-width /> </button> <button @click="deleteSchedule(schedule)" class="btn btn-xs btn-dark mr-1" :disabled="importScheduleDetailVisible" > - <font-awesome-icon icon="trash" fixed-width></font-awesome-icon> + <font-awesome-icon icon="trash" fixed-width /> </button> <button @click="triggerManualImport(schedule.id)" class="btn btn-xs btn-dark" :disabled="importScheduleDetailVisible" > - <font-awesome-icon icon="play" fixed-width></font-awesome-icon> + <font-awesome-icon icon="play" fixed-width /> </button> </div> </template> @@ -88,7 +85,7 @@ </div> </div> </div> - <Importscheduledetail></Importscheduledetail> + <Importscheduledetail /> </div> </template>
--- a/client/src/components/importschedule/Importscheduledetail.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importschedule/Importscheduledetail.vue Fri Apr 05 10:04:45 2019 +0200 @@ -15,33 +15,33 @@ </small> </div> <select v-model="import_" class="custom-select" id="importtype"> - <option :value="$options.IMPORTTYPES.BOTTLENECK" - ><translate>Bottlenecks</translate></option - > - <option :value="$options.IMPORTTYPES.WATERWAYAXIS" - ><translate>Waterway axis</translate></option - > - <option :value="$options.IMPORTTYPES.GAUGEMEASUREMENT" - ><translate>Gauge measurement</translate></option - > - <option :value="$options.IMPORTTYPES.FAIRWAYAVAILABILITY" - ><translate>Available fairway depths</translate></option - > - <option :value="$options.IMPORTTYPES.WATERWAYAREA" - ><translate>Waterway area</translate></option - > - <option :value="$options.IMPORTTYPES.FAIRWAYDIMENSION" - ><translate>Fairway dimension</translate></option - > - <option :value="$options.IMPORTTYPES.WATERWAYGAUGES" - ><translate>Waterway gauges</translate></option - > - <option :value="$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL" - ><translate>Distance marks virtual</translate></option - > - <option :value="$options.IMPORTTYPES.DISTANCEMARKSASHORE" - ><translate>Distance marks ashore</translate></option - > + <option :value="$options.IMPORTTYPES.BOTTLENECK"> + <translate>Bottlenecks</translate> + </option> + <option :value="$options.IMPORTTYPES.WATERWAYAXIS"> + <translate>Waterway axis</translate> + </option> + <option :value="$options.IMPORTTYPES.GAUGEMEASUREMENT"> + <translate>Gauge measurement</translate> + </option> + <option :value="$options.IMPORTTYPES.FAIRWAYAVAILABILITY"> + <translate>Available fairway depths</translate> + </option> + <option :value="$options.IMPORTTYPES.WATERWAYAREA"> + <translate>Waterway area</translate> + </option> + <option :value="$options.IMPORTTYPES.FAIRWAYDIMENSION"> + <translate>Fairway dimension</translate> + </option> + <option :value="$options.IMPORTTYPES.WATERWAYGAUGES"> + <translate>Waterway gauges</translate> + </option> + <option :value="$options.IMPORTTYPES.DISTANCEMARKSVIRTUAL"> + <translate>Distance marks virtual</translate> + </option> + <option :value="$options.IMPORTTYPES.DISTANCEMARKSASHORE"> + <translate>Distance marks ashore</translate> + </option> </select> </div> <div class="flex-column ml-4"> @@ -95,7 +95,7 @@ " @urlChanged="setUrl" :url="url" - ></Availablefairwaydepth> + /> <Bottleneck v-if="import_ == $options.IMPORTTYPES.BOTTLENECK" @urlChanged="setUrl" @@ -103,7 +103,7 @@ :url="url" :tolerance="tolerance" :directImport="directImport" - ></Bottleneck> + /> <Distancemarksvirtual v-if="import_ == $options.IMPORTTYPES.DISTANCEMARKSVIRTUAL" @urlChanged="setUrl" @@ -112,7 +112,7 @@ :url="url" :username="username" :password="password" - ></Distancemarksvirtual> + /> <Distancemarksashore v-if="import_ == $options.IMPORTTYPES.DISTANCEMARKSASHORE" @urlChanged="setUrl" @@ -121,7 +121,7 @@ :url="url" :featureType="featureType" :sortBy="sortBy" - ></Distancemarksashore> + /> <Faiwaydimensions v-if="import_ == $options.IMPORTTYPES.FAIRWAYDIMENSION" @urlChanged="setUrl" @@ -140,14 +140,14 @@ :maxWidth="maxWidth" :sourceOrganization="sourceOrganization" :depth="depth" - ></Faiwaydimensions> + /> <Gaugemeasurement v-if=" import_ == $options.IMPORTTYPES.GAUGEMEASUREMENT && !directImport " @urlChanged="setUrl" :url="url" - ></Gaugemeasurement> + /> <Waterwayarea v-if="import_ == $options.IMPORTTYPES.WATERWAYAREA" @urlChanged="setUrl" @@ -156,7 +156,7 @@ :url="url" :featureType="featureType" :sortBy="sortBy" - ></Waterwayarea> + /> <Waterwaygauges v-if="import_ == $options.IMPORTTYPES.WATERWAYGAUGES" @urlChanged="setUrl" @@ -165,7 +165,7 @@ :url="url" :username="username" :password="password" - ></Waterwaygauges> + /> <Waterwayaxis v-if="import_ == $options.IMPORTTYPES.WATERWAYAXIS" @urlChanged="setUrl" @@ -174,7 +174,7 @@ :url="url" :featureType="featureType" :sortBy="sortBy" - ></Waterwayaxis> + /> <template v-if="!directImport || !directImportAvailable"> <div class="d-flex flex-row"> @@ -411,12 +411,8 @@ class="shadow-sm btn btn-outline-info trigger" :disabled="!triggerActive || !isValid" > - <font-awesome-icon - class="fa-fw mr-2" - fixed-width - icon="play" - ></font-awesome-icon - ><translate>Trigger import</translate> + <font-awesome-icon class="fa-fw mr-2" fixed-width icon="play" /> + <translate>Trigger import</translate> </button> </form> </div>
--- a/client/src/components/importschedule/importtypes/Distancemarksvirtual.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importschedule/importtypes/Distancemarksvirtual.vue Fri Apr 05 10:04:45 2019 +0200 @@ -56,9 +56,7 @@ class="input-group-text ml-2" @click="passwordVisible = !passwordVisible" > - <font-awesome-icon - :icon="passwordVisible ? 'eye-slash' : 'eye'" - ></font-awesome-icon> + <font-awesome-icon :icon="passwordVisible ? 'eye-slash' : 'eye'" /> </span> </div> <div v-if="!password" class="d-flex flex-row">
--- a/client/src/components/importschedule/importtypes/Waterwaygauges.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/importschedule/importtypes/Waterwaygauges.vue Fri Apr 05 10:04:45 2019 +0200 @@ -56,9 +56,7 @@ class="input-group-text ml-2" @click="passwordVisible = !passwordVisible" > - <font-awesome-icon - :icon="passwordVisible ? 'eye-slash' : 'eye'" - ></font-awesome-icon> + <font-awesome-icon :icon="passwordVisible ? 'eye-slash' : 'eye'" /> </span> </div> <div v-if="!password" class="d-flex flex-row">
--- a/client/src/components/layers/Layerselect.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/layers/Layerselect.vue Fri Apr 05 10:04:45 2019 +0200 @@ -21,6 +21,9 @@ <div v-if="isVisible && isBottleneckIsolineLayer"> <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" /> </div> + <div v-if="isVisible && isBottleneckDifferences"> + <img class="rounded my-1 d-block" :src="differencesLegendImgDataURL" /> + </div> </div> </template> @@ -51,6 +54,7 @@ import { HTTP } from "@/lib/http"; import { mapState } from "vuex"; import { LAYERS } from "@/store/map.js"; + export default { props: ["layername", "layerindex", "isVisible"], name: "layerselect", @@ -58,9 +62,15 @@ LegendElement: () => import("./LegendElement.vue") }, computed: { - ...mapState("map", ["isolinesLegendImgDataURL"]), + ...mapState("map", [ + "isolinesLegendImgDataURL", + "differencesLegendImgDataURL" + ]), isBottleneckIsolineLayer() { return this.layername == LAYERS.BOTTLENECKISOLINE; + }, + isBottleneckDifferences() { + return this.layername == LAYERS.DIFFERENCES; } }, methods: { @@ -89,6 +99,24 @@ reader.readAsDataURL(response.data); }); } + if (this.isBottleneckDifferences) { + const src = + "/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=sounding_differences&legend_options=columns:4;fontAntiAliasing:true"; + HTTP.get(src, { + headers: { + Accept: "image/png", + "X-Gemma-Auth": localStorage.getItem("token") + }, + responseType: "blob" + }).then(response => { + var that = this; + const reader = new FileReader(); + reader.onload = function() { + that.$store.commit("map/differencesLegendImgDataURL", this.result); + }; + reader.readAsDataURL(response.data); + }); + } } }; </script>
--- a/client/src/components/splitscreen/Splitscreen.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/splitscreen/Splitscreen.vue Fri Apr 05 10:04:45 2019 +0200 @@ -14,7 +14,7 @@ v-if="activeSplitscreen" /> <div class="d-flex flex-fill"> - <SpinnerOverlay v-if="splitscreenLoading" /> + <UISpinnerOverlay v-if="splitscreenLoading" /> <transition name="fade" mode="out-in"> <component :is="activeSplitscreen.component"
--- a/client/src/components/systemconfiguration/ColorSettings.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/systemconfiguration/ColorSettings.vue Fri Apr 05 10:04:45 2019 +0200 @@ -43,9 +43,7 @@ * Bernhard Reiter <bernhard@intevation.de> * Markus Kottländer <markus@intevation.de> */ -import { Chrome } from "vue-color"; -import { Compact } from "vue-color"; - +import { Chrome, Compact } from "vue-color"; import { HTTP } from "@/lib/http"; import { displayError } from "@/lib/errors.js";
--- a/client/src/components/systemconfiguration/Systemconfiguration.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/systemconfiguration/Systemconfiguration.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,6 +1,6 @@ <template> <div class="d-flex flex-row"> - <Spacer></Spacer> + <Spacer /> <div class="card sysconfig mt-2 shadow-xs"> <UIBoxHeader icon="wrench" :title="systemconfigurationLabel" /> <div class="card-body text-left">
--- a/client/src/components/toolbar/Gauges.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Gauges.vue Fri Apr 05 10:04:45 2019 +0200 @@ -2,11 +2,12 @@ <div @click="$store.commit('application/showGauges', !showGauges)" class="toolbar-button" + v-tooltip.right="label" > <font-awesome-icon icon="ruler-vertical" :class="{ 'text-info': showGauges }" - ></font-awesome-icon> + /> </div> </template> @@ -28,7 +29,10 @@ export default { computed: { - ...mapState("application", ["showGauges"]) + ...mapState("application", ["showGauges"]), + label() { + return this.$gettext("Gauges"); + } } }; </script>
--- a/client/src/components/toolbar/Identify.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Identify.vue Fri Apr 05 10:04:45 2019 +0200 @@ -2,11 +2,9 @@ <div @click="$store.commit('application/showIdentify', !showIdentify)" class="toolbar-button" + v-tooltip.right="label" > - <font-awesome-icon - icon="info" - :class="{ 'text-info': showIdentify }" - ></font-awesome-icon> + <font-awesome-icon icon="info" :class="{ 'text-info': showIdentify }" /> <span :class="[ 'indicator', @@ -46,6 +44,9 @@ ...mapGetters("map", ["filteredIdentifiedFeatures"]), badgeCount() { return this.filteredIdentifiedFeatures.length + !!this.currentMeasurement; + }, + label() { + return this.$gettext("Identified Features"); } } };
--- a/client/src/components/toolbar/Layers.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Layers.vue Fri Apr 05 10:04:45 2019 +0200 @@ -2,11 +2,12 @@ <div @click="$store.commit('application/showLayers', !showLayers)" class="toolbar-button" + v-tooltip.right="label" > <font-awesome-icon icon="layer-group" :class="{ 'text-info': showLayers }" - ></font-awesome-icon> + /> </div> </template> @@ -29,7 +30,10 @@ export default { name: "layers", computed: { - ...mapState("application", ["showLayers"]) + ...mapState("application", ["showLayers"]), + label() { + return this.$gettext("Map Layers"); + } } }; </script>
--- a/client/src/components/toolbar/Linetool.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Linetool.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,9 +1,9 @@ <template> - <div @click="toggleLineTool" class="toolbar-button"> + <div @click="toggleLineTool" class="toolbar-button" v-tooltip.right="label"> <font-awesome-icon icon="ruler" :class="{ 'text-info': lineTool && lineTool.getActive() }" - ></font-awesome-icon> + /> </div> </template> @@ -28,7 +28,10 @@ name: "linetool", computed: { ...mapGetters("map", ["getVSourceByName"]), - ...mapState("map", ["lineTool", "polygonTool", "cutTool"]) + ...mapState("map", ["lineTool", "polygonTool", "cutTool"]), + label() { + return this.$gettext("Measure Distance"); + } }, methods: { toggleLineTool() {
--- a/client/src/components/toolbar/Pdftool.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Pdftool.vue Fri Apr 05 10:04:45 2019 +0200 @@ -2,11 +2,9 @@ <div @click="$store.commit('application/showPdfTool', !showPdfTool)" class="toolbar-button" + v-tooltip.right="label" > - <font-awesome-icon - icon="file-pdf" - :class="{ 'text-info': showPdfTool }" - ></font-awesome-icon> + <font-awesome-icon icon="file-pdf" :class="{ 'text-info': showPdfTool }" /> </div> </template> @@ -29,7 +27,10 @@ export default { name: "pdftool", computed: { - ...mapState("application", ["showPdfTool"]) + ...mapState("application", ["showPdfTool"]), + label() { + return this.$gettext("Generate PDF"); + } } }; </script>
--- a/client/src/components/toolbar/Polygontool.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Polygontool.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,9 +1,13 @@ <template> - <div @click="togglePolygonTool" class="toolbar-button"> + <div + @click="togglePolygonTool" + class="toolbar-button" + v-tooltip.right="label" + > <font-awesome-icon icon="draw-polygon" :class="{ 'text-info': polygonTool && polygonTool.getActive() }" - ></font-awesome-icon> + /> </div> </template> @@ -28,7 +32,10 @@ name: "polygontool", computed: { ...mapGetters("map", ["getVSourceByName"]), - ...mapState("map", ["lineTool", "polygonTool", "cutTool"]) + ...mapState("map", ["lineTool", "polygonTool", "cutTool"]), + label() { + return this.$gettext("Measure Area"); + } }, methods: { togglePolygonTool() {
--- a/client/src/components/toolbar/Profiles.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Profiles.vue Fri Apr 05 10:04:45 2019 +0200 @@ -2,11 +2,12 @@ <div @click="$store.commit('application/showProfiles', !showProfiles)" class="toolbar-button" + v-tooltip.right="label" > <font-awesome-icon icon="chart-area" :class="{ 'text-info': showProfiles }" - ></font-awesome-icon> + /> </div> </template> @@ -29,7 +30,10 @@ export default { name: "profiles", computed: { - ...mapState("application", ["showProfiles"]) + ...mapState("application", ["showProfiles"]), + label() { + return this.$gettext("Profiles"); + } } }; </script>
--- a/client/src/components/toolbar/Toolbar.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/toolbar/Toolbar.vue Fri Apr 05 10:04:45 2019 +0200 @@ -6,13 +6,13 @@ (expandToolbar ? 'expanded' : 'collapsed') " > - <Identify class="pointer" /> - <Layers class="pointer" /> - <Profiles class="pointer" /> - <Gauges class="pointer" /> - <Linetool class="pointer" /> - <Polygontool class="pointer" /> - <Pdftool class="pointer" /> + <Identify /> + <Layers /> + <Profiles /> + <Gauges /> + <Linetool /> + <Polygontool /> + <Pdftool /> </div> <div @click="$store.commit('application/expandToolbar', !expandToolbar)" @@ -21,7 +21,7 @@ <font-awesome-icon class="pointer" :icon="expandToolbar ? 'angle-up' : 'angle-down'" - ></font-awesome-icon> + /> </div> </div> </template> @@ -34,6 +34,7 @@ overflow: hidden; transition: max-height 0.4s; margin-bottom: auto; + cursor: pointer; } .toolbar-collapsed {
--- a/client/src/components/ui/UIBoxHeader.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/ui/UIBoxHeader.vue Fri Apr 05 10:04:45 2019 +0200 @@ -58,6 +58,8 @@ padding-left: 0.25rem .box-icon margin-right: 0.25rem + .box-control + margin-left: 3px </style> <script>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/components/ui/UISpinnerButton.vue Fri Apr 05 10:04:45 2019 +0200 @@ -0,0 +1,56 @@ +<template> + <div :class="classesString" @click="$emit('click')"> + <font-awesome-icon :icon="iconString" :spin="loading" fixed-width /> + <slot /> + </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.kottlaender@intevation.de> + */ +export default { + props: { + loading: { + type: Boolean, + default: false + }, + state: [Number, Boolean], + icons: { + type: [String, Array], + default: () => ["angle-down", "angle-up"] + }, + classes: { + type: [String, Array], + default: () => ["text-info", "text-white"] + } + }, + computed: { + classesString() { + return ( + "pointer " + + (Array.isArray(this.classes) + ? this.classes[Number(this.state)] + : this.classes) + ); + }, + iconString() { + return this.loading + ? "spinner" + : Array.isArray(this.icons) + ? this.icons[Number(this.state)] + : this.icons; + } + } +}; +</script>
--- a/client/src/components/ui/UITableBody.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/ui/UITableBody.vue Fri Apr 05 10:04:45 2019 +0200 @@ -38,7 +38,7 @@ &.center justify-content: center .expand - border-bottom: solid 1px $color-info + border-bottom: solid 2px $color-info &.active > .row
--- a/client/src/components/usermanagement/Passwordfield.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/usermanagement/Passwordfield.vue Fri Apr 05 10:04:45 2019 +0200 @@ -12,14 +12,12 @@ :required="required" /> <span class="input-group-text" @click="showPassword"> - <font-awesome-icon - :icon="readablePassword ? 'eye-slash' : 'eye'" - ></font-awesome-icon> + <font-awesome-icon :icon="readablePassword ? 'eye-slash' : 'eye'" /> </span> </div> <div v-show="passworderrors" class="text-danger"> <small> - <font-awesome-icon icon="exclamation-triangle"></font-awesome-icon> + <font-awesome-icon icon="exclamation-triangle" /> {{ this.passworderrors }} </small> </div>
--- a/client/src/components/usermanagement/Userdetail.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/usermanagement/Userdetail.vue Fri Apr 05 10:04:45 2019 +0200 @@ -20,9 +20,7 @@ /> <div v-show="errors.user" class="text-danger"> <small> - <font-awesome-icon - icon="exclamation-triangle" - ></font-awesome-icon> + <font-awesome-icon icon="exclamation-triangle" /> {{ errors.user }} </small> </div> @@ -46,9 +44,7 @@ </select> <div v-show="errors.country" class="text-danger"> <small> - <font-awesome-icon - icon="exclamation-triangle" - ></font-awesome-icon> + <font-awesome-icon icon="exclamation-triangle" /> {{ errors.country }} </small> </div> @@ -65,9 +61,7 @@ /> <div v-show="errors.email" class="text-danger"> <small> - <font-awesome-icon - icon="exclamation-triangle" - ></font-awesome-icon> + <font-awesome-icon icon="exclamation-triangle" /> {{ errors.email }} </small> </div> @@ -94,9 +88,7 @@ </select> <div v-show="errors.role" class="text-danger"> <small> - <font-awesome-icon - icon="exclamation-triangle" - ></font-awesome-icon> + <font-awesome-icon icon="exclamation-triangle" /> {{ errors.role }} </small> </div> @@ -107,7 +99,7 @@ :placeholder="passwordPlaceholder" :label="passwordLabel" :passworderrors="errors.password" - ></PasswordField> + /> </div> <div class="form-group row"> <PasswordField @@ -115,7 +107,7 @@ :placeholder="passwordRePlaceholder" :label="passwordReLabel" :passworderrors="errors.passwordre" - ></PasswordField> + /> </div> </div> <div> @@ -124,7 +116,7 @@ :disabled="submitted" class="shadow-sm btn btn-info submit-button" > - <translate>Submit</translate> + <translate>Save</translate> </button> </div> </form>
--- a/client/src/components/usermanagement/Usermanagement.vue Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/components/usermanagement/Usermanagement.vue Fri Apr 05 10:04:45 2019 +0200 @@ -1,6 +1,6 @@ <template> <div class="main d-flex flex-row" style="position: relative;"> - <Spacer></Spacer> + <Spacer /> <div class="d-flex content py-2"> <div :class="userlistStyle"> <div class="card shadow-xs"> @@ -27,7 +27,7 @@ v-tooltip="roleLabel(user.role)" :icon="roleIcon(user.role)" class="fa-lg" - ></font-awesome-icon> + /> </div> <div class="table-cell col-4" @click="selectUser(user.user)"> {{ user.user }} @@ -68,7 +68,7 @@ v-if="this.page !== 1" class="mr-2 btn btn-sm btn-light align-self-center" > - <font-awesome-icon icon="angle-left"></font-awesome-icon> + <font-awesome-icon icon="angle-left" /> </button> {{ this.page }} / {{ this.pages }} <button @@ -76,7 +76,7 @@ v-if="this.page !== this.pages" class="ml-2 btn btn-sm btn-light align-self-center" > - <font-awesome-icon icon="angle-right"></font-awesome-icon> + <font-awesome-icon icon="angle-right" /> </button> </div> <button @click="addUser" class="btn btn-info addbutton shadow-sm"> @@ -85,7 +85,7 @@ </div> </div> </div> - <Userdetail v-if="isUserDetailsVisible"></Userdetail> + <Userdetail v-if="isUserDetailsVisible" /> </div> </div> </template> @@ -132,14 +132,8 @@ import { mapGetters, mapState } from "vuex"; import { displayError, displayInfo } from "@/lib/errors.js"; import { HTTP } from "@/lib/http"; -import Vue from "vue"; -import { VTooltip, VPopover, VClosePopover } from "v-tooltip"; import { sortTable } from "@/lib/mixins"; -Vue.directive("tooltip", VTooltip); -Vue.directive("close-popover", VClosePopover); -Vue.component("v-popover", VPopover); - export default { name: "userview", mixins: [sortTable],
--- a/client/src/main.js Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/main.js Fri Apr 05 10:04:45 2019 +0200 @@ -34,6 +34,7 @@ import UITableHeader from "@/components/ui/UITableHeader"; import UITableBody from "@/components/ui/UITableBody"; import UISpinnerOverlay from "@/components/ui/UISpinnerOverlay"; +import UISpinnerButton from "@/components/ui/UISpinnerButton"; // styles import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; @@ -180,6 +181,7 @@ Vue.component("UITableHeader", UITableHeader); Vue.component("UITableBody", UITableBody); Vue.component("UISpinnerOverlay", UISpinnerOverlay); +Vue.component("UISpinnerButton", UISpinnerButton); // register global filters for (let name in filters) Vue.filter(name, filters[name]);
--- a/client/src/store/imports.js Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/store/imports.js Fri Apr 05 10:04:45 2019 +0200 @@ -32,6 +32,7 @@ declined: false, warning: false, stretches: [], + selectedStretchId: null, imports: [], reviewed: [], show: null, @@ -128,6 +129,9 @@ setStretches: (state, stretches) => { state.stretches = stretches; }, + selectedStretchId: (state, id) => { + state.selectedStretchId = id; + }, setReviewed: (state, reviewed) => { state.reviewed = reviewed; },
--- a/client/src/store/map.js Tue Apr 02 10:07:48 2019 +0200 +++ b/client/src/store/map.js Fri Apr 05 10:04:45 2019 +0200 @@ -80,6 +80,7 @@ polygonTool: null, // open layers interaction object (Draw) cutTool: null, // open layers interaction object (Draw) isolinesLegendImgDataURL: "", + differencesLegendImgDataURL: "", layers: { [LAYERS.OPENSTREETMAP]: { name: LAYERS.OPENSTREETMAP, @@ -121,15 +122,30 @@ source: new VectorSource({ strategy: bboxStrategy }), - style: new Style({ - stroke: new Stroke({ - color: "rgba(250, 200, 0, .8)", - width: 2 - }), - fill: new Fill({ - color: "rgba(250, 200, 10, .3)" - }) - }) + style: feature => { + let style = new Style({ + stroke: new Stroke({ + color: "rgba(250, 200, 0, .8)", + width: 2 + }), + fill: new Fill({ + color: "rgba(250, 200, 10, .3)" + }) + }); + if (feature.get("highlighted")) { + style = new Style({ + stroke: new Stroke({ + color: "rgba(250, 240, 10, .9)", + width: 5 + }), + fill: new Fill({ + color: "rgba(250, 240, 0, .7)" + }) + }); + } + + return style; + } }), isVisible: false }, @@ -644,6 +660,9 @@ }, isolinesLegendImgDataURL: (state, isolinesLegendImgDataURL) => { state.isolinesLegendImgDataURL = isolinesLegendImgDataURL; + }, + differencesLegendImgDataURL: (state, differencesLegendImgDataURL) => { + state.differencesLegendImgDataURL = differencesLegendImgDataURL; } }, actions: { @@ -756,7 +775,6 @@ for (let feature of features) { let id = feature.getId(); - // avoid identifying the same feature twice if ( identifiedFeatures.findIndex( @@ -820,6 +838,28 @@ }); } } + + // get selected stretch + if (/^stretches/.test(id)) { + if (rootState.imports.selectedStretchId === feature.getId()) { + commit("imports/selectedStretchId", null, { root: true }); + } else { + commit("imports/selectedStretchId", feature.getId(), { + root: true + }); + commit("moveMap", { + coordinates: getCenter( + feature + .getGeometry() + .clone() + .transform("EPSG:3857", "EPSG:4326") + .getExtent() + ), + zoom: null, + preventZoomOut: true + }); + } + } } commit("setIdentifiedFeatures", identifiedFeatures);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/tests/e2e/reports/import.xml Fri Apr 05 10:04:45 2019 +0200 @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<testsuites errors="0" + failures="0" + tests="6"> + + <testsuite name="import" + errors="0" failures="0" hostname="" id="" package="import" skipped="6" + tests="6" time="0.000" timestamp="Thu, 04 Apr 2019 10:16:03 GMT"> + + + + <system-err> + Error while running [Protocols / Loading protocols]: + + TypeError: browser.url(...).waitForElementVisible(...).setValue(...).setValue(...).click(...).pause(...).click(...).pause(...).click(...).pause(...).url.contains is not a function + at Object.Loading protocols (/home/thomas/go/src/gemma.intevation.de/gemma/client/tests/e2e/specs/protocols.js:31:12) + at Module.call (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/module.js:62:34) + at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/testcase.js:70:29 + at _fulfilled (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:834:54) + at self.promiseDispatch.done (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:863:30) + at Promise.promise.promiseDispatch (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:796:13) + at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:556:49 + at runSingle (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:137:13) + at flush (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:125:13) + at _combinedTickCallback (internal/process/next_tick.js:132:7) + </system-err> + + + + + <testcase + name="Bottleneck import" classname="import"> + <skipped /> + </testcase> + + <testcase + name="Available fairwaydepth" classname="import"> + <skipped /> + </testcase> + + <testcase + name="Gauge measurement" classname="import"> + <skipped /> + </testcase> + + <testcase + name="Import Fairway Dimensions" classname="import"> + <skipped /> + </testcase> + + <testcase + name="Import Waterway Axis" classname="import"> + <skipped /> + </testcase> + + <testcase + name="Import Waterway Area " classname="import"> + <skipped /> + </testcase> + + + </testsuite> +</testsuites>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/tests/e2e/reports/login.xml Fri Apr 05 10:04:45 2019 +0200 @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<testsuites errors="0" + failures="0" + tests="5"> + + <testsuite name="login" + errors="0" failures="0" hostname="" id="" package="login" skipped="5" + tests="5" time="0.000" timestamp="Thu, 04 Apr 2019 10:16:03 GMT"> + + + + <system-err> + Error while running [Protocols / Loading protocols]: + + TypeError: browser.url(...).waitForElementVisible(...).setValue(...).setValue(...).click(...).pause(...).click(...).pause(...).click(...).pause(...).url.contains is not a function + at Object.Loading protocols (/home/thomas/go/src/gemma.intevation.de/gemma/client/tests/e2e/specs/protocols.js:31:12) + at Module.call (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/module.js:62:34) + at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/testcase.js:70:29 + at _fulfilled (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:834:54) + at self.promiseDispatch.done (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:863:30) + at Promise.promise.promiseDispatch (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:796:13) + at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:556:49 + at runSingle (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:137:13) + at flush (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:125:13) + at _combinedTickCallback (internal/process/next_tick.js:132:7) + </system-err> + + + + + <testcase + name="Page Load" classname="login"> + <skipped /> + </testcase> + + <testcase + name="Login failed" classname="login"> + <skipped /> + </testcase> + + <testcase + name="Login oana success" classname="login"> + <skipped /> + </testcase> + + <testcase + name="Login oana switch url" classname="login"> + <skipped /> + </testcase> + + <testcase + name="Login switch user from oana to sophie" classname="login"> + <skipped /> + </testcase> + + + </testsuite> +</testsuites>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/tests/e2e/reports/protocols.xml Fri Apr 05 10:04:45 2019 +0200 @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<testsuites errors="1" + failures="0" + tests="1"> + + <testsuite name="protocols" + errors="1" failures="0" hostname="" id="" package="protocols" skipped="0" + tests="1" time="0.005000" timestamp="Thu, 04 Apr 2019 10:16:03 GMT"> + + <testcase name="Loading protocols" classname="protocols" time="0.005000" assertions="0"> + </testcase> + + + + <system-err> + Error while running [Protocols / Loading protocols]: + + TypeError: browser.url(...).waitForElementVisible(...).setValue(...).setValue(...).click(...).pause(...).click(...).pause(...).click(...).pause(...).url.contains is not a function + at Object.Loading protocols (/home/thomas/go/src/gemma.intevation.de/gemma/client/tests/e2e/specs/protocols.js:31:12) + at Module.call (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/module.js:62:34) + at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/nightwatch/lib/runner/testcase.js:70:29 + at _fulfilled (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:834:54) + at self.promiseDispatch.done (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:863:30) + at Promise.promise.promiseDispatch (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:796:13) + at /home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:556:49 + at runSingle (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:137:13) + at flush (/home/thomas/go/src/gemma.intevation.de/gemma/client/node_modules/q/q.js:125:13) + at _combinedTickCallback (internal/process/next_tick.js:132:7) + </system-err> + + + + </testsuite> +</testsuites>
--- a/client/tests/e2e/specs/import.js Tue Apr 02 10:07:48 2019 +0200 +++ b/client/tests/e2e/specs/import.js Fri Apr 05 10:04:45 2019 +0200 @@ -16,6 +16,7 @@ // http://nightwatchjs.org/guide#usage module.exports = { + "@disabled": true, "Bottleneck import": browser => { browser .url(process.env.VUE_DEV_SERVER_URL)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/tests/e2e/specs/protocols.js Fri Apr 05 10:04:45 2019 +0200 @@ -0,0 +1,37 @@ +/* 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> + */ + +// For authoring Nightwatch tests, see +// http://nightwatchjs.org/guide#usage + +module.exports = { + "Loading protocols": browser => { + browser + .url(process.env.VUE_DEV_SERVER_URL) + .waitForElementVisible("#app", 5000) + .setValue("input[id='inputUsername']", "sophie") + .setValue("input[id='inputPassword']", "so2Phie4") + .click("button[type='submit']") + .pause(1000) + .click(".menubutton") + .pause(1000) + .click("a[href='#/logs']") + .pause(1000) + .assert.urlContains("#/logs") + .click("#accesslog") + .click("#errorlog") + .assert.urlContains("#/logs") + .end(); + } +};
--- a/docker/Dockerfile.backend Tue Apr 02 10:07:48 2019 +0200 +++ b/docker/Dockerfile.backend Fri Apr 05 10:04:45 2019 +0200 @@ -23,4 +23,4 @@ EXPOSE 8000 -CMD ["/usr/local/bin/gemma", "-c", "/opt/gemma/gemma.toml"] +CMD ["/usr/local/bin/gemma", "-c", "/opt/gemma/example_conf.toml"]
--- a/docker/Dockerfile.db Tue Apr 02 10:07:48 2019 +0200 +++ b/docker/Dockerfile.db Fri Apr 05 10:04:45 2019 +0200 @@ -15,7 +15,6 @@ apt-key add - &&\ apt-get update &&\ apt-get -y install postgresql-11-postgis-2.5 postgresql-11-pgtap -RUN apt-get -y install --no-install-recommends postgis xsltproc jq USER postgres ENV PGBIN /usr/lib/postgresql/11/bin
--- a/pkg/common/time.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/common/time.go Fri Apr 05 10:04:45 2019 +0200 @@ -4,16 +4,22 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // -// Copyright (C) 2018 by via donau +// Copyright (C) 2018, 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> +// * Bernhard E. Reiter <bernhard.reiter@intevation.de> package common +import "time" + const ( - TimeFormat = "2006-01-02T15:04:05" + // time.RFC3339 equals "simplified ISO format as defined by ECMA-262" + // https://tc39.github.io/ecma262/#sec-date-time-string-format + // and "SHOULD be used in new protocols on the Internet." (RFC section 5.6) + TimeFormat = time.RFC3339 DateFormat = "2006-01-02" )
--- a/pkg/config/config.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/config/config.go Fri Apr 05 10:04:45 2019 +0200 @@ -4,7 +4,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // -// Copyright (C) 2018 by via donau +// Copyright (C) 2018, 2019 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // @@ -140,7 +140,6 @@ } // ProxyPrefix is the prefix used in generated URLs by the proxy. -// It defauls to http://${WebHost}:${WebPort}". // You may need to set this if you run gemma behind a proxy // on a specific domain. func ProxyPrefix() string { @@ -156,7 +155,6 @@ } // ExternalURL is the URL to find this server from the outside. -// It defauls to http://${WebHost}:${WebPort}". func ExternalURL() string { fetchExternal := func() { if externalURL == "" { @@ -183,7 +181,7 @@ return sessionTimeout } -// The root directories where to find schema files. +// SchemaDirs are the root directories where to find schema files. func SchemaDirs() string { return viper.GetString("schema-dirs") } // RootCmd is cobra command to be bound th the cobra/viper infrastructure. @@ -260,10 +258,10 @@ str("proxy-key", "", "signing key for proxy URLs.\n"+ "Defaults to random key.") str("proxy-prefix", "", "URL prefix of proxy.\n"+ - "Defaults to 'http://${web-host}:${web-port}'") + "Defaults to 'http://${host}:${port}'") str("external-url", "", "URL to find the server from the outside.\n"+ - "Defaults to 'http://${web-host}:${web-port}'") + "Defaults to 'http://${host}:${port}'") str("tmp-dir", "", "Temp directory of gemma server.\n"+ "Defaults to system temp directory.")
--- a/pkg/controllers/gauges.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/controllers/gauges.go Fri Apr 05 10:04:45 2019 +0200 @@ -42,7 +42,7 @@ water_level FROM waterway.gauge_measurements WHERE - a.fk_gauge_id = ( + fk_gauge_id = ( $1::char(2), $2::char(3), $3::char(5),
--- a/pkg/controllers/user.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/controllers/user.go Fri Apr 05 10:04:45 2019 +0200 @@ -403,10 +403,10 @@ var bodyTmpl *template.Template if userData.Role == "sys_admin" { - subject = "Sysadmin Notification TEST" + subject = "Gemma: Sysadmin Notification TEST" bodyTmpl = testSysadminNotifyMailTmpl } else if userData.Role == "waterway_admin" { - subject = "Waterway Admin Notification TEST" + subject = "Gemma: Waterway Admin Notification TEST" bodyTmpl = testWWAdminNotifyMailTmpl } else { err = JSONError{
--- a/pkg/imports/dma.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/imports/dma.go Fri Apr 05 10:04:45 2019 +0200 @@ -77,36 +77,28 @@ const ( deleteDistanceMarksSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) DELETE FROM waterway.distance_marks -WHERE ST_Covers( - (SELECT a FROM resp), - ST_Transform(geom::geometry, (SELECT t FROM resp))) +WHERE pg_has_role('sys_admin', 'MEMBER') + OR ST_Covers((SELECT a FROM resp), + ST_Transform(geom::geometry, (SELECT ST_SRID(a) FROM resp))) ` insertDistanceMarksSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) INSERT INTO waterway.distance_marks (geom, catdis) -SELECT ST_Transform(clipped.geom, 4326)::geography, $3 FROM ( - SELECT (ST_Dump( - ST_Intersection( - (SELECT a FROM resp), - ST_Transform( - ST_GeomFromWKB($1, $2::integer), - (SELECT t FROM resp) - ) - ) - )).geom AS geom - ) AS clipped - WHERE clipped.geom IS NOT NULL +SELECT ST_Transform(new_dma, 4326), $3 + FROM (SELECT + CASE WHEN pg_has_role('sys_admin', 'MEMBER') + THEN dma + ELSE ST_Intersection((SELECT a FROM resp), + ST_Transform(dma, (SELECT ST_SRID(a) FROM resp))) + END AS new_dma + FROM ST_GeomFromWKB($1, $2::integer) AS dma (dma)) AS new_dma + WHERE NOT ST_IsEmpty(new_dma) +RETURNING id ` ) @@ -169,6 +161,7 @@ unsupported = stringCounter{} missingProperties int badProperties int + outside int features int ) @@ -211,13 +204,19 @@ if err := json.Unmarshal(*feature.Geometry.Coordinates, &p); err != nil { return err } - if _, err := insertStmt.ExecContext( + var dmaid int64 + err := insertStmt.QueryRowContext( ctx, p.asWKB(), epsg, props.HydroCatdis, - ); err != nil { - feedback.Error("error: %s", err) + ).Scan(&dmaid) + switch { + case err == sql.ErrNoRows: + outside++ + // ignore -> filtered by responsibility area + continue + case err != nil: return err } features++ @@ -242,6 +241,10 @@ feedback.Warn("Unsupported types found: %s", unsupported) } + if outside > 0 { + feedback.Info("Features outside responsibility area: %d", outside) + } + if features == 0 { err := errors.New("No features found") feedback.Error("%v", err)
--- a/pkg/imports/email.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/imports/email.go Fri Apr 05 10:04:45 2019 +0200 @@ -28,7 +28,7 @@ const ( selectEmailSQL = `SELECT email_address FROM users.list_users WHERE username = $1` - importNotificationMailSubject = `import notification mail` + importNotificationMailSubject = `Gemma: import notification mail` ) var ( @@ -38,11 +38,11 @@ a {{ .Description }} import on server {{ .Server }} triggered this email notification. -{{ if eq .State "accepted" }}The imported data were successfully integrated into the database.{{ end -}} -{{ if eq .State "unchanged" }}The import has not changed any data in the database.{{ end -}} -{{ if eq .State "failed" }}The import failed for some reasons.{{ end -}} +{{ if eq .State "accepted" }}The imported data were successfully integrated into the database. {{ end -}} +{{ if eq .State "unchanged" }}The import has not changed any data in the database. {{ end -}} +{{ if eq .State "failed" }}The import failed for some reasons. {{ end -}} {{ if eq .State "pending" }}The imported data could be integrated into the database -but your final decision is needed.{{ end -}} +but your final decision is needed. {{ end -}} Please follow this link to have a closer look at the details:
--- a/pkg/imports/fd.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/imports/fd.go Fri Apr 05 10:04:45 2019 +0200 @@ -139,15 +139,12 @@ deleteFairwayDimensionSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) DELETE FROM waterway.fairway_dimensions -WHERE ST_Covers( - (SELECT a FROM resp), - ST_Transform(area::geometry, (SELECT t FROM resp))) +WHERE (pg_has_role('sys_admin', 'MEMBER') + OR ST_Covers((SELECT a FROM resp), + ST_Transform(area::geometry, (SELECT ST_SRID(a) FROM resp)))) AND staging_done AND level_of_service = $1::smallint` @@ -155,24 +152,27 @@ // avoid errors due to reprojection. insertFairwayDimensionSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) -INSERT INTO waterway.fairway_dimensions (area, level_of_service, min_width, max_width, min_depth, date_info, source_organization) -SELECT ST_Transform(clipped.geom, 4326)::geography, $3, $4, $5, $6, $7, $8 FROM ( - SELECT (ST_Dump( - ST_Intersection( - (SELECT ST_Buffer(a, -0.0001) FROM resp), - ST_MakeValid(ST_Transform( - ST_GeomFromWKB($1, $2::integer), - (SELECT t FROM resp) - )) - ) - )).geom AS geom - ) AS clipped - WHERE clipped.geom IS NOT NULL +INSERT INTO waterway.fairway_dimensions ( + area, + level_of_service, + min_width, + max_width, + min_depth, + date_info, + source_organization) +SELECT ST_Transform(dmp.geom, 4326), $3, $4, $5, $6, $7, $8 + FROM ST_GeomFromWKB($1, $2::integer) AS new_fd (new_fd), + ST_Dump( + CASE WHEN pg_has_role('sys_admin', 'MEMBER') + THEN ST_MakeValid(ST_Transform( + new_fd, best_utm(ST_Transform(new_fd, 4326)))) + ELSE ST_CollectionExtract(ST_Intersection( + (SELECT ST_Buffer(a, -0.0001) FROM resp), + ST_MakeValid(ST_Transform(new_fd, (SELECT ST_SRID(a) FROM resp)))), + 3) + END) AS dmp RETURNING id, ST_X(ST_Centroid(area::geometry)), ST_Y(ST_Centroid(area::geometry)) @@ -259,6 +259,8 @@ feedback.Info("Using EPSG: %d", epsg) + savepoint := Savepoint(ctx, tx, "feature") + features: for _, feature := range rfc.Features { if feature.Geometry.Coordinates == nil { @@ -287,24 +289,28 @@ } var fdid int64 var lat, lon float64 - err = insertStmt.QueryRowContext( - ctx, - p.asWKB(), - epsg, - fd.LOS, - fd.MinWidth, - fd.MaxWidth, - fd.Depth, - dateInfo, - fd.SourceOrganization, - ).Scan(&fdid, &lat, &lon) + err = savepoint(func() error { + err := insertStmt.QueryRowContext( + ctx, + p.asWKB(), + epsg, + fd.LOS, + fd.MinWidth, + fd.MaxWidth, + fd.Depth, + dateInfo, + fd.SourceOrganization, + ).Scan(&fdid, &lat, &lon) + return err + }) switch { case err == sql.ErrNoRows: outside++ // ignore -> filtered by responsibility_areas continue features case err != nil: - return err + feedback.Warn(handleError(err).Error()) + continue features } // Store for potential later removal. if err = track(ctx, tx, importID, "waterway.fairway_dimensions", fdid); err != nil {
--- a/pkg/imports/st.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/imports/st.go Fri Apr 05 10:04:45 2019 +0200 @@ -97,8 +97,9 @@ $8::char(5), $9::char(5), $10::int)::isrs) - ) AS r -) + ) AS r), +axs AS ( + SELECT ISRSrange_axis((SELECT r FROM r), $16::double precision) AS axs) INSERT INTO waterway.stretches ( name, stretch, @@ -110,10 +111,10 @@ ) VALUES ( $11, (SELECT r FROM r), - ISRSrange_area( - ISRSrange_axis((SELECT r FROM r), $16::double precision), - (SELECT ST_Collect(CAST(area AS geometry)) - FROM waterway.waterway_area)), + ST_Transform(ISRSrange_area( + (SELECT axs FROM axs), + (SELECT ST_Buffer(axs, 10000) FROM axs)), + 4326), $12, $13, $14,
--- a/pkg/imports/wa.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/imports/wa.go Fri Apr 05 10:04:45 2019 +0200 @@ -81,22 +81,16 @@ const ( deleteWaterwayAreaSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) DELETE FROM waterway.waterway_area WHERE pg_has_role('sys_admin', 'MEMBER') OR ST_Covers((SELECT a FROM resp), - ST_Transform(area::geometry, (SELECT t FROM resp))) + ST_Transform(area::geometry, (SELECT ST_SRID(a) FROM resp))) ` insertWaterwayAreaSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) INSERT INTO waterway.waterway_area (area, catccl, dirimp) SELECT dmp.geom, $3, $4 @@ -106,9 +100,10 @@ THEN ST_MakeValid(ST_Transform(new_area, best_utm(ST_Transform(new_area, 4326)))) ELSE ST_Intersection((SELECT a FROM resp), - ST_MakeValid(ST_Transform(new_area, (SELECT t FROM resp)))) + ST_MakeValid(ST_Transform(new_area, (SELECT ST_SRID(a) FROM resp)))) END, 3), 4326)) AS dmp +RETURNING id ` ) @@ -175,6 +170,7 @@ unsupported = stringCounter{} missingProperties int badProperties int + outside int features int ) @@ -233,18 +229,24 @@ if err := json.Unmarshal(*feature.Geometry.Coordinates, &p); err != nil { return err } - if err := savepoint(func() error { - _, err := insertStmt.ExecContext( + var waid int64 + err := savepoint(func() error { + err := insertStmt.QueryRowContext( ctx, p.asWKB(), epsg, catccl, dirimp, - ) + ).Scan(&waid) return err - }); err != nil { + }) + switch { + case err == sql.ErrNoRows: + outside++ + // ignore -> filtered by responsibility_areas + case err != nil: feedback.Warn(handleError(err).Error()) - } else { + default: features++ } default: @@ -268,6 +270,10 @@ feedback.Warn("Unsupported types found: %s", unsupported) } + if outside > 0 { + feedback.Info("Features outside responsibility area: %d", outside) + } + if features == 0 { err := errors.New("No features found") feedback.Error("%v", err)
--- a/pkg/imports/wx.go Tue Apr 02 10:07:48 2019 +0200 +++ b/pkg/imports/wx.go Fri Apr 05 10:04:45 2019 +0200 @@ -80,15 +80,12 @@ const ( deleteWaterwayAxisSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) DELETE FROM waterway.waterway_axis WHERE pg_has_role('sys_admin', 'MEMBER') OR ST_Covers((SELECT a FROM resp), - ST_Transform(wtwaxs::geometry, (SELECT t FROM resp))) + ST_Transform(wtwaxs::geometry, (SELECT ST_SRID(a) FROM resp))) ` checkCrossingAxisSQL = ` @@ -100,10 +97,7 @@ insertWaterwayAxisSQL = ` WITH resp AS ( - SELECT best_utm(area) AS t, - ST_Transform(area::geometry, best_utm(area)) AS a - FROM users.responsibility_areas - WHERE country = users.current_user_country() + SELECT users.current_user_area_utm() AS a ) INSERT INTO waterway.waterway_axis (wtwaxs, objnam, nobjnam) SELECT dmp.geom, $3, $4 @@ -113,7 +107,7 @@ THEN ST_Node(ST_Transform(new_line, best_utm(ST_Transform(new_line, 4326)))) ELSE ST_Intersection((SELECT a FROM resp), - ST_Node(ST_Transform(new_line, (SELECT t FROM resp)))) + ST_Node(ST_Transform(new_line, (SELECT ST_SRID(a) FROM resp)))) END, 2), 4326)) AS dmp RETURNING id @@ -189,6 +183,7 @@ unsupported = stringCounter{} missingProperties int badProperties int + outside int features int ) @@ -247,11 +242,12 @@ epsg, props, nobjnam, + &outside, + &features, checkCrossingStmt, insertStmt); err != nil { return err } - features++ case "MultiLineString": var ls []lineSlice if err := json.Unmarshal(*feature.Geometry.Coordinates, &ls); err != nil { @@ -266,11 +262,12 @@ epsg, props, nobjnam, + &outside, + &features, checkCrossingStmt, insertStmt); err != nil { return err } - features++ } default: unsupported[feature.Geometry.Type]++ @@ -293,10 +290,12 @@ feedback.Warn("Unsupported types found: %s", unsupported) } + if outside > 0 { + feedback.Info("Features outside responsibility area: %d", outside) + } + if features == 0 { - err := errors.New("No features found") - feedback.Error("%v", err) - return nil, err + return nil, errors.New("No features found") } if err = tx.Commit(); err == nil { @@ -315,10 +314,11 @@ epsg int, props waterwayAxisProperties, nobjnam sql.NullString, + outside, features *int, checkCrossingStmt, insertStmt *sql.Stmt, ) error { var id int - if err := savepoint(func() error { + err := savepoint(func() error { err := insertStmt.QueryRowContext( ctx, l.asWKB(), @@ -327,22 +327,29 @@ nobjnam, ).Scan(&id) return err - }); err != nil { + }) + switch { + case err == sql.ErrNoRows: + *outside++ + // ignore -> filtered by responsibility_areas + return nil + case err != nil: feedback.Warn(handleError(err).Error()) - } - - var crossing string - switch err := checkCrossingStmt.QueryRowContext( - ctx, - id, - ).Scan(&crossing); { - case err != nil && err != sql.ErrNoRows: - return err - case err == nil: - feedback.Warn( - "Linestring %d crosses previously imported linestring near %s. "+ - "Finding a contiguous axis may not work here", - id, crossing) + default: + *features++ + var crossing string + switch err := checkCrossingStmt.QueryRowContext( + ctx, + id, + ).Scan(&crossing); { + case err != nil && err != sql.ErrNoRows: + return err + case err == nil: + feedback.Warn( + "Linestring %d crosses previously imported linestring near %s. "+ + "Finding a contiguous axis may not work here", + id, crossing) + } } return nil }
--- a/schema/auth.sql Tue Apr 02 10:07:48 2019 +0200 +++ b/schema/auth.sql Fri Apr 05 10:04:45 2019 +0200 @@ -4,7 +4,7 @@ -- SPDX-License-Identifier: AGPL-3.0-or-later -- License-Filename: LICENSES/AGPL-3.0.txt --- Copyright (C) 2018 by via donau +-- Copyright (C) 2018, 2019 by via donau -- – Österreichische Wasserstraßen-Gesellschaft mbH -- Software engineering by Intevation GmbH @@ -102,6 +102,8 @@ LOOP EXECUTE format('CREATE POLICY hide_staging ON waterway.%I ' 'FOR SELECT TO waterway_user USING (staging_done)', the_table); + EXECUTE format('CREATE POLICY sys_admin ON waterway.%I ' + 'FOR ALL TO sys_admin USING (true)', the_table); EXECUTE format('ALTER TABLE waterway.%I ENABLE ROW LEVEL SECURITY', the_table); END LOOP; @@ -139,19 +141,15 @@ CREATE POLICY responsibility_area ON waterway.bottlenecks FOR ALL TO waterway_admin - USING (utm_covers(area)); + USING (users.utm_covers(area)); CREATE POLICY responsibility_area ON waterway.sounding_results FOR ALL TO waterway_admin - USING (utm_covers(area)); + USING (users.utm_covers(area)); CREATE POLICY responsibility_area ON waterway.fairway_dimensions FOR ALL TO waterway_admin - USING (utm_covers(area)); - -CREATE POLICY sys_admin ON waterway.stretches - FOR ALL TO sys_admin - USING (true); + USING (users.utm_covers(area)); -- -- RLS policies for imports and import config
--- a/schema/geo_functions.sql Tue Apr 02 10:07:48 2019 +0200 +++ b/schema/geo_functions.sql Fri Apr 05 10:04:45 2019 +0200 @@ -33,20 +33,3 @@ $$ LANGUAGE plpgsql IMMUTABLE; - -CREATE OR REPLACE FUNCTION utm_covers(g geography) RETURNS boolean AS -$$ -DECLARE - user_area geometry; - utm integer; -BEGIN - SELECT area::geometry FROM users.responsibility_areas INTO user_area - WHERE country = users.current_user_country(); - SELECT best_utm(user_area) INTO utm; - RETURN ST_Covers( - ST_Transform(user_area, utm), - ST_Transform(g::geometry, utm)); -END; -$$ -LANGUAGE plpgsql -STABLE;
--- a/schema/manage_users.sql Tue Apr 02 10:07:48 2019 +0200 +++ b/schema/manage_users.sql Fri Apr 05 10:04:45 2019 +0200 @@ -4,7 +4,7 @@ -- SPDX-License-Identifier: AGPL-3.0-or-later -- License-Filename: LICENSES/AGPL-3.0.txt --- Copyright (C) 2018 by via donau +-- Copyright (C) 2018, 2019 by via donau -- – Österreichische Wasserstraßen-Gesellschaft mbH -- Software engineering by Intevation GmbH @@ -68,6 +68,31 @@ STABLE PARALLEL SAFE; +CREATE OR REPLACE FUNCTION users.current_user_area_utm() + RETURNS geometry + AS $$ + DECLARE utm_area geometry; + BEGIN + SELECT ST_Transform(area::geometry, best_utm(area)) + INTO STRICT utm_area + FROM users.responsibility_areas + WHERE country = users.current_user_country(); + RETURN utm_area; + END; + $$ + LANGUAGE plpgsql + STABLE PARALLEL SAFE; + + +CREATE OR REPLACE FUNCTION users.utm_covers(g geography) RETURNS boolean AS + $$ + SELECT ST_Covers(a, ST_Transform(g::geometry, ST_SRID(a))) + FROM users.current_user_area_utm() AS a (a) + $$ + LANGUAGE SQL + STABLE PARALLEL SAFE; + + CREATE OR REPLACE FUNCTION internal.create_user() RETURNS trigger AS $$ BEGIN
--- a/schema/manage_users_tests.sql Tue Apr 02 10:07:48 2019 +0200 +++ b/schema/manage_users_tests.sql Fri Apr 05 10:04:45 2019 +0200 @@ -4,7 +4,7 @@ -- SPDX-License-Identifier: AGPL-3.0-or-later -- License-Filename: LICENSES/AGPL-3.0.txt --- Copyright (C) 2018 by via donau +-- Copyright (C) 2018, 2019 by via donau -- – Österreichische Wasserstraßen-Gesellschaft mbH -- Software engineering by Intevation GmbH @@ -17,10 +17,23 @@ SET search_path TO public, gemma, gemma_waterway, gemma_fairway; +SET SESSION AUTHORIZATION test_user_at; +-- +-- Utility functions +-- +SELECT results_eq($$ + SELECT ST_SRID(users.current_user_area_utm()) + $$, + $$ + SELECT best_utm(area) + FROM users.responsibility_areas + WHERE country = users.current_user_country() + $$, + 'Geometry has SRID corresponding to best_utm()'); + -- -- Role listing -- -SET SESSION AUTHORIZATION test_user_at; SELECT results_eq($$ SELECT username FROM users.list_users $$,
--- a/schema/run_tests.sh Tue Apr 02 10:07:48 2019 +0200 +++ b/schema/run_tests.sh Fri Apr 05 10:04:45 2019 +0200 @@ -28,7 +28,7 @@ -c 'SET client_min_messages TO WARNING' \ -c "DROP ROLE IF EXISTS $TEST_ROLES" \ -f tap_tests_data.sql \ - -c 'SELECT plan(63)' \ + -c 'SELECT plan(64)' \ -f gemma_tests.sql \ -f isrs_tests.sql \ -f auth_tests.sql \