Skip to content

Commit

Permalink
Merge pull request #31 from moevm/users-client
Browse files Browse the repository at this point in the history
Users client
  • Loading branch information
Saprigenie authored Nov 26, 2023
2 parents fd84ab1 + e671a91 commit f6db2da
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 17 deletions.
48 changes: 45 additions & 3 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,33 @@
О проекте
</router-link>
</li>

<li v-if="userStore.role === UserRole.admin" class="nav-item ps-4">
<router-link
:to="{ name: routeNames.Users }"
class="nav-link text-warning"
active-class="text-info"
>
Пользователи
</router-link>
</li>
</ul>
</div>
<div class="fs-2 text-primary" role="button">
<i
v-if="userStore.isAuthed"
class="bi bi-box-arrow-left"
@click="logout"
/>
<router-link
v-else
:to="{ name: routeNames.Auth }"
class="nav-link"
active-class="text-info"
>
<i class="bi bi-box-arrow-in-right" />
</router-link>
</div>
</div>
</nav>
<main>
Expand All @@ -45,21 +70,38 @@
<script setup lang="ts">
import { routeNames } from "@/router";
import ToasterComponent from "@/components/common/ToasterComponent.vue";
import { useUserStore } from "@/store/user";
import { useToaster } from "@/store/toaster";
import { ToastTypes } from "@/config/toast";
import { UserRole } from "@/config/users";
import { computed } from "vue";
const routes = [
const userStore = useUserStore(),
toaster = useToaster();
const routes = computed(() => [
routeNames.Map,
routeNames.MapsList,
routeNames.Queue,
routeNames.ObjectsList,
routeNames.Upload,
];
...(userStore.isAuthed ? [routeNames.Upload] : []),
]);
const routesTranslation = {
[routeNames.Map]: "Глобальная карта",
[routeNames.MapsList]: "Карты",
[routeNames.Queue]: "Очередь",
[routeNames.ObjectsList]: "Объекты",
[routeNames.Upload]: "Загрузить",
};
async function logout() {
await userStore.logout();
toaster.addToast({
title: "Выполнено",
body: "Вы вышли из аккаунта",
type: ToastTypes.info,
});
}
</script>

<style lang="scss">
Expand Down
1 change: 1 addition & 0 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const baseURL = `${serverURL}/api`;

export const api: AxiosInstance = axios.create({
baseURL,
withCredentials: true,
});

const min_map_zoom = import.meta.env.MIN_ZOOM ?? 1;
Expand Down
60 changes: 60 additions & 0 deletions client/src/components/routes/auth/AuthComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<div class="container">
<div class="card col-12 col-md-6 m-auto mt-2">
<div class="card-body m-auto">
<h1 class="text-center">Войти</h1>
<FormKit v-model="login" type="text" label="Логин" />
<FormKit v-model="password" type="password" label="Пароль" />

<div class="text-end">
<button class="btn btn-primary" @click="doLogin">Войти</button>
</div>

<div class="text-end mt-3">
<div class="alert alert-danger">
<button class="d-block w-100 btn btn-danger me-2" @click="devLogin">
Войти dev
</button>
</div>
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useUserStore } from "@/store/user";
import { useToaster } from "@/store/toaster";
import { ToastTypes } from "@/config/toast";
import { useRouter } from "vue-router";
import { routeNames } from "@/router";
const router = useRouter();
const userStore = useUserStore(),
toaster = useToaster();
const login = ref(""),
password = ref("");
async function doLogin() {
// await devLogin();
toaster.addToast({
title: "Выполнено",
body: "Вы вошли в аккаунт",
type: ToastTypes.primary,
});
}
async function devLogin() {
await userStore.devLogin();
toaster.addToast({
title: "Выполнено",
body: "Выполнен вход для разработчика",
type: ToastTypes.primary,
});
router.push({ name: routeNames.Home });
}
</script>
20 changes: 20 additions & 0 deletions client/src/components/routes/auth/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { api } from "@/api";
import { User } from "@/types/users";

function fetchUser() {
return api.get<User>("/auth/login");
}

function login(login: string, password: string) {
return api.post<User>("/auth/login", { login, password });
}

function logout() {
return api.delete("/auth/login");
}

function devLogin() {
return api.get<User>("/auth/login/dev");
}

