Mercurial > gemma
changeset 586:2821e087a973
remove files
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Fri, 07 Sep 2018 11:36:48 +0200 |
parents | ef307bd6b5d8 |
children | 53b7a46c53a7 |
files | client/src/assets/application.scss client/src/assets/logo.png client/src/assets/tooltip.scss client/src/assets/user.png client/src/components/Fairwayprofile.vue client/src/components/Layers.vue client/src/components/Layerselect.vue client/src/components/Maplayer.vue client/src/components/Passwordfield.vue client/src/components/Sidebar.vue client/src/components/Topbar.vue client/src/components/User.vue client/src/components/Userdetail.vue client/src/lib/errors.js client/src/lib/http.js client/src/lib/session.js client/src/stores/application.js client/src/stores/mapstore.js client/src/stores/user.js client/src/stores/usermanagement.js client/src/views/Login.vue client/src/views/Main.vue client/src/views/Usermanagement.vue client/src/views/Users.vue |
diffstat | 24 files changed, 0 insertions(+), 2118 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/assets/application.scss Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -$offset: 20px; -$small-offset: 10px; -$large-offset: 30px; -$x-large-offset: 50px; -$iconsize: 3em; -$iconLineHeight: 0.25em; -$iconwidth: 20px; -$basic-shadow: 1px 3px 8px 2px rgba(220, 220, 220, 0.75); -$basic-shadow-light: 1px 1px 12px 1px rgba(235, 235, 235, 0.75); -$transition: 0.5s; -$transition-fast: 0.1s; -$transition-slow: 3s; -$topbarheight: 5vh; - -%fully-centered { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.ui-element { - pointer-events: auto; -}
--- a/client/src/assets/tooltip.scss Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -.tooltip { - display: block !important; - z-index: 10000; - - .tooltip-inner { - background: black; - color: white; - border-radius: 16px; - padding: 5px 10px 4px; - } - - .tooltip-arrow { - width: 0; - height: 0; - border-style: solid; - position: absolute; - margin: 5px; - border-color: black; - z-index: 1; - } - - &[x-placement^="top"] { - margin-bottom: 5px; - - .tooltip-arrow { - border-width: 5px 5px 0 5px; - border-left-color: transparent !important; - border-right-color: transparent !important; - border-bottom-color: transparent !important; - bottom: -5px; - left: calc(50% - 5px); - margin-top: 0; - margin-bottom: 0; - } - } - - &[x-placement^="bottom"] { - margin-top: 5px; - - .tooltip-arrow { - border-width: 0 5px 5px 5px; - border-left-color: transparent !important; - border-right-color: transparent !important; - border-top-color: transparent !important; - top: -5px; - left: calc(50% - 5px); - margin-top: 0; - margin-bottom: 0; - } - } - - &[x-placement^="right"] { - margin-left: 5px; - - .tooltip-arrow { - border-width: 5px 5px 5px 0; - border-left-color: transparent !important; - border-top-color: transparent !important; - border-bottom-color: transparent !important; - left: -5px; - top: calc(50% - 5px); - margin-left: 0; - margin-right: 0; - } - } - - &[x-placement^="left"] { - margin-right: 5px; - - .tooltip-arrow { - border-width: 5px 0 5px 5px; - border-top-color: transparent !important; - border-right-color: transparent !important; - border-bottom-color: transparent !important; - right: -5px; - top: calc(50% - 5px); - margin-left: 0; - margin-right: 0; - } - } - - &.popover { - $color: #f9f9f9; - - .popover-inner { - background: $color; - color: black; - padding: 24px; - border-radius: 5px; - box-shadow: 0 5px 30px rgba(black, 0.1); - } - - .popover-arrow { - border-color: $color; - } - } - - &[aria-hidden="true"] { - visibility: hidden; - opacity: 0; - transition: opacity 0.15s, visibility 0.15s; - } - - &[aria-hidden="false"] { - visibility: visible; - opacity: 1; - transition: opacity 0.15s; - } -}
--- a/client/src/components/Fairwayprofile.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -<template> - <div class="fairwayprofile"> - <svg :width="width +'px'" :height="height +'px'"> - </svg> - </div> -</template> - -<style lang="scss"> -.fairwayprofile { - background-color: white; - margin-left: auto; - margin-right: auto; - margin-top: auto; - margin-bottom: auto; -} -</style> - -<script> -import * as d3 from "d3"; - -const WATER_COLOR = "#005DFF"; -const GROUND_COLOR = "#4A2F06"; - -const sampleData = [ - { x: 0, y: -3.0 }, - { x: 25, y: -2.0 }, - { x: 50, y: -4.5 }, - { x: 75, y: -4.0 }, - { x: 100, y: -3.0 }, - { x: 125, y: -4.0 }, - { x: 150, y: -5.0 }, - { x: 175, y: -4.0 }, - { x: 200, y: -3.0 }, - { x: 225, y: -3.5 }, - { x: 250, y: -3.0 }, - { x: 300, y: -2.5 } -]; - -export default { - name: "fairwayprofile", - props: ["width", "height", "xScale", "yScaleLeft", "yScaleRight", "margin"], - data() { - return {}; - }, - methods: { - generateCoordinates(svg, height, width) { - let xScale = d3 - .scaleLinear() - .domain(this.xScale) - .rangeRound([0, width]); - - xScale.ticks(5); - let yScaleLeft = d3 - .scaleLinear() - .domain(this.yScaleLeft) - .rangeRound([height, 0]); - - let yScaleRight = d3 - .scaleLinear() - .domain(this.yScaleRight) - .rangeRound([height, 0]); - - let xAxis = d3.axisBottom(xScale); - let yAxis = d3.axisLeft(yScaleLeft); - let yAxis2 = d3.axisRight(yScaleRight); - let graph = svg - .append("g") - .attr( - "transform", - "translate(" + this.margin.left + "," + this.margin.top + ")" - ); - graph - .append("g") - .attr("transform", "translate(0," + height + ")") - .call(xAxis.ticks(5)); - graph.append("g").call(yAxis); - graph - .append("g") - .attr("transform", "translate(" + width + ",0)") - .call(yAxis2); - return { xScale, yScaleLeft, yScaleRight, graph }; - }, - drawWaterlevel({ graph, xScale, yScaleRight, height }) { - let waterArea = d3 - .area() - .x(function(d) { - return xScale(d.x); - }) - .y0(height) - .y1(function(d) { - return yScaleRight(d.y); - }); - graph - .append("path") - .datum([{ x: 0, y: 0 }, { x: 300, y: 0 }]) - .attr("fill", WATER_COLOR) - .attr("stroke", WATER_COLOR) - .attr("d", waterArea); - }, - drawProfile({ graph, xScale, yScaleRight, sampleData, height }) { - let profileLine = d3 - .line() - .x(d => { - return xScale(d.x); - }) - .y(d => { - return yScaleRight(d.y); - }); - let profileArea = d3 - .area() - .x(function(d) { - return xScale(d.x); - }) - .y0(height) - .y1(function(d) { - return yScaleRight(d.y); - }); - graph - .append("path") - .datum(sampleData) - .attr("fill", GROUND_COLOR) - .attr("stroke", GROUND_COLOR) - .attr("stroke-width", 3) - .attr("d", profileArea); - graph - .append("path") - .datum(sampleData) - .attr("fill", "none") - .attr("stroke", "black") - .attr("stroke-linejoin", "round") - .attr("stroke-linecap", "round") - .attr("stroke-width", 3) - .attr("d", profileLine); - } - }, - mounted() { - let svg = d3.select("svg"); - const width = this.width - this.margin.right - this.margin.left; - const height = this.height - this.margin.top - this.margin.bottom; - - const { xScale, yScaleRight, graph } = this.generateCoordinates( - svg, - height, - width - ); - this.drawWaterlevel({ - graph, - xScale, - yScaleRight, - height, - width - }); - this.drawProfile({ - graph, - xScale, - yScaleRight, - sampleData, - height, - width - }); - } -}; -</script>
--- a/client/src/components/Layers.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -<template> - <div class="ui-element card layerselection shadow"> - <div class="card-body"> - <div class="headline"> - <h4 class="card-title">Layers</h4> - </div> - <hr> - <div class="d-flex flex-column"> - <Layerselect :layerindex="index" :layername="layer.name" v-for="(layer, index) in layers" :key="layer.name" :isVisible="layer.isVisible" @visibilityToggled="visibilityToggled"></Layerselect> - </div> - </div> - </div> -</template> - -<style lang="scss"> -@import "../assets/application.scss"; - -.layerselection { - background-color: white; - margin-left: 0.5rem; -} -</style> - -<script> -import Layerselect from "./Layerselect"; -import { mapGetters } from "vuex"; -export default { - name: "layers", - components: { - Layerselect - }, - computed: { - ...mapGetters("mapstore", ["layers"]) - }, - methods: { - visibilityToggled(layer) { - this.$store.commit("mapstore/toggleVisibility", layer); - } - } -}; -</script>
--- a/client/src/components/Layerselect.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -<template> - <div class="form-check d-flex flex-row"> - <input class="form-check-input" @change="visibilityToggled" :id="layername" type="checkbox" :checked="isVisible"> - <label class="form-check-label" :for="layername">{{this.layername}}</label> - </div> -</template> - -<style lang="sass"> - -</style> - - -<script> -export default { - props: ["layername", "layerindex", "isVisible"], - name: "layerselect", - methods: { - visibilityToggled() { - this.$emit("visibilityToggled", this.layerindex); - } - } -}; -</script>
--- a/client/src/components/Maplayer.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -<template> - <div class="mapdisplay"> - <div id="map"></div> - <!-- <div class="profile d-flex flex-row"> - <Fairwayprofile height="300" width="1024" :xScale="[0, 300]" :yScaleLeft="[191, 199]" :yScaleRight="[-6, 1]" :margin="{ top: 20, right: 40, bottom: 20, left: 40 }"></Fairwayprofile> - </div> --> - </div> -</template> - -<style lang="scss"> -@import "../assets/application.scss"; - -.profile { - background-color: white; - height: 50vh-$topbarheight; -} - -.mapdisplay { - height: 100vh; -} - -#map { - height: 100vh; -} - -.ol-zoom { - display: flex; - left: 15vw; - margin-top: 2vh; - z-index: 5; - background-color: white; -} -</style> - -<script> -import { HTTP } from "../lib/http"; -import "ol/ol.css"; -import { Map, View } from "ol"; -// needed for vector filter example -// import { greaterThan as greaterThanFilter } from "ol/format/filter.js"; -import { WFS, GeoJSON } from "ol/format.js"; -import { mapGetters } from "vuex"; -import Fairwayprofile from "./Fairwayprofile"; - -export default { - name: "maplayer", - props: ["lat", "long", "zoom"], - components: { - Fairwayprofile - }, - data() { - return { - projection: "EPSG:3857", - openLayersMap: null - }; - }, - computed: { - ...mapGetters("mapstore", ["layers"]), - layerData() { - return this.layers.map(x => { - return x.data; - }); - } - }, - methods: {}, - mounted() { - var that = this; - this.openLayersMap = new Map({ - layers: this.layerData, - target: "map", - view: new View({ - center: [this.long, this.lat], - zoom: this.zoom, - projection: this.projection - }) - }); - - var featureRequest = new WFS().writeGetFeature({ - // srsName: "urn:ogc:def:crs:EPSG::4326", - srsName: "EPSG:3857", - featureNS: "gemma", - featurePrefix: "gemma", - featureTypes: ["fairway_dimensions"], - outputFormat: "application/json" - // example for a filter - //filter: greaterThanFilter("level_of_service", 0) - }); - - HTTP.post( - "/internal/wfs", - new XMLSerializer().serializeToString(featureRequest), - { - headers: { - "X-Gemma-Auth": localStorage.getItem("token"), - "Content-type": "text/xml; charset=UTF-8" - } - } - ).then(function(response) { - var features = new GeoJSON().readFeatures(JSON.stringify(response.data)); - var vectorSrc = that.layers[2].data.getSource(); - vectorSrc.addFeatures(features); - // would scale to the extend of all resulting features - // that.openLayersMap.getView().fit(vectorSrc.getExtent()); - }); - } -}; -</script>
--- a/client/src/components/Passwordfield.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -<template> - <div> - <label for="password">{{this.label}}</label> - <div class="d-flex d-row"> - <input :type="isPasswordVisible" @change="fieldChanged" class="form-control" :placeholder='placeholder' :required="required"> - <span class="input-group-text" @click="showPassword"><i :class="eyeIcon"></i></span> - </div> - <div v-show="passworderrors" class="text-danger"><small><i class="fa fa-warning"></i> {{ this.passworderrors}}</small></div> - </div> -</template> - -<script> -export default { - name: "passwordfield", - props: ["model", "placeholder", "label", "passworderrors", "required"], - data() { - return { - password: "", - readablePassword: false - }; - }, - methods: { - showPassword() { - this.readablePassword = !this.readablePassword; - }, - fieldChanged(e) { - this.$emit("fieldchange", e.target.value); - } - }, - computed: { - isPasswordVisible() { - return this.readablePassword ? "text" : "password"; - }, - eyeIcon() { - return { - fa: true, - "fa-eye": !this.readablePassword, - "fa-eye-slash": this.readablePassword - }; - } - } -}; -</script>
--- a/client/src/components/Sidebar.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -<template> - <div :class="sidebarStyle"> - <div :class="menuStyle"> - <router-link to="/" class="text-body d-flex flex-row nav-link"> - <i class="fa fa-map-o align-self-center navicon"></i>Riverbed Morphology</router-link> - <div v-if="isSysAdmin"> - <hr/> - <div class="nav-link d-flex menupadding text-muted">Administration</div> - <router-link class="text-body d-flex flex-row nav-link" to="usermanagement"> - <i class="fa fa-address-card-o align-self-center navicon"></i>Users - </router-link> - </div> - </div> - <User></User> - </div> -</template> - -<script> -import { mapGetters } from "vuex"; -import User from "../components/User"; - -export default { - name: "sidebar", - components: { - User: User - }, - computed: { - ...mapGetters("user", ["isSysAdmin"]), - ...mapGetters("application", ["sidebarCollapsed"]), - menuStyle() { - return { - menu: true, - nav: true, - "flex-column": true - }; - }, - sidebarStyle() { - return { - "ui-element": true, - sidebar: true, - overlay: true, - sidebarcollapsed: this.sidebarCollapsed, - sidebarextended: !this.sidebarCollapsed, - shadow: true - }; - } - } -}; -</script> - -<style lang="scss"> -@import "../assets/application.scss"; - -.router-link-exact-active { - background-color: #f2f2f2; -} - -.navicon { - margin-right: $small-offset; -} - -.menu { - padding-top: 5vh; - height: 90%; -} - -.sidebar { - top: 0; - background-color: #ffffff; - padding-top: $large-offset; - height: 100vh; - opacity: 0.96; -} - -.overlay { - position: absolute; - z-index: -1; -} - -.sidebarcollapsed { - transition: $transition; - left: -250px; -} - -.sidebarextended { - transition: $transition; - left: 0; -} -</style>
--- a/client/src/components/Topbar.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -<template> - <div class="topbar d-flex flex-row justify-content-between"> - <div> - <i @click="toggleSidebar" class="ui-element menubutton fa fa-bars"></i> - </div> - <div v-if="routeName != 'usermanagement'" class="input-group searchcontainer"> - <div class="input-group-prepend"> - <span class="input-group-text searchlabel" for="search"> - <i class="fa fa-search"></i> - </span> - </div> - <input id="search" type="text" class="form-control ui-element search searchbar"> - </div> - <Layers v-if="routeName != 'usermanagement'"></Layers> - </div> -</template> - -<style lang="scss"> -@import "../assets/application.scss"; - -.menubutton { - background-color: white; - padding: 0.5rem; -} - -.searchcontainer { - margin-left: 20vw; - margin-right: auto; - width: 50vw !important; - height: 39px; - border-radius: 0.25rem; -} - -.searchbar { - margin-left: auto; - margin-right: auto; - height: 50px; -} - -.topbar { - padding-top: 2vh; - margin-right: 1vw; - margin-left: 0; -} - -.logout { - font-size: x-large; -} -</style> - - -<script> -import Layers from "./Layers"; -export default { - name: "topbar", - components: { - Layers: Layers - }, - methods: { - toggleSidebar() { - this.$store.commit("application/toggleSidebar"); - } - }, - computed: { - routeName() { - const routeName = this.$route.name; - console.log(routeName); - return routeName; - } - } -}; -</script>
--- a/client/src/components/User.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -<template> - <div class="ui-element d-flex justify-content-around usermanagement"> - <img class="userpic" src="../assets/user.png"> - <span class="username align-self-center">{{ userinfo }}</span> - <span class="logout align-self-center" @click="logoff"> - <i class="fa fa-power-off"></i> - </span> - </div> -</template> - -<style lang="scss"> -.usermanagement { - background: white; - width: 150px; - padding: 0.25rem; - border-radius: 0.25rem; -} -</style> - -<script> -import { mapGetters } from "vuex"; -export default { - name: "user", - data() { - return {}; - }, - methods: { - logoff() { - this.$store.commit("user/clear_auth"); - this.$store.commit("application/resetSidebar"); - this.$router.push("/login"); - } - }, - computed: { - ...mapGetters("user", ["userinfo"]) - } -}; -</script>
--- a/client/src/components/Userdetail.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,262 +0,0 @@ -<template> - <div class="userdetails shadow fadeIn animated"> - <div class="card"> - <div class="card-header shadow-sm text-white bg-info mb-3"> - {{ this.cardHeader }} - <span @click="closeDetailview" class="pull-right"> - <i class="fa fa-close"></i> - </span> - </div> - <div class="card-body"> - <form @submit.prevent="save"> - <div class="formfields"> - <div v-if="currentUser.isNew" class="form-group row"> - <label for="user">Username</label> - <input type="user" :placeholder="userNamePlaceholder" class="form-control form-control-sm" id="user" aria-describedby="userHelp" v-model="currentUser.user"> - <div v-show="errors.user" class="text-danger"> - <small> - <i class="fa fa-warning"></i> {{ errors.user }}</small> - </div> - </div> - <div class="form-group row"> - <label for="country">Country</label> - <select class="form-control form-control-sm" v-on:change="validateCountry" v-model="currentUser.country"> - <option disabled value="">Please select one</option> - <option v-for="country in countries" v-bind:value="country" v-bind:key="country">{{country}}</option> - </select> - <div v-show="errors.country" class="text-danger"> - <small> - <i class="fa fa-warning"></i> {{ errors.country }}</small> - </div> - </div> - <div class="form-group row"> - <label for="email">Email address</label> - <input type="email" v-on:change="validateEmailaddress" class="form-control form-control-sm" id="email" aria-describedby="emailHelp" v-model="currentUser.email"> - <div v-show="errors.email" class="text-danger"> - <small> - <i class="fa fa-warning"></i> {{ errors.email }}</small> - </div> - </div> - <div class="form-group row"> - <label for="role">Role</label> - <select class="form-control form-control-sm" v-on:change="validateRole" v-model="currentUser.role"> - <option disabled value="">Please select one</option> - <option value="sys_admin">Sysadmin</option> - <option value="waterway_admin">Waterway Admin</option> - <option value="waterway_user">Waterway User</option> - </select> - <div v-show="errors.role" class="text-danger"> - <small> - <i class="fa fa-warning"></i> {{ errors.role }}</small> - </div> - </div> - <div class="form-group row"> - <PasswordField @fieldchange="passwordChanged" :placeholder="passwordPlaceholder" :label="passwordLabel" :passworderrors="errors.password"></PasswordField> - </div> - <div class="form-group row"> - <PasswordField @fieldchange="passwordReChanged" :placeholder="passwordRePlaceholder" :label="passwordReLabel" :passworderrors="errors.passwordre"></PasswordField> - </div> - </div> - <div> - <button type="submit" :disabled="submitted" class="shadow-sm btn btn-info pull-right">Submit</button> - </div> - </form> - </div> - </div> - </div> -</template> - -<style lang="scss"> -@import "../assets/application.scss"; - -.formfields { - width: 10vw; -} - -.userdetails { - margin-top: $topbarheight; - min-width: 40vw; - margin-right: auto; - height: 100%; -} - -form { - margin-left: $offset; - font-size: 0.9rem; -} -</style> -<script> -import { displayError } from "../lib/errors.js"; -import { mapGetters } from "vuex"; -import PasswordField from "../components/Passwordfield"; - -const emptyErrormessages = () => { - return { - email: "", - country: "", - role: "", - password: "", - passwordre: "" - }; -}; - -const isEmailValid = email => { - /** - * - * For convenience purposes the same regex used as in the go code - * cf. types.go - * - */ - // eslint-disable-next-line - return /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test( - email - ); -}; - -const violatedPasswordRules = password => { - return ( - // rules according to issue 70 - password.length < 7 || - /\W/.test(password) == false || - /\d/.test(password) == false - ); -}; - -export default { - name: "userdetail", - components: { - PasswordField - }, - data() { - return { - passwordLabel: "Password", - passwordReLabel: "Repeat Password", - passwordPlaceholder: "password", - passwordRePlaceholder: "password again", - password: "", - passwordre: "", - currentUser: {}, - path: null, - submitted: false, - errors: { - email: "", - country: "", - role: "", - password: "", - passwordre: "" - } - }; - }, - mounted() { - this.currentUser = { ...this.user }; - this.path = this.user.name; - }, - watch: { - user() { - this.currentUser = { ...this.user }; - this.path = this.user.name; - this.clearPassword(); - this.clearErrors(); - } - }, - computed: { - cardHeader() { - if (this.currentUser.isNew) return "N.N"; - return this.currentUser.user; - }, - userNamePlaceholder() { - if (this.currentUser.isNew) return "N.N"; - return ""; - }, - ...mapGetters("application", ["countries"]), - user() { - return this.$store.getters["usermanagement/currentUser"]; - }, - isFormValid() { - return ( - isEmailValid(this.currentUser.email) && - this.currentUser.country && - this.password === this.passwordre && - (this.password === "" || !violatedPasswordRules(this.password)) - ); - } - }, - methods: { - passwordChanged(value) { - this.password = value; - this.validatePassword(); - }, - passwordReChanged(value) { - this.passwordre = value; - this.validatePassword(); - }, - clearErrors() { - this.errors = emptyErrormessages(); - }, - clearPassword() { - this.password = ""; - this.passwordre = ""; - }, - closeDetailview() { - this.$store.commit("usermanagement/clearCurrentUser"); - this.$store.commit("usermanagement/setUserDetailsInvisible"); - }, - validateCountry() { - this.errors.country = this.currentUser.country - ? "" - : "Please choose a country"; - }, - validateRole() { - this.errors.role = this.currentUser.role ? "" : "Please choose a role"; - }, - validatePassword() { - this.errors.passwordre = - this.password === this.passwordre ? "" : "Passwords do not match!"; - this.errors.password = - this.password === "" || !violatedPasswordRules(this.password) - ? "" - : "Password should at least be 8 char long including 1 digit and 1 special char like $"; - }, - validateEmailaddress() { - this.errors.email = isEmailValid(this.currentUser.email) - ? "" - : "invalid email"; - }, - validate() { - this.validateCountry(); - this.validateRole(); - this.validatePassword(); - this.validateEmailaddress(); - }, - save() { - this.validate(); - if (!this.isFormValid) return; - if (this.password) this.currentUser.password = this.password; - this.submitted = true; - this.$store - .dispatch("usermanagement/saveCurrentUser", { - path: this.user.user, - user: this.currentUser - }) - .then(() => { - this.submitted = false; - this.$store.dispatch("usermanagement/loadUsers").catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - }) - .catch(error => { - this.submitted = false; - const { status, data } = error.response; - displayError({ - title: "Error while saving user", - message: `${status}: ${data.message || data}` - }); - }); - } - } -}; -</script>
--- a/client/src/lib/errors.js Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -/** facade to wrap calls to vue2-toastr */ -import app from "../main"; - -const displayError = ({ title, message }) => { - app.$toast.error({ - title: title, - message: message - }); -}; - -export { displayError };
--- a/client/src/lib/http.js Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -import axios from "axios"; - -export const HTTP = axios.create({ - baseURL: process.env.VUE_APP_API_URL || "/api" - /* headers: { - Authorization: 'Bearer {token}' - }*/ -});
--- a/client/src/lib/session.js Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -/** - * Compares whether session is current - * based on the expiry information and the - * current date - * - * @param {number} expiresFromPastSession - */ -function sessionStillActive(expiresFromPastSession) { - if (!expiresFromPastSession) return false; - const now = Date.now(); - const stillActive = now < expiresFromPastSession; - return stillActive; -} -/** - * Converts a given unix time to Milliseconds - * - * @param {string} timestring - */ -function toMillisFromString(timestring) { - return timestring * 1000; -} - -export { sessionStillActive, toMillisFromString };
--- a/client/src/stores/application.js Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -const defaultCollapseState = true; - -const Application = { - namespaced: true, - state: { - appTitle: process.env.VUE_APP_TITLE, - secondaryLogo: process.env.VUE_APP_SECONDARY_LOGO_URL, - sidebar: { - iscollapsed: defaultCollapseState - }, - countries: ["AT", "SK", "HU", "HR", "RS", "BiH", "BG", "RO", "UA"] - }, - getters: { - countries: state => { - return state.countries; - }, - sidebarCollapsed: state => { - return state.sidebar.iscollapsed; - }, - appTitle: state => { - return state.appTitle; - }, - secondaryLogo: state => { - return state.secondaryLogo; - } - }, - mutations: { - toggleSidebar: state => { - state.sidebar.iscollapsed = !state.sidebar.iscollapsed; - }, - resetSidebar: state => { - state.sidebar.iscollapsed = defaultCollapseState; - }, - collapseSidebar: state => { - state.sidebar.iscollapsed = true; - } - }, - actions: {} -}; - -export default Application;
--- a/client/src/stores/mapstore.js Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -//import { HTTP } from "../lib/http"; - -import TileWMS from "ol/source/TileWMS.js"; -import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js"; -import OSM from "ol/source/OSM"; -import { Stroke, Style } from "ol/style.js"; -import VectorSource from "ol/source/Vector.js"; - -const MapStore = { - namespaced: true, - state: { - layers: [ - { - name: "Open Streetmap", - data: new TileLayer({ - source: new OSM() - }), - isVisible: true - }, - { - name: "D4D", - data: new TileLayer({ - source: new TileWMS({ - url: "https://demo.d4d-portal.info/wms", - params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true } - }) - }), - isVisible: true - }, - { - name: "Fairways Dimensions", - data: new VectorLayer({ - source: new VectorSource(), - style: new Style({ - stroke: new Stroke({ - color: "rgba(0, 0, 255, 1.0)", - width: 2 - }) - }) - }), - isVisible: true - } - ] - }, - getters: { - layers: state => { - return state.layers; - } - }, - mutations: { - toggleVisibility: (state, layer) => { - state.layers[layer].isVisible = !state.layers[layer].isVisible; - state.layers[layer].data.setVisible(state.layers[layer].isVisible); - } - } -}; - -export default MapStore;
--- a/client/src/stores/user.js Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -import { HTTP } from "../lib/http"; - -const User = { - namespaced: true, - state: { - authenticated: false, - expires: null, - roles: [], - user: "" - }, - getters: { - isAuthenticated: state => { - return state.authenticated; - }, - userinfo: state => { - return state.user; - }, - roles: state => { - return state.roles; - }, - expires: state => { - return state.expires; - }, - isWaterwayAdmin: state => { - return state.roles.includes("waterway_admin"); - }, - isSysAdmin: state => { - return state.roles.includes("sys_admin"); - } - }, - mutations: { - auth_success: (state, data) => { - const { token, user, expires, roles } = data; - localStorage.setItem("expires", expires); - localStorage.setItem("roles", roles); - localStorage.setItem("token", token); - localStorage.setItem("user", user); - state.expires = expires; - state.roles = roles; - state.user = user; - state.authenticated = true; - }, - clear_auth: state => { - state.authenticated = false; - state.expires = null; - state.roles = []; - state.user = ""; - localStorage.clear(); - }, - set_user: (state, name) => { - state.user = name; - }, - set_roles: (state, roles) => { - state.roles = roles; - }, - set_expires: (state, expires) => { - state.expires = expires; - }, - set_authenticate: state => { - state.authenticated = true; - } - }, - actions: { - login({ commit }, user) { - // using POST is a bit more secure than GET - return new Promise((resolve, reject) => { - HTTP.post("/login", user) - .then(response => { - commit("auth_success", response.data); - resolve(response); - }) - .catch(error => { - commit("clear_auth"); - reject(error); - }); - }); - } - } -}; - -export default User;
--- a/client/src/stores/usermanagement.js Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -import { HTTP } from "../lib/http"; - -const newUser = () => { - return { - user: "", - email: "", - country: null, - role: null, - isNew: true, - password: "", - roleLabel: "" - }; -}; - -const UserManagement = { - namespaced: true, - state: { - users: null, - currentUser: null, - userDetailsVisible: false - }, - getters: { - isUserDetailsVisible: state => { - return state.userDetailsVisible; - }, - currentUser: state => { - return state.currentUser; - }, - users: state => { - return state.users; - }, - getUserByName: state => name => { - return state.users.find(user => { - return user.user === name; - }); - } - }, - mutations: { - setUserDetailsInvisible: state => { - state.userDetailsVisible = false; - }, - setUserDetailsVisible: state => { - state.userDetailsVisible = true; - }, - usersLoaded: (state, data) => { - const resolveLabel = x => { - const labels = { - waterway_user: "Waterway User", - waterway_admin: "Waterway Administrator", - sys_admin: "System Admininistrator" - }; - return labels[x]; - }; - let users = data.users.map(u => { - u["roleLabel"] = resolveLabel(u["role"]); - return u; - }); - state.users = users; - }, - setCurrentUser: (state, data) => { - state.currentUser = data; - state.userDetailsVisible = true; - }, - clearCurrentUser: state => { - state.currentUser = newUser(); - } - }, - actions: { - deleteUser({ commit }, data) { - const { name } = data; - return new Promise((resolve, reject) => { - HTTP.delete("/users/" + name, { - headers: { "X-Gemma-Auth": localStorage.getItem("token") } - }) - .then(response => { - commit("clearCurrentUser"); - commit("setUserDetailsInvisible"); - resolve(response); - }) - .catch(error => { - reject(error); - }); - }); - }, - saveCurrentUser({ commit }, data) { - const { path, user } = data; - if (user.isNew) { - return new Promise((resolve, reject) => { - HTTP.post("/users", user, { - headers: { "X-Gemma-Auth": localStorage.getItem("token") } - }) - .then(response => { - commit("setUserDetailsInvisible"); - commit("clearCurrentUser"); - resolve(response); - }) - .catch(error => { - reject(error); - }); - }); - } else { - return new Promise((resolve, reject) => { - HTTP.put("/users/" + path, user, { - headers: { "X-Gemma-Auth": localStorage.getItem("token") } - }) - .then(response => { - commit("setUserDetailsInvisible"); - commit("clearCurrentUser"); - resolve(response); - }) - .catch(error => { - reject(error); - }); - }); - } - }, - loadUsers({ commit }) { - return new Promise((resolve, reject) => { - HTTP.get("/users", { - headers: { "X-Gemma-Auth": localStorage.getItem("token") } - }) - .then(response => { - commit("usersLoaded", response.data); - resolve(response); - }) - .catch(error => { - reject(error); - }); - }); - } - } -}; - -export default UserManagement;
--- a/client/src/views/Login.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,201 +0,0 @@ -(<template> - <div class="d-flex flex-column login shadow-lg"> - <div class="loginmask"> - <!-- logo section --> - <div class="d-flex flex-row justify-content-center mb-3"> - <div class="logo"><img src="../assets/logo.png"></div> - <div class="title"> - <h1>{{ appTitle }}</h1> - </div> - </div> - <!-- end logo section --> - <form class="loginform" @submit.prevent="login"> - <div id="alert" :style="errorMessageStyle" :class="errorMessageClass" role="alert"> - <span>{{ errorMessage }}</span> - </div> - <div class="input-group mb-3"> - <input type="text" v-model="user" id="inputUsername" class="form-control shadow-sm" :placeholder="usernameLabel" required autofocus> - </div> - <div class="input-group mb-3"> - <input :type="isPasswordVisible" v-model="password" id="inputPassword" class="form-control shadow-sm" :placeholder='passwordLabel' :required='!showPasswordReset' :disabled='showPasswordReset'> - <div class="input-group-append"> - <span class="input-group-text disabled" id="basic-addon2" @click="showPassword"> - <i :class="eyeIcon"></i> - </span> - </div> - </div> - <button v-if="showPasswordReset==false" class="btn btn-primary btn-block shadow-sm" :disabled="submitted || showPasswordReset" type="submit"> - <translate>Login</translate> - </button> - <div v-if="showPasswordReset" class="passwordreset"> - <button class="btn btn-block btn-info" type="button" @click="resetPassword"> - <translate>Request password reset!</translate> - </button> - <div class="pull-right"> - <a href="#" @click.prevent="togglePasswordReset"> - <translate>back to login</translate> - </a> - </div> - </div> - <div v-else class="pull-right"> - <a href="#" @click.prevent="togglePasswordReset"> - <translate>Forgot password</translate> - </a> - </div> - </form> - - <!-- bottom logo section --> - <div class="mb-3 secondary-logo"><img :src="secondaryLogo"></div> - </div> - </div> -</template>) - -<style lang="scss"> -@import "../assets/application.scss"; - -.login { - background-color: white; - min-width: 375px; - min-height: 500px; - @extend %fully-centered; -} - -.loginform { - max-width: 375px; - margin-left: auto; - margin-right: auto; -} - -.loginmask { - margin-left: $large-offset; - margin-right: $large-offset; - margin-top: $large-offset; -} - -.logo { - margin-right: $offset; -} - -.alert { - padding: 0.5rem; -} - -.secondary-logo { - max-width: 375px; - margin-left: auto; - margin-right: auto; - margin-bottom: auto; -} -</style> - -<script> -import { mapGetters } from "vuex"; -import { HTTP } from "../lib/http"; -import { displayError } from "../lib/errors.js"; - -export default { - name: "login", - data() { - return { - user: "", - password: "", - submitted: false, - loginFailed: false, - passwordJustResetted: false, - readablePassword: false, - showPasswordReset: false, - usernameToReset: "" - }; - }, - computed: { - errorMessage() { - if (this.loginFailed) return this.$gettext("Login failed"); - if (this.passwordJustResetted) - return this.$gettext("Password reset requested!"); - return "&npsp;"; - }, - passwordLabel() { - return this.$gettext("Enter passphrase"); - }, - usernameLabel() { - return this.$gettext("Enter username"); - }, - isPasswordVisible() { - return this.readablePassword ? "text" : "password"; - }, - eyeIcon() { - return { - fa: true, - "fa-eye": !this.readablePassword, - "fa-eye-slash": this.readablePassword - }; - }, - errorMessageStyle() { - if (this.loginFailed || this.passwordJustResetted) { - return "visibility:visible"; - } - return "visibility:hidden"; - }, - errorMessageClass() { - let result = { - "mb-3": true, - errormessage: true, - alert: true - }; - if (this.loginFailed) { - result["alert-danger"] = true; - } - if (this.passwordJustResetted) { - result["alert-info"] = true; - } - return result; - }, - ...mapGetters("application", ["appTitle", "secondaryLogo"]) - }, - methods: { - login() { - this.submitted = true; - this.passwordJustResetted = false; - const { user, password } = this; - this.$store - .dispatch("user/login", { user, password }) - .then(() => { - this.loginFailed = false; - this.$router.push("/"); - }) - .catch(error => { - this.loginFailed = true; - this.submitted = false; - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - }, - showPassword() { - // disallowing toggle when in reset mode - if (this.showPasswordReset) return; - this.readablePassword = !this.readablePassword; - }, - togglePasswordReset() { - this.passwordJustResetted = false; - this.showPasswordReset = !this.showPasswordReset; - this.loginFailed = false; - }, - resetPassword() { - if (this.user) { - HTTP.post("/users/passwordreset", { user: this.user }).catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - this.togglePasswordReset(); - this.passwordJustResetted = true; - } - } - } -}; -</script>
--- a/client/src/views/Main.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -<template> - <div class="main d-flex flex-column"> - <Maplayer :lat="6155376" :long="1819178" :zoom="11"></Maplayer> - </div> -</template> - -<style lang="scss"> -@import "../assets/application.scss"; - -.menubutton { - margin-left: $small-offset; -} -</style> - -<script> -import Maplayer from "../components/Maplayer"; - -export default { - name: "mainview", - components: { - Maplayer - } -}; -</script>
--- a/client/src/views/Usermanagement.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -<template> - <div class="main d-flex flex-column"> - <div class="d-flex content flex-column"> - <div class="d-flex flex-row"> - <div :class="userlistStyle"> - <div class="card"> - <div class="card-header shadow-sm text-white bg-info mb-3"> - Users - </div> - <div class="card-body"> - <table id="datatable" :class="tableStyle"> - <thead> - <tr> - <th scope="col" @click="sortBy('user')"> - <span>Username - <i v-if="sortCriterion=='user'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col" @click="sortBy('country')"> - <span>Country - <i v-if="sortCriterion=='country'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col" @click="sortBy('email')"> - <span>Email - <i v-if="sortCriterion=='email'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col" @click="sortBy('role')"> - <span>Role - <i v-if="sortCriterion=='role'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col"></th> - </tr> - </thead> - <tbody> - <tr v-for="user in users" :key="user.user" @click="selectUser(user.user)"> - <td>{{ user.user }}</td> - <td>{{ user.country }}</td> - <td>{{ user.email}}</td> - <td> - <i v-tooltip="user.roleLabel" :class="{ - fa:true, - icon:true, - 'fa-user':user.role==='waterway_user', - 'fa-star':user.role=='sys_admin', - 'fa-adn':user.role==='waterway_admin'}"></i> - </td> - <td> - <i @click="deleteUser(user.user)" class="icon fa fa-trash-o"></i> - </td> - </tr> - </tbody> - </table> - </div> - <div class="d-flex flex-row pagination"> - <i @click=" prevPage " v-if="this.currentPage!=1 " class="backwards btn btn-sm btn-light align-self-center pages fa fa-caret-left "></i> {{this.currentPage}} / {{this.pages}} - <i @click="nextPage " class="forwards btn btn-sm btn-light align-self-center pages fa fa-caret-right "></i> - </div> - <div class="adduser "> - <button @click="addUser " class="btn btn-info pull-right shadow-sm ">Add User</button> - </div> - </div> - </div> - <Userdetail v-if="isUserDetailsVisible "></Userdetail> - </div> - </div> - </div> -</template> - -<style lang="scss"> -@import "../assets/application.scss"; -@import "../assets/tooltip.scss"; -.main { - height: 100vh; -} - -.backwards { - margin-right: 0.5rem; -} - -.forwards { - margin-left: 0.5rem; -} - -.content { - margin-top: $large-offset; - margin-left: auto; - margin-right: auto; -} - -.adduser { - margin-right: $offset; - padding-bottom: $offset; -} - -.icon { - font-size: large; -} - -.userlist { - margin-top: $topbarheight; - margin-right: $offset; - min-width: 520px; - height: 100%; -} - -.pagination { - margin-left: auto; - margin-right: auto; -} -.userlistsmall { - width: 30vw; -} - -.userlistextended { - width: 70vw; -} - -.table { - width: 90% !important; - margin: auto; -} - -.table th, -.pages { - cursor: pointer; -} - -.table th, -td { - font-size: 0.9rem; - border-top: 0px !important; - text-align: left; - padding: 0.5rem !important; -} - -.table td { - font-size: 0.9rem; - cursor: pointer; -} - -tr span { - display: flex; -} -</style> - -<script> -import Userdetail from "../components/Userdetail"; -import store from "../store"; -import { mapGetters } from "vuex"; -import { displayError } from "../lib/errors.js"; - -export default { - name: "userview", - data() { - return { - sortCriterion: "user", - pageSize: 10, - currentPage: 1 - }; - }, - components: { - Userdetail - }, - computed: { - ...mapGetters("usermanagement", ["isUserDetailsVisible"]), - ...mapGetters("application", ["sidebarCollapsed"]), - users() { - let users = [...this.$store.getters["usermanagement/users"]]; - users.sort((a, b) => { - if ( - a[this.sortCriterion].toLowerCase() < - b[this.sortCriterion].toLowerCase() - ) - return -1; - if ( - a[this.sortCriterion].toLowerCase() > - b[this.sortCriterion].toLowerCase() - ) - return 1; - return 0; - }); - const start = (this.currentPage - 1) * this.pageSize; - return users.slice(start, start + this.pageSize); - }, - pages() { - let users = [...this.$store.getters["usermanagement/users"]]; - return Math.ceil(users.length / this.pageSize); - }, - tableStyle() { - return { - table: true, - "table-hover": true, - "table-sm": this.isUserDetailsVisible, - fadeIn: true, - animated: true - }; - }, - userlistStyle() { - return { - userlist: true, - shadow: true, - userlistsmall: this.isUserDetailsVisible, - userlistextended: !this.isUserDetailsVisible - }; - } - }, - methods: { - tween() {}, - nextPage() { - if (this.currentPage < this.pages) { - document.querySelector("#datatable").classList.add("fadeOut"); - setTimeout(() => { - document.querySelector("#datatable").classList.remove("fadeOut"); - this.currentPage += 1; - }, 10); - } - return; - }, - prevPage() { - if (this.currentPage > 0) { - document.querySelector("#datatable").classList.add("fadeOut"); - setTimeout(() => { - document.querySelector("#datatable").classList.remove("fadeOut"); - this.currentPage -= 1; - }, 10); - } - return; - }, - sortBy(criterion) { - this.sortCriterion = criterion; - }, - deleteUser(name) { - this.$store - .dispatch("usermanagement/deleteUser", { name: name }) - .then(() => { - this.submitted = false; - this.$store.dispatch("usermanagement/loadUsers").catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - }) - .catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - }, - addUser() { - this.$store.commit("usermanagement/clearCurrentUser"); - this.$store.commit("usermanagement/setUserDetailsVisible"); - }, - selectUser(name) { - const user = this.$store.getters["usermanagement/getUserByName"](name); - this.$store.commit("usermanagement/setCurrentUser", user); - } - }, - beforeRouteEnter(to, from, next) { - store - .dispatch("usermanagement/loadUsers") - .then(next) - .catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data}` - }); - }); - }, - beforeRouteLeave(to, from, next) { - store.commit("usermanagement/clearCurrentUser"); - store.commit("usermanagement/setUserDetailsInvisible"); - next(); - } -}; -</script>
--- a/client/src/views/Users.vue Fri Sep 07 11:13:56 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -<template> - <div class="main d-flex flex-column"> - <div class="d-flex content flex-column"> - <div class="d-flex flex-row"> - <div :class="userlistStyle"> - <div class="card"> - <div class="card-header shadow-sm text-white bg-info mb-3"> - Users - </div> - <div class="card-body"> - <table id="datatable" :class="tableStyle"> - <thead> - <tr> - <th scope="col" @click="sortBy('user')"> - <span>Username - <i v-if="sortCriterion=='user'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col" @click="sortBy('country')"> - <span>Country - <i v-if="sortCriterion=='country'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col" @click="sortBy('email')"> - <span>Email - <i v-if="sortCriterion=='email'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col" @click="sortBy('role')"> - <span>Role - <i v-if="sortCriterion=='role'" class="fa fa-angle-down"></i> - </span> - </th> - <th scope="col"></th> - </tr> - </thead> - <tbody> - <tr v-for="user in users" :key="user.user" @click="selectUser(user.user)"> - <td>{{ user.user }}</td> - <td>{{ user.country }}</td> - <td>{{ user.email}}</td> - <td> - <i v-tooltip="user.roleLabel" :class="{ - fa:true, - icon:true, - 'fa-user':user.role==='waterway_user', - 'fa-star':user.role=='sys_admin', - 'fa-adn':user.role==='waterway_admin'}"></i> - </td> - <td> - <i @click="deleteUser(user.user)" class="icon fa fa-trash-o"></i> - </td> - </tr> - </tbody> - </table> - </div> - <div class="d-flex flex-row pagination"> - <i @click=" prevPage " v-if="this.currentPage!=1 " class="backwards btn btn-sm btn-light align-self-center pages fa fa-caret-left "></i> {{this.currentPage}} / {{this.pages}} - <i @click="nextPage " class="forwards btn btn-sm btn-light align-self-center pages fa fa-caret-right "></i> - </div> - <div class="adduser "> - <button @click="addUser " class="btn btn-info pull-right shadow-sm ">Add User</button> - </div> - </div> - </div> - <Userdetail v-if="isUserDetailsVisible "></Userdetail> - </div> - </div> - </div> -</template> - -<style lang="scss"> -@import "../assets/application.scss"; -@import "../assets/tooltip.scss"; -.main { - height: 100vh; -} - -.backwards { - margin-right: 0.5rem; -} - -.forwards { - margin-left: 0.5rem; -} - -.content { - margin-top: $large-offset; - margin-left: auto; - margin-right: auto; -} - -.adduser { - margin-right: $offset; - padding-bottom: $offset; -} - -.icon { - font-size: large; -} - -.userlist { - margin-top: $topbarheight; - margin-right: $offset; - min-width: 520px; - height: 100%; -} - -.pagination { - margin-left: auto; - margin-right: auto; -} -.userlistsmall { - width: 30vw; -} - -.userlistextended { - width: 70vw; -} - -.table { - width: 90% !important; - margin: auto; -} - -.table th, -.pages { - cursor: pointer; -} - -.table th, -td { - font-size: 0.9rem; - border-top: 0px !important; - text-align: left; - padding: 0.5rem !important; -} - -.table td { - font-size: 0.9rem; - cursor: pointer; -} - -tr span { - display: flex; -} -</style> - -<script> -import Userdetail from "../components/Userdetail"; -import store from "../store"; -import { mapGetters } from "vuex"; -import { displayError } from "../lib/errors.js"; - -export default { - name: "userview", - data() { - return { - sortCriterion: "user", - pageSize: 10, - currentPage: 1 - }; - }, - components: { - Userdetail - }, - computed: { - ...mapGetters("usermanagement", ["isUserDetailsVisible"]), - ...mapGetters("application", ["sidebarCollapsed"]), - users() { - let users = [...this.$store.getters["usermanagement/users"]]; - users.sort((a, b) => { - if ( - a[this.sortCriterion].toLowerCase() < - b[this.sortCriterion].toLowerCase() - ) - return -1; - if ( - a[this.sortCriterion].toLowerCase() > - b[this.sortCriterion].toLowerCase() - ) - return 1; - return 0; - }); - const start = (this.currentPage - 1) * this.pageSize; - return users.slice(start, start + this.pageSize); - }, - pages() { - let users = [...this.$store.getters["usermanagement/users"]]; - return Math.ceil(users.length / this.pageSize); - }, - tableStyle() { - return { - table: true, - "table-hover": true, - "table-sm": this.isUserDetailsVisible, - fadeIn: true, - animated: true - }; - }, - userlistStyle() { - return { - userlist: true, - shadow: true, - userlistsmall: this.isUserDetailsVisible, - userlistextended: !this.isUserDetailsVisible - }; - } - }, - methods: { - tween() {}, - nextPage() { - if (this.currentPage < this.pages) { - document.querySelector("#datatable").classList.add("fadeOut"); - setTimeout(() => { - document.querySelector("#datatable").classList.remove("fadeOut"); - this.currentPage += 1; - }, 10); - } - return; - }, - prevPage() { - if (this.currentPage > 0) { - document.querySelector("#datatable").classList.add("fadeOut"); - setTimeout(() => { - document.querySelector("#datatable").classList.remove("fadeOut"); - this.currentPage -= 1; - }, 10); - } - return; - }, - sortBy(criterion) { - this.sortCriterion = criterion; - }, - deleteUser(name) { - this.$store - .dispatch("usermanagement/deleteUser", { name: name }) - .then(() => { - this.submitted = false; - this.$store.dispatch("usermanagement/loadUsers").catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - }) - .catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data.message || data}` - }); - }); - }, - addUser() { - this.$store.commit("usermanagement/clearCurrentUser"); - this.$store.commit("usermanagement/setUserDetailsVisible"); - }, - selectUser(name) { - const user = this.$store.getters["usermanagement/getUserByName"](name); - this.$store.commit("usermanagement/setCurrentUser", user); - } - }, - beforeRouteEnter(to, from, next) { - store - .dispatch("usermanagement/loadUsers") - .then(next) - .catch(error => { - const { status, data } = error.response; - displayError({ - title: "Backend Error", - message: `${status}: ${data}` - }); - }); - }, - beforeRouteLeave(to, from, next) { - store.commit("usermanagement/clearCurrentUser"); - store.commit("usermanagement/setUserDetailsInvisible"); - next(); - } -}; -</script>