|
@@ -1,15 +1,301 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div></div>
|
|
|
|
|
|
|
+ <div style="width: 95%; max-width: 900px">
|
|
|
|
|
+ <data-table
|
|
|
|
|
+ title="Utilisateurs"
|
|
|
|
|
+ :data="data"
|
|
|
|
|
+ :searchable="true"
|
|
|
|
|
+ :customButtons="customButtons"
|
|
|
|
|
+ :clickable="false"
|
|
|
|
|
+ :loadingAnimation="isLoading"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template v-slot:thead-tr> <th style="width: 100px">Action</th></template>
|
|
|
|
|
+ <template v-slot:tbody-tr="props">
|
|
|
|
|
+ <td class="fitwidth">
|
|
|
|
|
+ <button class="btn icon small primary" @click="editUser(props.row.username)">
|
|
|
|
|
+ <i class="material-icons">edit</i></button
|
|
|
|
|
+ >
|
|
|
|
|
+ <button class="btn icon small error" @click="deleteUser(props.row.username)">
|
|
|
|
|
+ <i class="material-icons">delete_forever</i>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </td></template
|
|
|
|
|
+ >
|
|
|
|
|
+ </data-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="showModal" class="modal" @click="closeModal">
|
|
|
|
|
+ <div class="modal-content user-form" ref="modal">
|
|
|
|
|
+ <h3 v-if="mode == 'edition'">Modifier Utilisateur</h3>
|
|
|
|
|
+ <h3 v-if="mode == 'creation'">Nouvel Utilisateur</h3>
|
|
|
|
|
+ <styled-input
|
|
|
|
|
+ label="Nom d'utilisateur"
|
|
|
|
|
+ v-model="userName"
|
|
|
|
|
+ :validateInput="usernameIncorrect"
|
|
|
|
|
+ :disabled="mode == 'edition'"
|
|
|
|
|
+ />
|
|
|
|
|
+ <styled-input label="Adresse email" v-model="email" />
|
|
|
|
|
+ <styled-input label="Mot de Passe" v-model="pwd" type="password" />
|
|
|
|
|
+ <styled-input
|
|
|
|
|
+ label="Confirmation du mot de Passe"
|
|
|
|
|
+ v-model="pwdcheck"
|
|
|
|
|
+ :validateInput="passwordIncorrect"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ />
|
|
|
|
|
+ <chips-input
|
|
|
|
|
+ id="preference_selection"
|
|
|
|
|
+ label="Rôles"
|
|
|
|
|
+ placeholder="Choisir un rôle"
|
|
|
|
|
+ :autocompleteList="autocompleteList"
|
|
|
|
|
+ :strict-autocomplete="true"
|
|
|
|
|
+ v-model="roleList"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="actions" style="margin-top: 8px; text-align: right">
|
|
|
|
|
+ <button class="btn primary small" @click="modifyUser" v-if="mode == 'edition'">
|
|
|
|
|
+ <i class="material-icons">save</i>Sauvegarder
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button class="btn primary small" @click="createUser" v-if="mode == 'creation'">
|
|
|
|
|
+ <i class="material-icons">create</i>Créer
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button class="btn error small" @click="showModal = false">
|
|
|
|
|
+ <i class="material-icons">close</i>Annuler
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
<script lang="ts">
|
|
|
|
|
+import Toast from "@/utils/Toast";
|
|
|
|
|
+import User, { Role } from "@/models/MyUser";
|
|
|
import { defineComponent } from "vue";
|
|
import { defineComponent } from "vue";
|
|
|
|
|
+import styledInput from "../components/input.vue";
|
|
|
|
|
+import chipsInput from "../components/SelectChipInput.vue";
|
|
|
|
|
+import DataTable, {
|
|
|
|
|
+ CustomButton,
|
|
|
|
|
+ DataTableData,
|
|
|
|
|
+ DataTableObject,
|
|
|
|
|
+} from "@/components/DataTable.vue";
|
|
|
|
|
|
|
|
|
|
+const API_URL = process.env.VUE_APP_API_URL;
|
|
|
export default defineComponent({
|
|
export default defineComponent({
|
|
|
|
|
+ components: { styledInput, chipsInput, DataTable },
|
|
|
data() {
|
|
data() {
|
|
|
- return {};
|
|
|
|
|
|
|
+ return {
|
|
|
|
|
+ columns: [
|
|
|
|
|
+ {
|
|
|
|
|
+ label: "Nom",
|
|
|
|
|
+ field: "username",
|
|
|
|
|
+ numeric: false,
|
|
|
|
|
+ html: false,
|
|
|
|
|
+ class: "overflow-cell",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: "Email",
|
|
|
|
|
+ field: "email",
|
|
|
|
|
+ numeric: false,
|
|
|
|
|
+ html: false,
|
|
|
|
|
+ class: "overflow-cell",
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: "Rôles",
|
|
|
|
|
+ field: "rolesStr",
|
|
|
|
|
+ numeric: false,
|
|
|
|
|
+ html: false,
|
|
|
|
|
+ class: "fitwidth",
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ customButtons: [] as Array<CustomButton>,
|
|
|
|
|
+ userList: [] as Array<User>,
|
|
|
|
|
+ potentialRoles: [] as Array<Role>,
|
|
|
|
|
+ roleList: [] as Array<string>,
|
|
|
|
|
+ showModal: false,
|
|
|
|
|
+ userName: "",
|
|
|
|
|
+ email: "",
|
|
|
|
|
+ pwd: "",
|
|
|
|
|
+ pwdcheck: "",
|
|
|
|
|
+ error: false,
|
|
|
|
|
+ errorUsername: false,
|
|
|
|
|
+ mode: "creation",
|
|
|
|
|
+ isLoading: true,
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ passwordIncorrect(): () => number {
|
|
|
|
|
+ return this.error ? () => -1 : () => 0;
|
|
|
|
|
+ },
|
|
|
|
|
+ usernameIncorrect(): () => number {
|
|
|
|
|
+ return this.errorUsername ? () => -1 : () => 0;
|
|
|
|
|
+ },
|
|
|
|
|
+ autocompleteList(): Array<string> {
|
|
|
|
|
+ return this.potentialRoles.map((p) => p.name);
|
|
|
|
|
+ },
|
|
|
|
|
+ data(): DataTableData<DataTableObject> {
|
|
|
|
|
+ return {
|
|
|
|
|
+ items: this.userList.map((user) => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ username: user.username,
|
|
|
|
|
+ email: user.email,
|
|
|
|
|
+ rolesStr: user.roles?.map((r) => r.name).join(", ") ?? "",
|
|
|
|
|
+ };
|
|
|
|
|
+ }),
|
|
|
|
|
+ columns: this.columns,
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ resetForm() {
|
|
|
|
|
+ this.errorUsername = false;
|
|
|
|
|
+ this.error = false;
|
|
|
|
|
+ this.userName = "";
|
|
|
|
|
+ this.email = "";
|
|
|
|
|
+ this.pwd = "";
|
|
|
|
|
+ this.pwdcheck = "";
|
|
|
|
|
+ this.roleList = [];
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ editUser(username: string) {
|
|
|
|
|
+ this.resetForm();
|
|
|
|
|
+ const u = this.getUser(username);
|
|
|
|
|
+ this.userName = u.username;
|
|
|
|
|
+ this.email = u.email ?? "";
|
|
|
|
|
+ this.roleList = u.roles?.map((r) => r.name) ?? [];
|
|
|
|
|
+
|
|
|
|
|
+ this.mode = "edition";
|
|
|
|
|
+ this.showModal = true;
|
|
|
|
|
+ },
|
|
|
|
|
+ newUser() {
|
|
|
|
|
+ if (this.mode != "creation") {
|
|
|
|
|
+ this.resetForm();
|
|
|
|
|
+ this.mode = "creation";
|
|
|
|
|
+ }
|
|
|
|
|
+ this.showModal = true;
|
|
|
|
|
+ },
|
|
|
|
|
+ getUser(username: string): User {
|
|
|
|
|
+ return this.userList.find((user) => user.username == username) as User;
|
|
|
|
|
+ },
|
|
|
|
|
+ getFormUser(): User | null {
|
|
|
|
|
+ this.error = this.pwd !== this.pwdcheck;
|
|
|
|
|
+ if (this.error) {
|
|
|
|
|
+ Toast({ html: "Les mots de passe ne correspondent pas." });
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return {
|
|
|
|
|
+ username: this.userName,
|
|
|
|
|
+ email: this.email,
|
|
|
|
|
+ password: this.pwd,
|
|
|
|
|
+ roles: this.roleList.map((name) => ({
|
|
|
|
|
+ name,
|
|
|
|
|
+ id: this.potentialRoles.find((r) => r.name == name)?.id as number,
|
|
|
|
|
+ })),
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ closeModal(e: MouseEvent) {
|
|
|
|
|
+ if (!(this.$refs.modal as HTMLElement).contains(e.target as HTMLElement)) {
|
|
|
|
|
+ this.showModal = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ modifyUser() {
|
|
|
|
|
+ const user = this.getFormUser();
|
|
|
|
|
+ if (user) {
|
|
|
|
|
+ const toast = Toast({ html: "Modification de l'utilisateur: " + this.userName });
|
|
|
|
|
+ const url = `${API_URL}users/${this.pwd != "" ? "changePassword/" : ""}${this.userName}`;
|
|
|
|
|
+ fetch(url, {
|
|
|
|
|
+ method: "PUT",
|
|
|
|
|
+ headers: { "Content-Type": "application/json" },
|
|
|
|
|
+ body: JSON.stringify(user),
|
|
|
|
|
+ })
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ toast.dismiss();
|
|
|
|
|
+ if (res.status == 200) {
|
|
|
|
|
+ this.errorUsername = true;
|
|
|
|
|
+ return res.json();
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new Error(res.statusText);
|
|
|
|
|
+ })
|
|
|
|
|
+ .then((data: User) => {
|
|
|
|
|
+ const idx = this.userList.findIndex((u) => u.username == data.username);
|
|
|
|
|
+ if (idx > -1) {
|
|
|
|
|
+ this.userList[idx] = data;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.showModal = false;
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err) => Toast({ html: err, classes: "error" }));
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ createUser() {
|
|
|
|
|
+ const user = this.getFormUser();
|
|
|
|
|
+ if (user) {
|
|
|
|
|
+ const toast = Toast({ html: "Création de l'utilisateur: " + this.userName });
|
|
|
|
|
+ fetch(API_URL + "users", {
|
|
|
|
|
+ method: "POST",
|
|
|
|
|
+ headers: { "Content-Type": "application/json" },
|
|
|
|
|
+ body: JSON.stringify(user),
|
|
|
|
|
+ })
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ toast.dismiss();
|
|
|
|
|
+ if (res.status == 200) {
|
|
|
|
|
+ this.errorUsername = false;
|
|
|
|
|
+ return res.json();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (res.status == 409) {
|
|
|
|
|
+ Toast({ html: "Le nom d'utilisateur est déja utilisé", classes: "error" });
|
|
|
|
|
+ this.errorUsername = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (res.status == 404) {
|
|
|
|
|
+ Toast({ html: "Le serveur ne répond pas.", classes: "error" });
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new Error(res.statusText);
|
|
|
|
|
+ })
|
|
|
|
|
+ .then((data: User) => {
|
|
|
|
|
+ this.userList.push(data);
|
|
|
|
|
+ this.resetForm();
|
|
|
|
|
+ this.showModal = false;
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((err) => Toast({ html: err, classes: "error" }));
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ deleteUser(username: string) {
|
|
|
|
|
+ if (confirm("Êtes vous sûr de vouloir supprimer l'utilisateur " + username))
|
|
|
|
|
+ fetch(API_URL + "users/" + username, { method: "DELETE" }).then((res) => {
|
|
|
|
|
+ if (res.status == 200) {
|
|
|
|
|
+ Toast({ html: "Utilisateur supprimé : " + username, classes: "success" });
|
|
|
|
|
+ this.userList = this.userList.filter((u) => u.username != username);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Toast({ html: "L'utilisateur n'a pas été supprimé : " + username, classes: "error" });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ fetch(API_URL + "roles")
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ if (res.status == 200) {
|
|
|
|
|
+ return res.json();
|
|
|
|
|
+ }
|
|
|
|
|
+ Toast({ html: "/roles " + res.statusText });
|
|
|
|
|
+ })
|
|
|
|
|
+ .then((data: unknown) => {
|
|
|
|
|
+ this.potentialRoles = data as Array<Role>;
|
|
|
|
|
+ });
|
|
|
|
|
+ fetch(API_URL + "users")
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ if (res.status == 200) {
|
|
|
|
|
+ return res.json();
|
|
|
|
|
+ }
|
|
|
|
|
+ Toast({ html: "/users " + res.statusText });
|
|
|
|
|
+ })
|
|
|
|
|
+ .then((data: unknown) => {
|
|
|
|
|
+ this.userList = data as Array<User>;
|
|
|
|
|
+ this.isLoading = false;
|
|
|
|
|
+ });
|
|
|
|
|
+ this.customButtons.push({ icon: "add", hide: false, onclick: this.newUser });
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
-<style scoped></style>
|
|
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.user-form {
|
|
|
|
|
+ width: 95%;
|
|
|
|
|
+ max-width: 600px;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|