feat: Админ-панель. Добавлены вкладки пользователей и бэкапов.
This commit is contained in:
parent
67963bd395
commit
2447bc53af
26
api/app/controllers/backup_router.py
Normal file
26
api/app/controllers/backup_router.py
Normal file
@ -0,0 +1,26 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
from app.infrastructure.backup_service import BackupService
|
||||
from app.infrastructure.dependencies import require_admin
|
||||
from app.settings import settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/create/",
|
||||
response_class=FileResponse,
|
||||
summary="Create backup",
|
||||
description="Create backup",
|
||||
)
|
||||
async def create_backup(
|
||||
user=Depends(require_admin),
|
||||
):
|
||||
backup_service = BackupService(
|
||||
db_url=settings.BACKUP_DB_URL,
|
||||
app_files_dir=settings.FILE_UPLOAD_DIR,
|
||||
backup_dir=settings.BACKUP_DIR,
|
||||
pg_dump_path=settings.PG_DUMP_PATH,
|
||||
)
|
||||
return await backup_service.create_backup()
|
||||
@ -0,0 +1,40 @@
|
||||
"""0006 добавил таблицу backups
|
||||
|
||||
Revision ID: b58238896c0f
|
||||
Revises: 9e7ab1a46b64
|
||||
Create Date: 2025-06-29 16:50:16.638528
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'b58238896c0f'
|
||||
down_revision: Union[str, None] = '9e7ab1a46b64'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('backups',
|
||||
sa.Column('timestamp', sa.DateTime(), nullable=False),
|
||||
sa.Column('path', sa.String(), nullable=False),
|
||||
sa.Column('filename', sa.String(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('backups')
|
||||
# ### end Alembic commands ###
|
||||
@ -4,6 +4,7 @@ Base = declarative_base()
|
||||
|
||||
from app.domain.models.appointment_files import AppointmentFile
|
||||
from app.domain.models.appointments import Appointment
|
||||
from app.domain.models.backups import Backup
|
||||
from app.domain.models.appointment_types import AppointmentType
|
||||
from app.domain.models.lens_types import LensType
|
||||
from app.domain.models.lens_issues import LensIssue
|
||||
|
||||
17
api/app/domain/models/backups.py
Normal file
17
api/app/domain/models/backups.py
Normal file
@ -0,0 +1,17 @@
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Column, DateTime, String, Integer, ForeignKey
|
||||
|
||||
from app.domain.models.base import BaseModel
|
||||
|
||||
|
||||
class Backup(BaseModel):
|
||||
__tablename__ = 'backups'
|
||||
|
||||
timestamp = Column(DateTime, nullable=False, default=datetime.datetime.now)
|
||||
path = Column(String, nullable=False)
|
||||
filename = Column(String, nullable=False)
|
||||
|
||||
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
||||
|
||||
|
||||
40
api/app/infrastructure/backup_service.py
Normal file
40
api/app/infrastructure/backup_service.py
Normal file
@ -0,0 +1,40 @@
|
||||
import subprocess
|
||||
import os
|
||||
import tarfile
|
||||
import datetime
|
||||
from fastapi import HTTPException
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
|
||||
class BackupService:
|
||||
def __init__(self, db_url: str, app_files_dir: str, backup_dir: str, pg_dump_path: str):
|
||||
self.db_url = db_url
|
||||
self.app_files_dir = app_files_dir
|
||||
self.backup_dir = backup_dir
|
||||
self.pg_dump_path = pg_dump_path
|
||||
os.makedirs(backup_dir, exist_ok=True)
|
||||
|
||||
async def create_backup(self) -> FileResponse:
|
||||
try:
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_name = f"backup_{timestamp}"
|
||||
backup_path = os.path.join(self.backup_dir, backup_name)
|
||||
|
||||
db_dump_path = f"{os.getcwd()}/{backup_path}.sql"
|
||||
dump_cmd = f'"{self.pg_dump_path}" -Fc -d {self.db_url} -f "{db_dump_path}"'
|
||||
subprocess.run(dump_cmd, shell=True, check=True)
|
||||
|
||||
with tarfile.open(f"{backup_path}.tar.gz", "w:gz") as tar:
|
||||
tar.add(self.app_files_dir, arcname=self.app_files_dir)
|
||||
tar.add(db_dump_path, arcname="db_dump.sql")
|
||||
|
||||
os.remove(db_dump_path)
|
||||
return FileResponse(
|
||||
f"{backup_path}.tar.gz",
|
||||
media_type="application/gzip",
|
||||
filename=backup_name,
|
||||
)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e)
|
||||
raise HTTPException(500, f"Ошибка создания бэкапа: {e}")
|
||||
@ -5,6 +5,7 @@ from app.controllers.appointment_files_router import router as appointment_files
|
||||
from app.controllers.appointment_types_router import router as appointments_types_router
|
||||
from app.controllers.appointments_router import router as appointment_router
|
||||
from app.controllers.auth_router import router as auth_router
|
||||
from app.controllers.backup_router import router as backups_router
|
||||
from app.controllers.lens_issues_router import router as lens_issues_router
|
||||
from app.controllers.lens_types_router import router as lens_types_router
|
||||
from app.controllers.lenses_router import router as lenses_router
|
||||
@ -35,6 +36,7 @@ def start_app():
|
||||
tags=['appointment_types'])
|
||||
api_app.include_router(appointment_router, prefix=f'{settings.APP_PREFIX}/appointments', tags=['appointments'])
|
||||
api_app.include_router(auth_router, prefix=settings.APP_PREFIX, tags=['auth'])
|
||||
api_app.include_router(backups_router, prefix=f'{settings.APP_PREFIX}/backups', tags=['backups'])
|
||||
api_app.include_router(lens_issues_router, prefix=f'{settings.APP_PREFIX}/lens_issues', tags=['lens_issue'])
|
||||
api_app.include_router(lens_types_router, prefix=f'{settings.APP_PREFIX}/lens_types', tags=['lens_types'])
|
||||
api_app.include_router(lenses_router, prefix=f'{settings.APP_PREFIX}/lenses', tags=['lenses'])
|
||||
|
||||
@ -8,6 +8,10 @@ class Settings(BaseSettings):
|
||||
SECRET_KEY: str
|
||||
ALGORITHM: str
|
||||
APP_PREFIX: str = '/api/v1'
|
||||
FILE_UPLOAD_DIR: str = 'uploads'
|
||||
BACKUP_DIR: str = 'backups'
|
||||
BACKUP_DB_URL: str
|
||||
PG_DUMP_PATH: str
|
||||
|
||||
class Config:
|
||||
env_file = '.env'
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
import { logout } from '../Redux/Slices/authSlice.js';
|
||||
import {fetchBaseQuery} from '@reduxjs/toolkit/query/react';
|
||||
import {logout} from '../Redux/Slices/authSlice.js';
|
||||
import CONFIG from "../Core/сonfig.js";
|
||||
|
||||
export const baseQuery = fetchBaseQuery({
|
||||
baseUrl: CONFIG.BASE_URL,
|
||||
prepareHeaders: (headers, { getState, endpoint }) => {
|
||||
prepareHeaders: (headers, {getState, endpoint}) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (token) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
@ -25,7 +25,7 @@ export const baseQuery = fetchBaseQuery({
|
||||
|
||||
export const baseQueryWithAuth = async (args, api, extraOptions) => {
|
||||
const result = await baseQuery(args, api, extraOptions);
|
||||
if (result.error && result.error.status === 401) {
|
||||
if (result.error && [401, 403].includes(result.error.status)) {
|
||||
localStorage.removeItem('access_token');
|
||||
api.dispatch(logout());
|
||||
window.location.href = '/login';
|
||||
|
||||
@ -1,101 +1,43 @@
|
||||
import {Table, Button, Result, Typography, Switch, Input, Tooltip, FloatButton} from "antd";
|
||||
import {ControlOutlined, PlusOutlined} from "@ant-design/icons";
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import {Tabs, Typography} from "antd";
|
||||
import {
|
||||
ContainerOutlined,
|
||||
ControlOutlined,
|
||||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import useAdminPage from "./useAdminPage.js";
|
||||
import CreateUserModalForm from "./Components/CreateUserModalForm/CreateUserModalForm.jsx";
|
||||
import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||
import UsersManageTab from "./Components/UsersManageTab/UsersManageTab.jsx";
|
||||
import BackupManageTab from "./Components/BackupManageTab/BackupManageTab.jsx";
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: '1',
|
||||
label: 'Пользователи',
|
||||
children: <UsersManageTab/>,
|
||||
icon: <UserOutlined/>
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Резервное копирование и восстановление',
|
||||
children: <BackupManageTab/>,
|
||||
icon: <ContainerOutlined/>
|
||||
}
|
||||
]
|
||||
|
||||
const AdminPage = () => {
|
||||
const adminPageData = useAdminPage();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "ID",
|
||||
dataIndex: "id",
|
||||
key: "id",
|
||||
},
|
||||
{
|
||||
title: "Фамилия",
|
||||
dataIndex: "last_name",
|
||||
key: "lastName",
|
||||
sorter: (a, b) => a.last_name.localeCompare(b.last_name),
|
||||
},
|
||||
{
|
||||
title: "Имя",
|
||||
dataIndex: "first_name",
|
||||
key: "firstName",
|
||||
sorter: (a, b) => a.first_name.localeCompare(b.first_name),
|
||||
},
|
||||
{
|
||||
title: "Отчество",
|
||||
dataIndex: "patronymic",
|
||||
key: "patronymic",
|
||||
},
|
||||
{
|
||||
title: "Роль",
|
||||
dataIndex: ["role", "title"],
|
||||
key: "role",
|
||||
filters: adminPageData.roles.map(role => ({text: role.title, value: role.title})),
|
||||
onFilter: (value, record) => record.role.title === value,
|
||||
},
|
||||
{
|
||||
title: "Заблокирован",
|
||||
dataIndex: "is_blocked",
|
||||
key: "is_blocked",
|
||||
render: (value, record) => (
|
||||
<Switch
|
||||
loading={adminPageData.isBlocking}
|
||||
checked={value}
|
||||
checkedChildren="Заблокирован"
|
||||
unCheckedChildren="Не заблокирован"
|
||||
onChange={(isBlocked) => adminPageData.setIsBlockUser(record.id, isBlocked)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Действия",
|
||||
key: "actions",
|
||||
render: (_, record) => (
|
||||
<Button type="link" onClick={() => adminPageData.openEditModal(record)}>
|
||||
Редактировать
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (adminPageData.isError) {
|
||||
return <Result status="500" title="500" subTitle="Произошла ошибка при загрузке данных"/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={adminPageData.containerStyle}>
|
||||
{adminPageData.isLoading ? (
|
||||
<LoadingIndicator/>
|
||||
) : (
|
||||
<>
|
||||
<Typography.Title level={1}>
|
||||
<ControlOutlined/> Панель администратора
|
||||
</Typography.Title>
|
||||
<Input
|
||||
placeholder="Введите фамилию, имя или отчество"
|
||||
style={{marginBottom: 12}}
|
||||
/>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={adminPageData.users}
|
||||
rowKey="id"
|
||||
pagination={{pageSize: 10}}
|
||||
loading={adminPageData.isLoading}
|
||||
/>
|
||||
|
||||
<Tooltip title="Добавить пользователя">
|
||||
<FloatButton onClick={adminPageData.openCreateModal} icon={<PlusOutlined/>} type={"primary"}/>
|
||||
</Tooltip>
|
||||
|
||||
<CreateUserModalForm/>
|
||||
<UpdateUserModalForm/>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<Typography.Title level={1}>
|
||||
<ControlOutlined/> Панель администратора
|
||||
</Typography.Title>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
items={items}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
import {Button, Space, Spin, Typography, Upload} from "antd";
|
||||
import {CloudDownloadOutlined, UploadOutlined} from "@ant-design/icons";
|
||||
|
||||
const BackupManageTab = () => {
|
||||
return (
|
||||
<Spin spinning={false}>
|
||||
<Typography>
|
||||
<Typography.Title level={4}>Управление резервными копиями</Typography.Title>
|
||||
<Typography.Paragraph>
|
||||
Здесь вы можете создать резервную копию системы и восстановить её из архива.
|
||||
</Typography.Paragraph>
|
||||
</Typography>
|
||||
<Space direction="vertical" size="large" style={{width: "100%"}}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CloudDownloadOutlined/>}
|
||||
// onClick={handleCreateBackup}
|
||||
// disabled={loading}
|
||||
block
|
||||
>
|
||||
Создать и скачать бэкап
|
||||
</Button>
|
||||
<Upload
|
||||
// beforeUpload={handleUpload}
|
||||
showUploadList={false}
|
||||
accept=".tar.gz,.zip"
|
||||
// disabled={loading}
|
||||
>
|
||||
<Button icon={<UploadOutlined/>} block>
|
||||
Загрузить бэкап для восстановления
|
||||
</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
</Spin>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackupManageTab;
|
||||
@ -0,0 +1,99 @@
|
||||
import {Button, FloatButton, Input, Result, Switch, Table, Tooltip} from "antd";
|
||||
import CreateUserModalForm from "../CreateUserModalForm/CreateUserModalForm.jsx";
|
||||
import UpdateUserModalForm from "../../../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||
import useUsersManageTab from "./useUsersManageTab.js";
|
||||
import {PlusOutlined} from "@ant-design/icons";
|
||||
import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
|
||||
const UsersManageTab = () => {
|
||||
const usersManageTabData = useUsersManageTab();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "ID",
|
||||
dataIndex: "id",
|
||||
key: "id",
|
||||
},
|
||||
{
|
||||
title: "Фамилия",
|
||||
dataIndex: "last_name",
|
||||
key: "lastName",
|
||||
sorter: (a, b) => a.last_name.localeCompare(b.last_name),
|
||||
},
|
||||
{
|
||||
title: "Имя",
|
||||
dataIndex: "first_name",
|
||||
key: "firstName",
|
||||
sorter: (a, b) => a.first_name.localeCompare(b.first_name),
|
||||
},
|
||||
{
|
||||
title: "Отчество",
|
||||
dataIndex: "patronymic",
|
||||
key: "patronymic",
|
||||
},
|
||||
{
|
||||
title: "Роль",
|
||||
dataIndex: ["role", "title"],
|
||||
key: "role",
|
||||
filters: usersManageTabData.roles.map(role => ({text: role.title, value: role.title})),
|
||||
onFilter: (value, record) => record.role.title === value,
|
||||
},
|
||||
{
|
||||
title: "Заблокирован",
|
||||
dataIndex: "is_blocked",
|
||||
key: "is_blocked",
|
||||
render: (value, record) => (
|
||||
<Switch
|
||||
loading={usersManageTabData.isBlocking}
|
||||
checked={value}
|
||||
checkedChildren="Заблокирован"
|
||||
unCheckedChildren="Не заблокирован"
|
||||
onChange={(isBlocked) => usersManageTabData.setIsBlockUser(record.id, isBlocked)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Действия",
|
||||
key: "actions",
|
||||
render: (_, record) => (
|
||||
<Button type="link" onClick={() => usersManageTabData.openEditModal(record)}>
|
||||
Редактировать
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (usersManageTabData.isError) {
|
||||
return <Result status="500" title="500" subTitle="Произошла ошибка при загрузке данных"/>;
|
||||
}
|
||||
|
||||
if (usersManageTabData.isLoading) {
|
||||
return <LoadingIndicator/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Введите фамилию, имя или отчество"
|
||||
style={{marginBottom: 12}}
|
||||
allowClear
|
||||
onChange={usersManageTabData.handleSearch}
|
||||
/>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={usersManageTabData.filteredUsers}
|
||||
rowKey="id"
|
||||
pagination={{pageSize: 10}}
|
||||
/>
|
||||
|
||||
<Tooltip title="Добавить пользователя">
|
||||
<FloatButton onClick={usersManageTabData.openCreateModal} icon={<PlusOutlined/>} type={"primary"}/>
|
||||
</Tooltip>
|
||||
|
||||
<CreateUserModalForm/>
|
||||
<UpdateUserModalForm/>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default UsersManageTab;
|
||||
@ -0,0 +1,93 @@
|
||||
import {useGetAllUsersQuery, useSetIsBlockedMutation} from "../../../../../Api/usersApi.js";
|
||||
import {useGetRolesQuery} from "../../../../../Api/rolesApi.js";
|
||||
import {Grid, notification} from "antd";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {setIsCurrentUser, setSelectedUser} from "../../../../../Redux/Slices/usersSlice.js";
|
||||
import {openModal} from "../../../../../Redux/Slices/adminSlice.js";
|
||||
import {useMemo, useState} from "react";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const useUsersManageTab = () => {
|
||||
const dispatch = useDispatch();
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const [searchString, setSearchString] = useState("");
|
||||
|
||||
const {
|
||||
data: users = [], isLoading, isError,
|
||||
} = useGetAllUsersQuery(undefined, {
|
||||
pollingInterval: 10000,
|
||||
});
|
||||
|
||||
const {data: roles = [], isLoading: isLoadingRoles, isError: isErrorRoles} = useGetRolesQuery(undefined, {
|
||||
pollingInterval: 60000,
|
||||
});
|
||||
const [setIsBlocked, {isLoading: isBlocking, isError: isBlockError}] = useSetIsBlockedMutation();
|
||||
|
||||
const containerStyle = {padding: screens.xs ? 16 : 24};
|
||||
|
||||
const openEditModal = (user) => {
|
||||
dispatch(setSelectedUser(user));
|
||||
dispatch(setIsCurrentUser(true));
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
dispatch(openModal());
|
||||
};
|
||||
|
||||
const handleSearch = (e) => {
|
||||
setSearchString(e.target.value);
|
||||
};
|
||||
|
||||
const setIsBlockUser = async (user, isBlock) => {
|
||||
try {
|
||||
await setIsBlocked({userId: user, isBlocked: isBlock}).unwrap();
|
||||
|
||||
notification.success({
|
||||
message: "Успех",
|
||||
description: isBlock
|
||||
? "Пользователь успешно заблокирован"
|
||||
: "Пользователь успешно разблокирован",
|
||||
placement: "topRight",
|
||||
})
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: "Ошибка",
|
||||
description: error?.data?.detail ? error?.data?.detail : isBlock
|
||||
? "Не удалось заблокировать пользователя"
|
||||
: "Не удалось разблокировать пользователя",
|
||||
placement: "topRight",
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const filteredUsers = useMemo(() => {
|
||||
return users.filter((user) => {
|
||||
return Object.entries(user).some(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return value.toString().toLowerCase().includes(searchString.toLowerCase());
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [users, searchString]);
|
||||
|
||||
return {
|
||||
roles,
|
||||
isBlocking,
|
||||
filteredUsers,
|
||||
isLoading: isLoading || isLoadingRoles,
|
||||
isError: isError || isErrorRoles,
|
||||
isUpdating: false,
|
||||
isUpdateError: false,
|
||||
isCreating: false,
|
||||
isCreateError: false,
|
||||
containerStyle,
|
||||
openEditModal,
|
||||
openCreateModal,
|
||||
setIsBlockUser,
|
||||
handleSearch,
|
||||
};
|
||||
};
|
||||
|
||||
export default useUsersManageTab;
|
||||
@ -1,74 +1,15 @@
|
||||
import {Grid, notification} from "antd";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {openModal} from "../../../Redux/Slices/adminSlice.js";
|
||||
import {setIsCurrentUser, setSelectedUser} from "../../../Redux/Slices/usersSlice.js";
|
||||
import {useGetAllUsersQuery, useSetIsBlockedMutation} from "../../../Api/usersApi.js";
|
||||
import {useGetRolesQuery} from "../../../Api/rolesApi.js";
|
||||
import {Grid} from "antd";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const useAdminPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const {
|
||||
data: users = [], isLoading, isError,
|
||||
} = useGetAllUsersQuery(undefined, {
|
||||
pollingInterval: 10000,
|
||||
});
|
||||
|
||||
const {data: roles = [], isLoading: isLoadingRoles, isError: isErrorRoles} = useGetRolesQuery(undefined, {
|
||||
pollingInterval: 60000,
|
||||
});
|
||||
const [setIsBlocked, {isLoading: isBlocking, isError: isBlockError}] = useSetIsBlockedMutation();
|
||||
|
||||
const containerStyle = {padding: screens.xs ? 16 : 24};
|
||||
|
||||
const openEditModal = (user) => {
|
||||
dispatch(setSelectedUser(user));
|
||||
dispatch(setIsCurrentUser(true));
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
dispatch(openModal());
|
||||
};
|
||||
|
||||
const setIsBlockUser = async (user, isBlock) => {
|
||||
try {
|
||||
await setIsBlocked({userId: user, isBlocked: isBlock}).unwrap();
|
||||
|
||||
notification.success({
|
||||
message: "Успех",
|
||||
description: isBlock
|
||||
? "Пользователь успешно заблокирован"
|
||||
: "Пользователь успешно разблокирован",
|
||||
placement: "topRight",
|
||||
})
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: "Ошибка",
|
||||
description: error?.data?.detail ? error?.data?.detail : isBlock
|
||||
? "Не удалось заблокировать пользователя"
|
||||
: "Не удалось разблокировать пользователя",
|
||||
placement: "topRight",
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
users,
|
||||
roles,
|
||||
isBlocking,
|
||||
isLoading: isLoading || isLoadingRoles,
|
||||
isError: isError || isErrorRoles,
|
||||
isUpdating: false,
|
||||
isUpdateError: false,
|
||||
isCreating: false,
|
||||
isCreateError: false,
|
||||
containerStyle,
|
||||
openEditModal,
|
||||
openCreateModal,
|
||||
setIsBlockUser,
|
||||
containerStyle
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user