view client/src/components/usermanagement/Userdetail.vue @ 5367:1695e17c5a83 extented-report

Adds schedualbility for reports as an import. In order to enable the sysadmin to schedule the automatic reports on dataquality there is now a new import type established. This import allows to edit the schedule for issuing the report. Besides: The layout for the usermanagement is now x-scrollable when overflown.
author Thomas Junk <thomas.junk@intevation.de>
date Wed, 23 Jun 2021 14:46:14 +0200
parents bf3d91d333ed
children 29af073c824d
line wrap: on
line source

<template>
  <div class="userdetails shadow-xs fadeIn animated card">
    <UIBoxHeader
      icon="user"
      :title="this.cardHeader"
      :closeCallback="closeDetailview"
    />
    <div class="card-body">
      <form @submit.prevent="save" class="ml-3">
        <div class="formfields">
          <div v-if="currentUser.isNew" class="form-group row">
            <label for="user"> <translate>Username</translate> </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>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.user }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <label for="country"> <translate>Country</translate> </label>
            <select
              class="form-control form-control-sm"
              v-on:change="validateCountry"
              v-model="currentUser.country"
            >
              <option disabled value>
                <translate>Please select one</translate>
              </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>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.country }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <label for="email"> <translate>Email address</translate> </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>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.email }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <label for="role"> <translate>Role</translate> </label>
            <select
              class="form-control form-control-sm"
              v-on:change="validateRole"
              v-model="currentUser.role"
            >
              <option disabled value>
                <translate>Please select one</translate>
              </option>
              <option value="sys_admin">
                <translate>Sysadmin</translate>
              </option>
              <option value="waterway_admin">
                <translate>Waterway Admin</translate>
              </option>
              <option value="waterway_user">
                <translate>Waterway User</translate>
              </option>
            </select>
            <div v-show="errors.role" class="text-danger">
              <small>
                <font-awesome-icon icon="exclamation-triangle" />
                {{ errors.role }}
              </small>
            </div>
          </div>
          <div class="form-group row">
            <PasswordField
              @fieldchange="passwordChanged"
              :placeholder="passwordPlaceholder"
              :label="passwordLabel"
              :passworderrors="errors.password"
            />
          </div>
          <div class="form-group row">
            <PasswordField
              @fieldchange="passwordReChanged"
              :placeholder="passwordRePlaceholder"
              :label="passwordReLabel"
              :passworderrors="errors.passwordre"
            />
          </div>
          <div class="form-group row">
            <label for="user">
              <translate>Recipient for DQL Report </translate>
            </label>
            <toggle-button
              :value="currentUser.reports"
              v-model="currentUser.reports"
              class="pt-1 w-100"
              :sync="true"
              :speed="100"
              v-tooltip="receivesReportLabel"
              :width="40"
              :height="20"
            />
          </div>
        </div>
        <div>
          <button
            type="submit"
            :disabled="submitted"
            class="shadow-sm btn btn-info submit-button"
          >
            <translate>Save</translate>
          </button>
        </div>
      </form>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.submit-button {
  position: absolute;
  right: $offset;
  bottom: $offset;
}
.mailbutton {
  width: 12vw;
  position: absolute;
  left: $large-offset;
  bottom: 0;
}

.formfields {
  width: 60%;
}

.userdetails {
  min-width: 400px;
  max-height: 693px;
  margin-right: $offset;
  overflow-y: auto;
}

form {
  font-size: $smaller;
}
</style>

<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 { displayError } from "@/lib/errors";
import { mapState } from "vuex";

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: () => import("./Passwordfield")
  },
  props: ["reportToggled"],
  data() {
    return {
      passwordLabel: this.$gettext("Password"),
      passwordReLabel: this.$gettext("Repeat Password"),
      passwordPlaceholder: this.$gettext("password"),
      passwordRePlaceholder: this.$gettext("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: {
    reportToggled() {
      this.currentUser.reports = this.user.reports;
    },
    user() {
      this.currentUser = { ...this.user };
      this.path = this.user.name;
      this.clearPassword();
      this.clearErrors();
    }
  },
  computed: {
    receivesReportLabel() {
      return this.$gettext("User receives Data Quality Report");
    },
    cardHeader() {
      if (this.currentUser.isNew) return this.$gettext("Add User");
      return this.currentUser.user;
    },
    userNamePlaceholder() {
      if (this.currentUser.isNew) return this.$gettext("Username");
      return "";
    },
    ...mapState("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
        ? ""
        : this.$gettext("Please choose a country");
    },
    validateRole() {
      this.errors.role = this.currentUser.role
        ? ""
        : this.$gettext("Please choose a role");
    },
    validatePassword() {
      this.errors.passwordre =
        this.password === this.passwordre
          ? ""
          : this.$gettext("Passwords do not match!");
      this.errors.password =
        this.password === "" || !violatedPasswordRules(this.password)
          ? ""
          : this.$gettext(
              "Password should at least be 8 char long including 1 digit and 1 special char like $"
            );
    },
    validateEmailaddress() {
      this.errors.email = isEmailValid(this.currentUser.email)
        ? ""
        : this.$gettext("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 => {
            let message = "Backend not reachable";
            if (error.response) {
              const { status, data } = error.response;
              message = `${status}: ${data.message || data}`;
            }
            displayError({
              title: this.$gettext("Backend Error"),
              message: message
            });
          });
        })
        .catch(error => {
          this.submitted = false;
          let message = "Backend not reachable";
          if (error.response) {
            const { status, data } = error.response;
            message = `${status}: ${data.message || data}`;
          }
          displayError({
            title: this.$gettext("Backend Error"),
            message: message
          });
        });
    }
  }
};
</script>