export const UserAPI = { fetchUser, login, logout, devLogin };
7 changes: 5 additions & 2 deletions client/src/components/routes/maps/MapsListComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ import FlagRenderer from "@/components/renderers/FlagRenderer.vue";
import Modal from "@/components/common/Modal.vue";
import { useImages } from "@/api/websocket/images";
import { ref } from "vue";
import { useUserStore } from "@/store/user";
const router = useRouter();
const userStore = useUserStore();
const columnDefs: ColDef<MapInfo>[] = [
{ headerName: "Id", field: "id", flex: 2, minWidth: 80 },
{ headerName: "Имя", field: "name", flex: 3, minWidth: 180 },
Expand Down Expand Up @@ -89,15 +92,15 @@ const columnDefs: ColDef<MapInfo>[] = [
button: "btn-secondary",
hide: (data) => !(data.ready && data.sliced),
onClicked: (action, data) => {
router.push({ name: routeNames.Map, params: { y: data.center[0],
router.push({ name: routeNames.Map, params: { y: data.center[0],
x: data.center[1] } })
}
},
{
tooltip: "Удалить карту",
icon: "bi bi-trash",
button: "btn-danger",
hide: (data) => !(data.ready && data.sliced),
hide: (data) => !(data.ready && data.sliced) || !userStore.isAuthed,
onClicked: (action, data) => {
delElement.value = data.id;
modal.value?.open();
Expand Down
93 changes: 93 additions & 0 deletions client/src/components/routes/users/UsersComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<template>
<div class="container-lg mt-3">
<h3>Пользователи</h3>
<AgGridVue
class="ag-theme-alpine"
:column-defs="columnDefs"
:row-data="users"
:grid-options="options"
@grid-ready="fitActionsColumn"
/>
</div>
</template>

<script setup lang="ts">
import { AgGridVue } from "ag-grid-vue3";
import { ColDef, GridOptions } from "ag-grid-community";
import {
fitActionsColumn,
getActionsColDef,
getDefaultGridOptions,
} from "@/ag-grid/factory";
import { MapInfo } from "@/types/maps";
import { onBeforeMount, ref } from "vue";
import { User } from "@/types/users";
import { UserAdminAPI } from "@/components/routes/users/api";
import { useRouter } from "vue-router";
import { useUserStore } from "@/store/user";
import { routeNames } from "@/router";
import { ToastTypes } from "@/config/toast";
import { useToaster } from "@/store/toaster";
const router = useRouter();
const userStore = useUserStore(),
toaster = useToaster();
const users = ref<User[]>([]);
onBeforeMount(async () => {
users.value = (await UserAdminAPI.getUsers()).data;
});
const columnDefs: ColDef<User>[] = [
{ headerName: "Id", field: "_id.$oid", flex: 2, minWidth: 80 },
{ headerName: "Логин", field: "login", flex: 3, minWidth: 180 },
{ headerName: "Имя", field: "name", flex: 3, minWidth: 100 },
{ headerName: "Роль", field: "role", flex: 3, minWidth: 90 },
{
...getActionsColDef([
{
tooltip: "Войти",
icon: "bi bi-box-arrow-in-right",
button: "btn-primary",
onClicked: async (action, data) => {
await userStore.login(data.login, data.password);
await router.push({ name: routeNames.Home });
toaster.addToast({
title: "Выполнено",
body: `Выполнен вход за пользователя ${data.name} (${data.login})`,
type: ToastTypes.primary,
});
},
},
{
tooltip: "Удалить",
icon: "bi bi-trash",
button: "btn-danger",
onClicked: (action, data) => {
/**/
},
},
{
tooltip: "Редактировать",
icon: "bi bi-gear",
button: "btn-warning",
onClicked: (action, data) => {
/**/
},
},
]),
minWidth: 180,
},
];
const options: GridOptions<MapInfo> = {
...getDefaultGridOptions(),
domLayout: "autoHeight",
animateRows: true,
};
</script>

<style scoped lang="scss"></style>
21 changes: 21 additions & 0 deletions client/src/components/routes/users/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { api } from "@/api";
import { User } from "@/types/users";
import { UserCreation } from "@/components/routes/users/types";

function getUsers() {
return api.get<User[]>("/users");
}

function createUser(user: UserCreation) {
return api.post("/users", user);
}

function deleteUser(id: string) {
return api.delete(`/users/user/${id}`);
}

function updateUser(id: string, user: UserCreation) {
return api.put(`/users/user/${id}`, user);
}

export const UserAdminAPI = { getUsers, createUser, deleteUser, updateUser };
3 changes: 3 additions & 0 deletions client/src/components/routes/users/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { User } from "@/types/users";

export type UserCreation = Omit<User, "_id">;
4 changes: 4 additions & 0 deletions client/src/config/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum UserRole {
user = "user",
admin = "admin",
}
22 changes: 15 additions & 7 deletions client/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ import { vBsTooltip } from "@/bootstrap/tooltip";
import { plugin, defaultConfig } from "@formkit/vue";
import "@formkit/themes/genesis";
import { createPinia } from "pinia";
import { useUserStore } from "@/store/user";

const pinia = createPinia();
async function bootstrap() {
const pinia = createPinia();

const app = createApp(App);
app.use(router);
app.use(plugin, defaultConfig());
app.use(pinia);
app.directive("bs-tooltip", vBsTooltip);
app.mount("#app");
const userStore = useUserStore(pinia);
await userStore.fetchUser();

const app = createApp(App);
app.use(router);
app.use(plugin, defaultConfig());
app.use(pinia);
app.directive("bs-tooltip", vBsTooltip);
app.mount("#app");
}

bootstrap();
16 changes: 16 additions & 0 deletions client/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const routeNames = {
Map: "Map",
Object: "Object",
Home: "Home",
Auth: "Auth",
Users: "Users",
};

export const routePaths = {
Expand All @@ -18,6 +20,8 @@ export const routePaths = {
[routeNames.Map]: "/map/:y?/:x?",
[routeNames.Object]: "/object/:id",
[routeNames.Home]: "/home",
[routeNames.Auth]: "/auth",
[routeNames.Auth]: "/Users",
};

export const routes: RouteRecordRaw[] = [
Expand Down Expand Up @@ -63,6 +67,18 @@ export const routes: RouteRecordRaw[] = [
path: routePaths[routeNames.Home],
component: () => import("@/views/HomeView.vue"),
},

{
name: routeNames.Auth,
path: routePaths[routeNames.Auth],
component: () => import("@/views/AuthView.vue"),
},

{
name: routeNames.Users,
path: routePaths[routeNames.Users],
component: () => import("@/views/UsersView.vue"),
},
];

export const router = createRouter({
Expand Down
Loading

0 comments on commit f6db2da

Please sign in to comment.