diff --git a/HOOKS_USAGE_GUIDE.md b/HOOKS_USAGE_GUIDE.md new file mode 100644 index 0000000..59efbdd --- /dev/null +++ b/HOOKS_USAGE_GUIDE.md @@ -0,0 +1,341 @@ +# TanStack Query Hooks Documentation + +Tất cả các API đã được tách riêng thành TanStack Query hooks trong folder `src/hooks/queries/`. + +## Cấu trúc + +``` +src/hooks/queries/ + ├── index.ts # Export tất cả hooks + ├── useAuthQueries.ts # Auth hooks + ├── useAppVersionQueries.ts # App/Software hooks + ├── useDeviceCommQueries.ts # Device communication hooks + └── useCommandQueries.ts # Command hooks +``` + +## Cách Sử Dụng + +### 1. Auth Queries (Xác thực) + +#### Đăng nhập +```tsx +import { useLogin } from '@/hooks/queries' + +function LoginPage() { + const loginMutation = useLogin() + + const handleLogin = async () => { + try { + await loginMutation.mutateAsync({ + username: 'user', + password: 'password' + }) + // Tự động lưu token vào localStorage + } catch (error) { + console.error(error) + } + } + + return ( + + ) +} +``` + +#### Đăng xuất +```tsx +import { useLogout } from '@/hooks/queries' + +function LogoutButton() { + const logoutMutation = useLogout() + + return ( + + ) +} +``` + +#### Kiểm tra phiên +```tsx +import { usePing } from '@/hooks/queries' + +function CheckSession() { + const { data, isLoading } = usePing(token, true) + + if (isLoading) return
Checking...
+ return
Session: {data?.message}
+} +``` + +#### Thay đổi mật khẩu +```tsx +import { useChangePassword } from '@/hooks/queries' + +function ChangePasswordForm() { + const changePasswordMutation = useChangePassword() + + const handleSubmit = async () => { + await changePasswordMutation.mutateAsync({ + currentPassword: 'old', + newPassword: 'new' + }) + } + + return +} +``` + +### 2. App Version Queries (Phần mềm/Agent) + +#### Lấy danh sách agent +```tsx +import { useGetAgentVersion } from '@/hooks/queries' + +function AgentList() { + const { data: agents, isLoading } = useGetAgentVersion() + + if (isLoading) return
Loading...
+ return
{agents?.length} agents
+} +``` + +#### Lấy danh sách phần mềm +```tsx +import { useGetSoftwareList } from '@/hooks/queries' + +function SoftwareList() { + const { data: software, isLoading } = useGetSoftwareList() + + return software?.map(item =>
{item.name}
) +} +``` + +#### Upload file +```tsx +import { useUploadSoftware } from '@/hooks/queries' + +function UploadForm() { + const uploadMutation = useUploadSoftware() + + const handleUpload = async (file: File) => { + const formData = new FormData() + formData.append('file', file) + + await uploadMutation.mutateAsync({ + formData, + onUploadProgress: (event) => { + const percent = (event.loaded / event.total) * 100 + console.log(`Upload: ${percent}%`) + } + }) + } + + return e.target.files && handleUpload(e.target.files[0])} /> +} +``` + +#### Quản lý blacklist +```tsx +import { + useGetBlacklist, + useAddBlacklist, + useDeleteBlacklist +} from '@/hooks/queries' + +function BlacklistManager() { + const { data: blacklist } = useGetBlacklist() + const addMutation = useAddBlacklist() + const deleteMutation = useDeleteBlacklist() + + const handleAdd = async () => { + await addMutation.mutateAsync({ appId: 1 }) + } + + const handleDelete = async (appId: number) => { + await deleteMutation.mutateAsync(appId) + } + + return ( + <> + {blacklist?.map(item => ( +
+ {item.name} + +
+ ))} + + + ) +} +``` + +### 3. Device Communication Queries + +#### Lấy danh sách phòng +```tsx +import { useGetRoomList } from '@/hooks/queries' + +function RoomSelector() { + const { data: rooms } = useGetRoomList() + + return ( + + ) +} +``` + +#### Lấy thiết bị trong phòng +```tsx +import { useGetDeviceFromRoom } from '@/hooks/queries' + +function DeviceList({ roomName }: { roomName: string }) { + const { data: devices, isLoading } = useGetDeviceFromRoom(roomName, true) + + if (isLoading) return
Loading devices...
+ + return devices?.map(device => ( +
{device.name}
+ )) +} +``` + +#### Gửi lệnh +```tsx +import { useSendCommand } from '@/hooks/queries' + +function CommandForm() { + const sendMutation = useSendCommand() + + const handleSend = async () => { + await sendMutation.mutateAsync({ + roomName: 'Room A', + data: { command: 'dir' } + }) + } + + return +} +``` + +#### Cài đặt phần mềm +```tsx +import { useInstallMsi } from '@/hooks/queries' + +function InstallSoftware() { + const installMutation = useInstallMsi() + + const handleInstall = async () => { + await installMutation.mutateAsync({ + roomName: 'Room A', + data: { msiFileId: 1 } + }) + } + + return +} +``` + +### 4. Command Queries + +#### Lấy danh sách lệnh +```tsx +import { useGetCommandList } from '@/hooks/queries' + +function CommandList() { + const { data: commands } = useGetCommandList() + + return commands?.map(cmd =>
{cmd.name}
) +} +``` + +#### Thêm lệnh +```tsx +import { useAddCommand } from '@/hooks/queries' + +function AddCommandForm() { + const addMutation = useAddCommand() + + const handleAdd = async () => { + await addMutation.mutateAsync({ + name: 'My Command', + command: 'echo hello' + }) + } + + return +} +``` + +## Lợi ích + +1. **Automatic Caching** - TanStack Query tự động cache dữ liệu +2. **Background Refetching** - Cập nhật dữ liệu trong background +3. **Stale Time Management** - Kiểm soát thời gian dữ liệu còn "fresh" +4. **Automatic Invalidation** - Tự động update dữ liệu sau mutations +5. **Deduplication** - Gộp các request giống nhau +6. **Error Handling** - Xử lý lỗi tập trung +7. **Loading States** - Tracking loading/pending/error states + +## Advanced Usage + +### Dependent Queries +```tsx +function DeviceDetails({ deviceId }: { deviceId: number }) { + const { data: device } = useGetDeviceFromRoom(deviceId, true) + + // Chỉ fetch khi có device + const { data: status } = useGetClientFolderStatus( + device?.roomName, + !!device + ) + + return
{status?.path}
+} +``` + +### Prefetching +```tsx +import { useQueryClient } from '@tanstack/react-query' +import { useGetSoftwareList } from '@/hooks/queries' + +function PrefetchOnHover() { + const queryClient = useQueryClient() + + const handleMouseEnter = () => { + queryClient.prefetchQuery({ + queryKey: ['app-version', 'software'], + queryFn: () => useGetSoftwareList + }) + } + + return
Hover me
+} +``` + +## Migration từ cách cũ + +**Trước:** +```tsx +const { data } = useQueryData({ + queryKey: ["software-version"], + url: API_ENDPOINTS.APP_VERSION.GET_SOFTWARE, +}) +``` + +**Sau:** +```tsx +const { data } = useGetSoftwareList() +``` + +Đơn giản hơn, type-safe hơn, và dễ bảo trì hơn! diff --git a/SERVICES_GUIDE.md b/SERVICES_GUIDE.md new file mode 100644 index 0000000..5b41a17 --- /dev/null +++ b/SERVICES_GUIDE.md @@ -0,0 +1,174 @@ +# API Services Documentation + +Tất cả logic gọi API đã được tách riêng vào folder `services`. Mỗi service tương ứng với một nhóm API. + +## Cấu trúc Services + +``` +src/services/ + ├── index.ts # Export tất cả services + ├── auth.service.ts # API xác thực + ├── app-version.service.ts # API quản lý phần mềm + ├── device-comm.service.ts # API thiết bị + ├── command.service.ts # API lệnh + └── device.service.ts # Helper functions +``` + +## Cách Sử Dụng + +### 1. Auth Service (Xác thực) + +```tsx +import { authService } from '@/services' + +// Đăng nhập +const response = await authService.login({ + username: 'user', + password: 'pass' +}) + +// Đăng xuất +await authService.logout() + +// Kiểm tra session +const pingResult = await authService.ping(token) + +// Thay đổi mật khẩu +await authService.changePassword({ + currentPassword: 'old', + newPassword: 'new' +}) + +// Tạo tài khoản mới (admin) +await authService.createAccount({ + userName: 'newuser', + password: 'pass', + name: 'John Doe', + roleId: 1, + accessBuildings: [1, 2, 3] +}) +``` + +### 2. App Version Service (Quản lý phần mềm) + +```tsx +import { appVersionService } from '@/services' + +// Lấy danh sách agent +const agents = await appVersionService.getAgentVersion() + +// Lấy danh sách phần mềm +const software = await appVersionService.getSoftwareList() + +// Upload file +const formData = new FormData() +formData.append('file', fileInput.files[0]) +await appVersionService.uploadSoftware(formData, (progressEvent) => { + console.log(`Progress: ${progressEvent.loaded}/${progressEvent.total}`) +}) + +// Lấy blacklist +const blacklist = await appVersionService.getBlacklist() + +// Thêm vào blacklist +await appVersionService.addBlacklist({ appId: 1, reason: 'virus' }) + +// Xóa khỏi blacklist +await appVersionService.deleteBlacklist(1) +``` + +### 3. Device Comm Service (Thiết bị) + +```tsx +import { deviceCommService } from '@/services' + +// Lấy tất cả thiết bị +const allDevices = await deviceCommService.getAllDevices() + +// Lấy danh sách phòng +const rooms = await deviceCommService.getRoomList() + +// Lấy thiết bị trong phòng +const devices = await deviceCommService.getDeviceFromRoom('Room A') + +// Gửi lệnh +await deviceCommService.sendCommand('Room A', { + command: 'dir' +}) + +// Cập nhật agent +await deviceCommService.updateAgent('Room A', { version: '1.0.0' }) + +// Cài đặt MSI +await deviceCommService.installMsi('Room A', { msiFileId: 1 }) +``` + +### 4. Command Service (Lệnh) + +```tsx +import { commandService } from '@/services' + +// Lấy danh sách lệnh +const commands = await commandService.getCommandList() + +// Thêm lệnh +await commandService.addCommand({ name: 'cmd1', command: 'echo hello' }) + +// Cập nhật lệnh +await commandService.updateCommand(1, { name: 'cmd1 updated' }) + +// Xóa lệnh +await commandService.deleteCommand(1) +``` + +## Sử dụng với React Query/Hooks + +### Cách cũ (trực tiếp gọi từ component): +```tsx +const { data } = useQueryData({ + queryKey: ["software-version"], + url: API_ENDPOINTS.APP_VERSION.GET_SOFTWARE, +}) +``` + +### Cách mới (tách biệt logic): + +Có thể tạo custom hooks bao quanh services: + +```tsx +// hooks/useGetSoftware.ts +import { useQuery } from '@tanstack/react-query' +import { appVersionService } from '@/services' + +export function useGetSoftware() { + return useQuery({ + queryKey: ['software-version'], + queryFn: () => appVersionService.getSoftwareList(), + staleTime: 60 * 1000, + }) +} +``` + +Sau đó sử dụng trong component: +```tsx +function AppsComponent() { + const { data, isLoading } = useGetSoftware() + // ... +} +``` + +## Lợi ích của cách sử dụng mới + +1. **Tách biệt logic** - Logic API nằm riêng, dễ bảo trì +2. **Tái sử dụng** - Có thể sử dụng service từ bất kỳ nơi +3. **Dễ test** - Dễ mock services khi viết unit tests +4. **Centralized error handling** - Có thể xử lý lỗi chung +5. **Type safety** - TypeScript types cho tất cả API requests/responses + +## Cải tiến trong tương lai + +Có thể thêm: +- Global error handling middleware trong axios +- Request/response interceptors cho authentication +- Retry logic cho failed requests +- Request cancellation diff --git a/SERVICES_VS_HOOKS.md b/SERVICES_VS_HOOKS.md new file mode 100644 index 0000000..b5b03cf --- /dev/null +++ b/SERVICES_VS_HOOKS.md @@ -0,0 +1,328 @@ +# Khác biệt giữa Services và Query Hooks + +## Tóm tắt nhanh + +| Aspect | Services | Query Hooks | +|--------|----------|-------------| +| **Location** | `src/services/` | `src/hooks/queries/` | +| **Mục đích** | Gọi API trực tiếp | Wrapper TanStack Query | +| **Caching** | ❌ Không | ✅ Có | +| **Background Refetch** | ❌ Không | ✅ Có | +| **Auto Invalidation** | ❌ Không | ✅ Có | +| **Type** | Async functions | React Hooks | +| **Dùng trong** | Non-React code, utilities | React components | + +--- + +## Chi tiết Từng Layer + +### 1. Services Layer (`src/services/`) + +**Mục đích:** Đơn thuần gọi API và trả về dữ liệu + +```typescript +// app-version.service.ts +export async function getSoftwareList(): Promise { + const response = await axios.get( + API_ENDPOINTS.APP_VERSION.GET_SOFTWARE + ); + return response.data; +} +``` + +**Đặc điểm:** +- ✅ Pure async functions +- ✅ Không phụ thuộc vào React +- ✅ Có thể sử dụng ở bất kỳ đâu (utils, servers, non-React code) +- ❌ Không có caching +- ❌ Phải tự quản lý state loading/error +- ❌ Phải tự gọi lại khi dữ liệu thay đổi + +**Khi nào dùng:** +```typescript +// Dùng trong utility functions +export async function initializeApp() { + const software = await appVersionService.getSoftwareList(); + // ... +} + +// Hoặc trong services khác +export async function validateSoftware() { + const list = await appVersionService.getSoftwareList(); + // ... +} +``` + +--- + +### 2. Query Hooks Layer (`src/hooks/queries/`) + +**Mục đích:** Wrapper TanStack Query bên trên services + +```typescript +// useAppVersionQueries.ts +export function useGetSoftwareList(enabled = true) { + return useQuery({ + queryKey: ["app-version", "software"], + queryFn: () => appVersionService.getSoftwareList(), + enabled, + staleTime: 60 * 1000, // 1 minute + }); +} +``` + +**Đặc điểm:** +- ✅ React hooks +- ✅ Automatic caching +- ✅ Background refetching +- ✅ Automatic invalidation sau mutations +- ✅ Built-in loading/error states +- ✅ Deduplication (gộp requests giống nhau) +- ❌ Chỉ dùng được trong React components +- ❌ Phức tạp hơn services + +**Khi nào dùng:** +```typescript +// Dùng trong React components +function SoftwareList() { + const { data: software, isLoading } = useGetSoftwareList() + + if (isLoading) return
Loading...
+ return software?.map(item =>
{item.name}
) +} +``` + +--- + +## So sánh cụ thể + +### Ví dụ 1: Lấy danh sách + +**Service - Raw API call:** +```typescript +// services/app-version.service.ts +export async function getSoftwareList(): Promise { + const response = await axios.get(API_ENDPOINTS.APP_VERSION.GET_SOFTWARE); + return response.data; +} +``` + +**Hook - TanStack Query wrapper:** +```typescript +// hooks/queries/useAppVersionQueries.ts +export function useGetSoftwareList(enabled = true) { + return useQuery({ + queryKey: ["app-version", "software"], + queryFn: () => appVersionService.getSoftwareList(), + staleTime: 60 * 1000, + }); +} +``` + +**Sử dụng trong component:** +```typescript +function Component() { + // ❌ KHÔNG nên dùng service trực tiếp + const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + useEffect(() => { + appVersionService.getSoftwareList().then(d => { + setData(d); + setLoading(false); + }); + }, []); + + // ✅ NÊN dùng hook thay vì + const { data, isLoading } = useGetSoftwareList(); +} +``` + +--- + +### Ví dụ 2: Upload file + +**Service:** +```typescript +// services/app-version.service.ts +export async function uploadSoftware( + formData: FormData, + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void +): Promise<{ message: string }> { + return axios.post(API_ENDPOINTS.APP_VERSION.UPLOAD, formData, { + onUploadProgress, + }); +} +``` + +**Hook:** +```typescript +// hooks/queries/useAppVersionQueries.ts +export function useUploadSoftware() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: { formData: FormData; onUploadProgress?: ... }) => + appVersionService.uploadSoftware(data.formData, data.onUploadProgress), + onSuccess: () => { + // Tự động invalidate software list + queryClient.invalidateQueries({ + queryKey: ["app-version", "software"], + }); + }, + }); +} +``` + +**Sử dụng:** +```typescript +function UploadForm() { + const uploadMutation = useUploadSoftware(); + + const handleUpload = async (file: File) => { + const formData = new FormData(); + formData.append("file", file); + + await uploadMutation.mutateAsync({ + formData, + onUploadProgress: (e) => console.log(`${e.loaded}/${e.total}`) + }); + + // ✅ Software list tự động update + }; + + return ( + + ); +} +``` + +--- + +## Architecture Flow + +``` +┌─────────────────────────────────────────┐ +│ React Component │ +│ (SoftwareList, UploadForm, etc.) │ +└──────────────┬──────────────────────────┘ + │ + │ uses + ▼ +┌─────────────────────────────────────────┐ +│ Query Hooks (src/hooks/queries/) │ +│ - useGetSoftwareList() │ +│ - useUploadSoftware() │ +│ - useDeleteBlacklist() │ +│ - Features: │ +│ - Caching │ +│ - Auto invalidation │ +│ - Loading states │ +└──────────────┬──────────────────────────┘ + │ + │ wraps + ▼ +┌─────────────────────────────────────────┐ +│ Service Functions (src/services/) │ +│ - getSoftwareList() │ +│ - uploadSoftware() │ +│ - deleteBlacklist() │ +│ - Features: │ +│ - Pure async functions │ +│ - Direct API calls │ +└──────────────┬──────────────────────────┘ + │ + │ uses + ▼ +┌─────────────────────────────────────────┐ +│ Axios (HTTP Client) │ +└──────────────┬──────────────────────────┘ + │ + │ requests + ▼ +┌─────────────────────────────────────────┐ +│ Backend API Server │ +└─────────────────────────────────────────┘ +``` + +--- + +## Nguyên tắc sử dụng + +### ✅ NÊN dùng Services khi: +- Gọi API từ non-React code (utilities, event handlers, etc.) +- Cần gọi API một lần rồi không cần tracking state +- Không cần caching hay background refetch +- Viết code không phụ thuộc React + +```typescript +// ✅ OK - utility function +export async function syncData() { + const software = await appVersionService.getSoftwareList(); + const commands = await commandService.getCommandList(); + return { software, commands }; +} +``` + +### ✅ NÊN dùng Hooks khi: +- Lấy/update dữ liệu trong React components +- Cần caching và background refetch +- Muốn dữ liệu tự động update +- Cần tracking loading/error states + +```typescript +// ✅ OK - React component +function Dashboard() { + const { data: software, isLoading } = useGetSoftwareList(); + const uploadMutation = useUploadSoftware(); + + return
{/* ... */}
; +} +``` + +### ❌ KHÔNG nên dùng Services khi: +- Đang trong React component và cần state management +- Cần automatic refetching +- Cần auto-invalidation sau mutations + +```typescript +// ❌ WRONG +function Component() { + const [data, setData] = useState([]); + useEffect(() => { + appVersionService.getSoftwareList().then(setData); + }, []); +} + +// ✅ RIGHT +function Component() { + const { data } = useGetSoftwareList(); +} +``` + +### ❌ KHÔNG nên dùng Hooks khi: +- Không phải trong React component +- Không có React context + +```typescript +// ❌ WRONG - không thể gọi hooks ở đây +export function initApp() { + const { data } = useGetSoftwareList(); // ERROR! +} + +// ✅ RIGHT +export async function initApp() { + const data = await appVersionService.getSoftwareList(); +} +``` + +--- + +## Summary + +**Services** = Cơ sở API calls, có thể tái sử dụng ở bất kỳ đâu +**Hooks** = Lớp React trên services, tối ưu cho React components + +**Dùng Services** khi bạn cần tính linh hoạt và độc lập với React +**Dùng Hooks** khi bạn muốn TanStack Query quản lý state và caching tự động diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/components/pages/error-route.tsx b/src/components/pages/error-route.tsx new file mode 100644 index 0000000..fc27d39 --- /dev/null +++ b/src/components/pages/error-route.tsx @@ -0,0 +1,24 @@ +import { AlertTriangle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Link } from "@tanstack/react-router"; + +export default function ErrorRoute({ error }: { error: string }) { + return ( +
+
+ +
+ +

Lỗi

+

+ Đã xảy ra lỗi: {error} +

+ +
+ +
+
+ ); +} diff --git a/src/components/pages/not-found.tsx b/src/components/pages/not-found.tsx new file mode 100644 index 0000000..bf8a62a --- /dev/null +++ b/src/components/pages/not-found.tsx @@ -0,0 +1,35 @@ +import { Button } from "@/components/ui/button"; +import { Link } from "@tanstack/react-router"; +import { ArrowLeft, Search } from "lucide-react"; + +export default function NotFound() { + return ( +
+
+
+
+
+ +
+
+ +
+

404

+

Không tìm thấy

+

+ Trang bạn yêu cầu truy cập không tồn tại hoặc đã bị xoá. +

+
+ +
+ +
+
+
+ ); +} diff --git a/src/config/api.ts b/src/config/api.ts index 5f38b58..e3fac17 100644 --- a/src/config/api.ts +++ b/src/config/api.ts @@ -5,6 +5,15 @@ export const BASE_URL = isDev : "/api"; export const API_ENDPOINTS = { + AUTH: { + LOGIN: `${BASE_URL}/login`, + LOGOUT: `${BASE_URL}/logout`, + CHANGE_PASSWORD: `${BASE_URL}/auth/change-password`, + CHANGE_PASSWORD_ADMIN: `${BASE_URL}/auth/admin/change-password`, + PING: `${BASE_URL}/ping`, + CSRF_TOKEN: `${BASE_URL}/csrf-token`, + CREATE_ACCOUNT: `${BASE_URL}/auth/create-account`, + }, APP_VERSION: { //agent and app api GET_VERSION: `${BASE_URL}/AppVersion/version`, diff --git a/src/hooks/queries/index.ts b/src/hooks/queries/index.ts new file mode 100644 index 0000000..222c0e1 --- /dev/null +++ b/src/hooks/queries/index.ts @@ -0,0 +1,11 @@ +// Auth Queries +export * from "./useAuthQueries"; + +// App Version Queries +export * from "./useAppVersionQueries"; + +// Device Communication Queries +export * from "./useDeviceCommQueries"; + +// Command Queries +export * from "./useCommandQueries"; diff --git a/src/hooks/queries/useAppVersionQueries.ts b/src/hooks/queries/useAppVersionQueries.ts new file mode 100644 index 0000000..82da2be --- /dev/null +++ b/src/hooks/queries/useAppVersionQueries.ts @@ -0,0 +1,186 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import * as appVersionService from "@/services/app-version.service"; +import type { AxiosProgressEvent } from "axios"; +import type { Version } from "@/types/file"; + +const APP_VERSION_QUERY_KEYS = { + all: ["app-version"] as const, + agentVersion: () => [...APP_VERSION_QUERY_KEYS.all, "agent"] as const, + softwareList: () => [...APP_VERSION_QUERY_KEYS.all, "software"] as const, + blacklist: () => [...APP_VERSION_QUERY_KEYS.all, "blacklist"] as const, + requiredFiles: () => [...APP_VERSION_QUERY_KEYS.all, "required-files"] as const, +}; + +/** + * Hook để lấy danh sách phiên bản agent + */ +export function useGetAgentVersion(enabled = true) { + return useQuery({ + queryKey: APP_VERSION_QUERY_KEYS.agentVersion(), + queryFn: () => appVersionService.getAgentVersion(), + enabled, + staleTime: 60 * 1000, // 1 minute + }); +} + +/** + * Hook để lấy danh sách phần mềm + */ +export function useGetSoftwareList(enabled = true) { + return useQuery({ + queryKey: APP_VERSION_QUERY_KEYS.softwareList(), + queryFn: () => appVersionService.getSoftwareList(), + enabled, + staleTime: 60 * 1000, + }); +} + +/** + * Hook để upload file + */ +export function useUploadSoftware() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: { + formData: FormData; + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void; + }) => appVersionService.uploadSoftware(data.formData, data.onUploadProgress), + onSuccess: () => { + // Invalidate software list + queryClient.invalidateQueries({ + queryKey: APP_VERSION_QUERY_KEYS.softwareList(), + }); + }, + }); +} + +/** + * Hook để lấy danh sách blacklist + */ +export function useGetBlacklist(enabled = true) { + return useQuery({ + queryKey: APP_VERSION_QUERY_KEYS.blacklist(), + queryFn: () => appVersionService.getBlacklist(), + enabled, + staleTime: 60 * 1000, + }); +} + +/** + * Hook để thêm vào blacklist + */ +export function useAddBlacklist() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: any) => appVersionService.addBlacklist(data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: APP_VERSION_QUERY_KEYS.blacklist(), + }); + }, + }); +} + +/** + * Hook để xóa khỏi blacklist + */ +export function useDeleteBlacklist() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (appId: number) => appVersionService.deleteBlacklist(appId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: APP_VERSION_QUERY_KEYS.blacklist(), + }); + }, + }); +} + +/** + * Hook để cập nhật blacklist + */ +export function useUpdateBlacklist() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ appId, data }: { appId: string; data: any }) => + appVersionService.updateBlacklist(appId, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: APP_VERSION_QUERY_KEYS.blacklist(), + }); + }, + }); +} + +/** + * Hook để yêu cầu cập nhật blacklist + */ +export function useRequestUpdateBlacklist() { + return useMutation({ + mutationFn: (data: any) => appVersionService.requestUpdateBlacklist(data), + }); +} + +/** + * Hook để lấy danh sách file bắt buộc + */ +export function useGetRequiredFiles(enabled = true) { + return useQuery({ + queryKey: APP_VERSION_QUERY_KEYS.requiredFiles(), + queryFn: () => appVersionService.getRequiredFiles(), + enabled, + staleTime: 60 * 1000, + }); +} + +/** + * Hook để thêm file bắt buộc + */ +export function useAddRequiredFile() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: any) => appVersionService.addRequiredFile(data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: APP_VERSION_QUERY_KEYS.requiredFiles(), + }); + }, + }); +} + +/** + * Hook để xóa file bắt buộc + */ +export function useDeleteRequiredFile() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (fileId: number) => appVersionService.deleteRequiredFile(fileId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: APP_VERSION_QUERY_KEYS.requiredFiles(), + }); + }, + }); +} + +/** + * Hook để xóa file + */ +export function useDeleteFile() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (fileId: number) => appVersionService.deleteFile(fileId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: APP_VERSION_QUERY_KEYS.softwareList(), + }); + }, + }); +} diff --git a/src/hooks/queries/useAuthQueries.ts b/src/hooks/queries/useAuthQueries.ts new file mode 100644 index 0000000..a81e519 --- /dev/null +++ b/src/hooks/queries/useAuthQueries.ts @@ -0,0 +1,120 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import * as authService from "@/services/auth.service"; +import type { LoginResquest, LoginResponse } from "@/types/auth"; + +const AUTH_QUERY_KEYS = { + all: ["auth"] as const, + ping: () => [...AUTH_QUERY_KEYS.all, "ping"] as const, + csrfToken: () => [...AUTH_QUERY_KEYS.all, "csrf-token"] as const, +}; + +/** + * Hook để đăng nhập + */ +export function useLogin() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (credentials) => authService.login(credentials), + onSuccess: (data) => { + // Lưu vào localStorage + if (data.token) { + localStorage.setItem("token", data.token); + localStorage.setItem("username", data.username || ""); + localStorage.setItem("name", data.name || ""); + localStorage.setItem("acs", JSON.stringify(data.access || [])); + localStorage.setItem("role", data.role?.roleName || ""); + localStorage.setItem("priority", String(data.role?.priority || "-1")); + } + // Invalidate ping query + queryClient.invalidateQueries({ queryKey: AUTH_QUERY_KEYS.ping() }); + }, + }); +} + +/** + * Hook để đăng xuất + */ +export function useLogout() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: () => authService.logout(), + onSuccess: () => { + localStorage.removeItem("token"); + localStorage.removeItem("username"); + localStorage.removeItem("name"); + localStorage.removeItem("acs"); + localStorage.removeItem("role"); + localStorage.removeItem("priority"); + // Clear all queries + queryClient.clear(); + }, + }); +} + +/** + * Hook để kiểm tra phiên đăng nhập + */ +export function usePing(token?: string, enabled = true) { + return useQuery({ + queryKey: AUTH_QUERY_KEYS.ping(), + queryFn: () => authService.ping(token), + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes + retry: 1, + }); +} + +/** + * Hook để lấy CSRF token + */ +export function useGetCsrfToken(enabled = true) { + return useQuery({ + queryKey: AUTH_QUERY_KEYS.csrfToken(), + queryFn: () => authService.getCsrfToken(), + enabled, + staleTime: Infinity, + }); +} + +/** + * Hook để thay đổi mật khẩu + */ +export function useChangePassword() { + return useMutation({ + mutationFn: (data: { currentPassword: string; newPassword: string }) => + authService.changePassword(data), + }); +} + +/** + * Hook để admin thay đổi mật khẩu user khác + */ +export function useChangePasswordAdmin() { + return useMutation({ + mutationFn: (data: { username: string; newPassword: string }) => + authService.changePasswordAdmin(data), + }); +} + +/** + * Hook để tạo tài khoản mới + */ +export function useCreateAccount() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: { + userName: string; + password: string; + name: string; + roleId: number; + accessBuildings?: number[]; + }) => authService.createAccount(data), + onSuccess: () => { + // Có thể invalidate user list query nếu có + queryClient.invalidateQueries({ queryKey: ["users"] }); + }, + }); +} diff --git a/src/hooks/queries/useCommandQueries.ts b/src/hooks/queries/useCommandQueries.ts new file mode 100644 index 0000000..3e14346 --- /dev/null +++ b/src/hooks/queries/useCommandQueries.ts @@ -0,0 +1,74 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import * as commandService from "@/services/command.service"; + +const COMMAND_QUERY_KEYS = { + all: ["commands"] as const, + list: () => [...COMMAND_QUERY_KEYS.all, "list"] as const, + detail: (id: number) => [...COMMAND_QUERY_KEYS.all, "detail", id] as const, +}; + +/** + * Hook để lấy danh sách lệnh + */ +export function useGetCommandList(enabled = true) { + return useQuery({ + queryKey: COMMAND_QUERY_KEYS.list(), + queryFn: () => commandService.getCommandList(), + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes + }); +} + +/** + * Hook để thêm lệnh mới + */ +export function useAddCommand() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: any) => commandService.addCommand(data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: COMMAND_QUERY_KEYS.list(), + }); + }, + }); +} + +/** + * Hook để cập nhật lệnh + */ +export function useUpdateCommand() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + commandId, + data, + }: { + commandId: number; + data: any; + }) => commandService.updateCommand(commandId, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: COMMAND_QUERY_KEYS.list(), + }); + }, + }); +} + +/** + * Hook để xóa lệnh + */ +export function useDeleteCommand() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (commandId: number) => commandService.deleteCommand(commandId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: COMMAND_QUERY_KEYS.list(), + }); + }, + }); +} diff --git a/src/hooks/queries/useDeviceCommQueries.ts b/src/hooks/queries/useDeviceCommQueries.ts new file mode 100644 index 0000000..f938f01 --- /dev/null +++ b/src/hooks/queries/useDeviceCommQueries.ts @@ -0,0 +1,172 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import * as deviceCommService from "@/services/device-comm.service"; +import type { DeviceHealthCheck } from "@/types/device"; + +const DEVICE_COMM_QUERY_KEYS = { + all: ["device-comm"] as const, + allDevices: () => [...DEVICE_COMM_QUERY_KEYS.all, "all"] as const, + roomList: () => [...DEVICE_COMM_QUERY_KEYS.all, "rooms"] as const, + devicesInRoom: (roomName: string) => + [...DEVICE_COMM_QUERY_KEYS.all, "room", roomName] as const, + clientFolderStatus: (roomName: string) => + [...DEVICE_COMM_QUERY_KEYS.all, "folder-status", roomName] as const, +}; + +/** + * Hook để lấy tất cả thiết bị + */ +export function useGetAllDevices(enabled = true) { + return useQuery({ + queryKey: DEVICE_COMM_QUERY_KEYS.allDevices(), + queryFn: () => deviceCommService.getAllDevices(), + enabled, + staleTime: 60 * 1000, + }); +} + +/** + * Hook để lấy danh sách phòng + */ +export function useGetRoomList(enabled = true) { + return useQuery({ + queryKey: DEVICE_COMM_QUERY_KEYS.roomList(), + queryFn: () => deviceCommService.getRoomList(), + enabled, + staleTime: 5 * 60 * 1000, // 5 minutes + }); +} + +/** + * Hook để lấy danh sách thiết bị trong phòng + */ +export function useGetDeviceFromRoom(roomName?: string, enabled = true) { + return useQuery({ + queryKey: roomName ? DEVICE_COMM_QUERY_KEYS.devicesInRoom(roomName) : ["disabled"], + queryFn: () => + roomName ? deviceCommService.getDeviceFromRoom(roomName) : Promise.reject("No room"), + enabled: enabled && !!roomName, + staleTime: 30 * 1000, // 30 seconds + }); +} + +/** + * Hook để tải file + */ +export function useDownloadFiles() { + return useMutation({ + mutationFn: ({ + roomName, + data, + }: { + roomName: string; + data: any; + }) => deviceCommService.downloadFiles(roomName, data), + }); +} + +/** + * Hook để cài đặt MSI + */ +export function useInstallMsi() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + roomName, + data, + }: { + roomName: string; + data: any; + }) => deviceCommService.installMsi(roomName, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: DEVICE_COMM_QUERY_KEYS.all, + }); + }, + }); +} + +/** + * Hook để cập nhật agent + */ +export function useUpdateAgent() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ + roomName, + data, + }: { + roomName: string; + data: any; + }) => deviceCommService.updateAgent(roomName, data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: DEVICE_COMM_QUERY_KEYS.all, + }); + }, + }); +} + +/** + * Hook để cập nhật blacklist + */ +export function useUpdateDeviceBlacklist() { + return useMutation({ + mutationFn: ({ + roomName, + data, + }: { + roomName: string; + data: any; + }) => deviceCommService.updateBlacklist(roomName, data), + }); +} + +/** + * Hook để gửi lệnh shell + */ +export function useSendCommand() { + return useMutation({ + mutationFn: ({ + roomName, + data, + }: { + roomName: string; + data: any; + }) => deviceCommService.sendCommand(roomName, data), + }); +} + +/** + * Hook để thay đổi phòng của thiết bị + */ +export function useChangeDeviceRoom() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: any) => deviceCommService.changeDeviceRoom(data), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: DEVICE_COMM_QUERY_KEYS.all, + }); + }, + }); +} + +/** + * Hook để lấy trạng thái folder client + */ +export function useGetClientFolderStatus(roomName?: string, enabled = true) { + return useQuery({ + queryKey: roomName + ? DEVICE_COMM_QUERY_KEYS.clientFolderStatus(roomName) + : ["disabled"], + queryFn: () => + roomName + ? deviceCommService.getClientFolderStatus(roomName) + : Promise.reject("No room"), + enabled: enabled && !!roomName, + staleTime: 30 * 1000, + }); +} diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx new file mode 100644 index 0000000..be153ff --- /dev/null +++ b/src/hooks/useAuth.tsx @@ -0,0 +1,106 @@ +import { sleep } from "@/lib/utils"; +import { PermissionEnum } from "@/types/permission"; +import React, { useContext, useEffect, useMemo } from "react"; +import { useCallback, useState } from "react"; + +export interface IAuthContext { + isAuthenticated: boolean; + setAuthenticated: (value: boolean) => void; + logout: () => Promise; + login: (username: string) => void; + username: string; + token: string; + name: string; + acs: number[]; + hasPermission: (permission: PermissionEnum) => boolean; + role: { + roleName: string; + priority: number; + }; +} + +const AuthContext = React.createContext(null); + +const key = "accesscontrol.auth.user"; + +function getStoredUser() { + return localStorage.getItem(key); +} + +function setStoredUser(user: string | null) { + if (user) { + localStorage.setItem(key, user); + } else { + localStorage.removeItem(key); + } +} + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [user, setUser] = useState(getStoredUser() || ""); + const [isAuthenticated, setIsAuthenticated] = useState(!!user); + const token = localStorage.getItem("token") || ""; + const name = localStorage.getItem("name") || ""; + const acsString = localStorage.getItem("acs"); + const acs = useMemo(() => (acsString ? acsString.split(",").map(Number) : []), [acsString]); + const roleName = localStorage.getItem("role") || ""; + const priority = localStorage.getItem("priority") || "-1"; + + const setAuthenticated = useCallback((value: boolean) => { + setIsAuthenticated(value); + }, []); + + const login = useCallback((username: string) => { + setStoredUser(username); + setUser(username); + }, []); + + const hasPermission = useCallback( + (permission: PermissionEnum) => { + return acs.some((a) => a === permission); + }, + [acs] + ); + + const logout = useCallback(async () => { + await sleep(250); + setAuthenticated(false); + setStoredUser(""); + setUser(""); + localStorage.removeItem("token"); + localStorage.removeItem("name"); + localStorage.removeItem("acs"); + localStorage.removeItem("role"); + localStorage.removeItem("priority"); + }, [setAuthenticated]); + + useEffect(() => { + setUser(getStoredUser() || ""); + }, []); + + return ( + + {children} + + ); +} + +// eslint-disable-next-line react-refresh/only-export-components +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +} diff --git a/src/hooks/useDeleteData.ts b/src/hooks/useDeleteData.ts deleted file mode 100644 index 3ec2b8d..0000000 --- a/src/hooks/useDeleteData.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import axios from "axios"; - -type DeleteDataOptions = { - onSuccess?: (data: TOutput) => void; - onError?: (error: any) => void; - invalidate?: string[][]; -}; - -export function useDeleteData({ - onSuccess, - onError, - invalidate = [], -}: DeleteDataOptions = {}) { - const queryClient = useQueryClient(); - - return useMutation< - TOutput, - any, - { - url: string; - config?: any; - } - >({ - mutationFn: async ({ url, config }) => { - const response = await axios.delete(url, config); - return response.data; - }, - onSuccess: (data) => { - invalidate.forEach((key) => - queryClient.invalidateQueries({ queryKey: key }) - ); - onSuccess?.(data); - }, - onError, - }); -} diff --git a/src/hooks/useMutationData.ts b/src/hooks/useMutationData.ts deleted file mode 100644 index 27dfb1b..0000000 --- a/src/hooks/useMutationData.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import axios, { type Method } from "axios"; - -type MutationDataOptions = { - url: string; - method?: Method; - onSuccess?: (data: TOutput) => void; - onError?: (error: any) => void; - invalidate?: string[][]; -}; - -export function useMutationData({ - url, - method = "POST", - onSuccess, - onError, - invalidate = [], -}: MutationDataOptions) { - const queryClient = useQueryClient(); - - return useMutation< - TOutput, - any, - { data: TInput; url?: string; config?: any; method?: Method } - >({ - mutationFn: async ({ - data, - config, - url: customUrl, - method: customMethod, - }) => { - const isFormData = data instanceof FormData; - - const response = await axios.request({ - url: customUrl ?? url, - method: customMethod ?? method, - data, - headers: { - ...(isFormData ? {} : { "Content-Type": "application/json" }), - }, - ...config, - }); - return response.data; - }, - onSuccess: async (data) => { - // Invalidate queries trước - await Promise.all( - invalidate.map((key) => - queryClient.invalidateQueries({ queryKey: key }) - ) - ); - // Sau đó gọi callback - onSuccess?.(data); - }, - onError, - }); -} diff --git a/src/hooks/useQueryData.ts b/src/hooks/useQueryData.ts deleted file mode 100644 index 5fce8cf..0000000 --- a/src/hooks/useQueryData.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import axios from "axios"; - -type QueryDataOptions = { - queryKey: string[]; - url: string; - params?: Record; - select?: (data: any) => T; - enabled?: boolean; -}; - -export function useQueryData({ - queryKey, - url, - params, - select, - enabled = true, -}: QueryDataOptions) { - return useQuery({ - queryKey, - queryFn: () => axios.get(url, { params }).then((res) => res.data), - select, - enabled, - }); -} diff --git a/src/styles.css b/src/index.css similarity index 100% rename from src/styles.css rename to src/index.css diff --git a/src/layouts/app-layout.tsx b/src/layouts/app-layout.tsx index 7a33b1e..e269118 100644 --- a/src/layouts/app-layout.tsx +++ b/src/layouts/app-layout.tsx @@ -8,8 +8,13 @@ import { import { Home, Building, AppWindow, Terminal, CircleX } from "lucide-react"; import { Toaster } from "@/components/ui/sonner"; import { useQueryClient } from "@tanstack/react-query"; -import { API_ENDPOINTS } from "@/config/api"; import { Separator } from "@/components/ui/separator"; +import { + useGetAgentVersion, + useGetSoftwareList, + useGetRoomList, + useGetBlacklist, +} from "@/hooks/queries"; type AppLayoutProps = { children: ReactNode; @@ -20,43 +25,32 @@ export default function AppLayout({ children }: AppLayoutProps) { const handlePrefetchAgents = () => { queryClient.prefetchQuery({ - queryKey: ["agent-version"], - queryFn: () => - fetch(API_ENDPOINTS.APP_VERSION.GET_VERSION).then((res) => - res.json() - ), + queryKey: ["app-version", "agent"], + queryFn: useGetAgentVersion as any, staleTime: 60 * 1000, }); }; + const handlePrefetchSofware = () => { queryClient.prefetchQuery({ - queryKey: ["software-version"], - queryFn: () => - fetch(API_ENDPOINTS.APP_VERSION.GET_SOFTWARE).then((res) => - res.json() - ), + queryKey: ["app-version", "software"], + queryFn: useGetSoftwareList as any, staleTime: 60 * 1000, }); }; const handlePrefetchRooms = () => { queryClient.prefetchQuery({ - queryKey: ["room-list"], - queryFn: () => - fetch(API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST).then((res) => - res.json() - ), - staleTime: 60 * 1000, + queryKey: ["device-comm", "rooms"], + queryFn: useGetRoomList as any, + staleTime: 5 * 60 * 1000, }); }; const handlePrefetchBannedSoftware = () => { queryClient.prefetchQuery({ - queryKey: ["blacklist"], - queryFn: () => - fetch(API_ENDPOINTS.APP_VERSION + "").then((res) => - res.json() - ), + queryKey: ["app-version", "blacklist"], + queryFn: useGetBlacklist as any, staleTime: 60 * 1000, }); }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bd0c391..d34bbd9 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,3 +4,7 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } + +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/src/main.tsx b/src/main.tsx index b2587f7..e46d9be 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,25 +1,24 @@ +/* eslint-disable react-refresh/only-export-components */ import { StrictMode } from "react"; +import "./index.css"; import ReactDOM from "react-dom/client"; import { RouterProvider, createRouter } from "@tanstack/react-router"; -import useAuthToken from "./hooks/useAuthtoken"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // Import the generated route tree import { routeTree } from "./routeTree.gen"; - -import "./styles.css"; - -const auth = useAuthToken.getState(); - -export const queryClient = new QueryClient(); +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import axios from "axios"; +import { AuthProvider, useAuth } from "@/hooks/useAuth"; +import { toast, Toaster } from "sonner"; // Create a new router instance const router = createRouter({ routeTree, - context: { auth }, defaultPreload: "intent", scrollRestoration: true, - defaultStructuralSharing: true, - defaultPreloadStaleTime: 0, + context: { + auth: undefined!, // This will be set after we initialize the auth store + queryClient: undefined! + } }); // Register the router instance for type safety @@ -27,18 +26,61 @@ declare module "@tanstack/react-router" { interface Register { router: typeof router; } + interface HistoryState { + name?: string; + } +} + +function InnerApp() { + const auth = useAuth(); + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: (failureCount, error: unknown) => { + if (axios.isAxiosError(error)) { + if (error.response?.status === 401) { + if (error.response?.data.message != null) { + toast.error("Không có quyền truy cập!"); + return false; + } else { + auth.logout(); + queryClient.invalidateQueries(); + queryClient.clear(); + return false; + } + } + } + return failureCount < 3; + } + } + } + }); + return ( + + + + ); +} + +function App() { + return ( + + + + ); } // Render the app const rootElement = document.getElementById("app"); -if (rootElement && !rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement); - root.render( - - - {" "} - - - - ); + +if (!rootElement) { + throw new Error("Failed to find the root element"); } + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + + +); diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index bc97134..2ed4a75 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -9,22 +9,18 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as AuthenticatedRouteImport } from './routes/_authenticated' import { Route as AuthRouteImport } from './routes/_auth' import { Route as IndexRouteImport } from './routes/index' -import { Route as AuthenticatedRoomIndexRouteImport } from './routes/_authenticated/room/index' -import { Route as AuthenticatedDeviceIndexRouteImport } from './routes/_authenticated/device/index' -import { Route as AuthenticatedCommandIndexRouteImport } from './routes/_authenticated/command/index' -import { Route as AuthenticatedBlacklistIndexRouteImport } from './routes/_authenticated/blacklist/index' -import { Route as AuthenticatedAppsIndexRouteImport } from './routes/_authenticated/apps/index' -import { Route as AuthenticatedAgentIndexRouteImport } from './routes/_authenticated/agent/index' -import { Route as AuthLoginIndexRouteImport } from './routes/_auth/login/index' -import { Route as AuthenticatedRoomRoomNameIndexRouteImport } from './routes/_authenticated/room/$roomName/index' +import { Route as AuthRoomIndexRouteImport } from './routes/_auth/room/index' +import { Route as AuthDeviceIndexRouteImport } from './routes/_auth/device/index' +import { Route as AuthDashboardIndexRouteImport } from './routes/_auth/dashboard/index' +import { Route as AuthCommandIndexRouteImport } from './routes/_auth/command/index' +import { Route as AuthBlacklistIndexRouteImport } from './routes/_auth/blacklist/index' +import { Route as AuthAppsIndexRouteImport } from './routes/_auth/apps/index' +import { Route as AuthAgentIndexRouteImport } from './routes/_auth/agent/index' +import { Route as authLoginIndexRouteImport } from './routes/(auth)/login/index' +import { Route as AuthRoomRoomNameIndexRouteImport } from './routes/_auth/room/$roomName/index' -const AuthenticatedRoute = AuthenticatedRouteImport.update({ - id: '/_authenticated', - getParentRoute: () => rootRouteImport, -} as any) const AuthRoute = AuthRouteImport.update({ id: '/_auth', getParentRoute: () => rootRouteImport, @@ -34,86 +30,89 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) -const AuthenticatedRoomIndexRoute = AuthenticatedRoomIndexRouteImport.update({ +const AuthRoomIndexRoute = AuthRoomIndexRouteImport.update({ id: '/room/', path: '/room/', - getParentRoute: () => AuthenticatedRoute, -} as any) -const AuthenticatedDeviceIndexRoute = - AuthenticatedDeviceIndexRouteImport.update({ - id: '/device/', - path: '/device/', - getParentRoute: () => AuthenticatedRoute, - } as any) -const AuthenticatedCommandIndexRoute = - AuthenticatedCommandIndexRouteImport.update({ - id: '/command/', - path: '/command/', - getParentRoute: () => AuthenticatedRoute, - } as any) -const AuthenticatedBlacklistIndexRoute = - AuthenticatedBlacklistIndexRouteImport.update({ - id: '/blacklist/', - path: '/blacklist/', - getParentRoute: () => AuthenticatedRoute, - } as any) -const AuthenticatedAppsIndexRoute = AuthenticatedAppsIndexRouteImport.update({ - id: '/apps/', - path: '/apps/', - getParentRoute: () => AuthenticatedRoute, -} as any) -const AuthenticatedAgentIndexRoute = AuthenticatedAgentIndexRouteImport.update({ - id: '/agent/', - path: '/agent/', - getParentRoute: () => AuthenticatedRoute, -} as any) -const AuthLoginIndexRoute = AuthLoginIndexRouteImport.update({ - id: '/login/', - path: '/login/', getParentRoute: () => AuthRoute, } as any) -const AuthenticatedRoomRoomNameIndexRoute = - AuthenticatedRoomRoomNameIndexRouteImport.update({ - id: '/room/$roomName/', - path: '/room/$roomName/', - getParentRoute: () => AuthenticatedRoute, - } as any) +const AuthDeviceIndexRoute = AuthDeviceIndexRouteImport.update({ + id: '/device/', + path: '/device/', + getParentRoute: () => AuthRoute, +} as any) +const AuthDashboardIndexRoute = AuthDashboardIndexRouteImport.update({ + id: '/dashboard/', + path: '/dashboard/', + getParentRoute: () => AuthRoute, +} as any) +const AuthCommandIndexRoute = AuthCommandIndexRouteImport.update({ + id: '/command/', + path: '/command/', + getParentRoute: () => AuthRoute, +} as any) +const AuthBlacklistIndexRoute = AuthBlacklistIndexRouteImport.update({ + id: '/blacklist/', + path: '/blacklist/', + getParentRoute: () => AuthRoute, +} as any) +const AuthAppsIndexRoute = AuthAppsIndexRouteImport.update({ + id: '/apps/', + path: '/apps/', + getParentRoute: () => AuthRoute, +} as any) +const AuthAgentIndexRoute = AuthAgentIndexRouteImport.update({ + id: '/agent/', + path: '/agent/', + getParentRoute: () => AuthRoute, +} as any) +const authLoginIndexRoute = authLoginIndexRouteImport.update({ + id: '/(auth)/login/', + path: '/login/', + getParentRoute: () => rootRouteImport, +} as any) +const AuthRoomRoomNameIndexRoute = AuthRoomRoomNameIndexRouteImport.update({ + id: '/room/$roomName/', + path: '/room/$roomName/', + getParentRoute: () => AuthRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute - '/login': typeof AuthLoginIndexRoute - '/agent': typeof AuthenticatedAgentIndexRoute - '/apps': typeof AuthenticatedAppsIndexRoute - '/blacklist': typeof AuthenticatedBlacklistIndexRoute - '/command': typeof AuthenticatedCommandIndexRoute - '/device': typeof AuthenticatedDeviceIndexRoute - '/room': typeof AuthenticatedRoomIndexRoute - '/room/$roomName': typeof AuthenticatedRoomRoomNameIndexRoute + '/login': typeof authLoginIndexRoute + '/agent': typeof AuthAgentIndexRoute + '/apps': typeof AuthAppsIndexRoute + '/blacklist': typeof AuthBlacklistIndexRoute + '/command': typeof AuthCommandIndexRoute + '/dashboard': typeof AuthDashboardIndexRoute + '/device': typeof AuthDeviceIndexRoute + '/room': typeof AuthRoomIndexRoute + '/room/$roomName': typeof AuthRoomRoomNameIndexRoute } export interface FileRoutesByTo { '/': typeof IndexRoute - '/login': typeof AuthLoginIndexRoute - '/agent': typeof AuthenticatedAgentIndexRoute - '/apps': typeof AuthenticatedAppsIndexRoute - '/blacklist': typeof AuthenticatedBlacklistIndexRoute - '/command': typeof AuthenticatedCommandIndexRoute - '/device': typeof AuthenticatedDeviceIndexRoute - '/room': typeof AuthenticatedRoomIndexRoute - '/room/$roomName': typeof AuthenticatedRoomRoomNameIndexRoute + '/login': typeof authLoginIndexRoute + '/agent': typeof AuthAgentIndexRoute + '/apps': typeof AuthAppsIndexRoute + '/blacklist': typeof AuthBlacklistIndexRoute + '/command': typeof AuthCommandIndexRoute + '/dashboard': typeof AuthDashboardIndexRoute + '/device': typeof AuthDeviceIndexRoute + '/room': typeof AuthRoomIndexRoute + '/room/$roomName': typeof AuthRoomRoomNameIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute '/_auth': typeof AuthRouteWithChildren - '/_authenticated': typeof AuthenticatedRouteWithChildren - '/_auth/login/': typeof AuthLoginIndexRoute - '/_authenticated/agent/': typeof AuthenticatedAgentIndexRoute - '/_authenticated/apps/': typeof AuthenticatedAppsIndexRoute - '/_authenticated/blacklist/': typeof AuthenticatedBlacklistIndexRoute - '/_authenticated/command/': typeof AuthenticatedCommandIndexRoute - '/_authenticated/device/': typeof AuthenticatedDeviceIndexRoute - '/_authenticated/room/': typeof AuthenticatedRoomIndexRoute - '/_authenticated/room/$roomName/': typeof AuthenticatedRoomRoomNameIndexRoute + '/(auth)/login/': typeof authLoginIndexRoute + '/_auth/agent/': typeof AuthAgentIndexRoute + '/_auth/apps/': typeof AuthAppsIndexRoute + '/_auth/blacklist/': typeof AuthBlacklistIndexRoute + '/_auth/command/': typeof AuthCommandIndexRoute + '/_auth/dashboard/': typeof AuthDashboardIndexRoute + '/_auth/device/': typeof AuthDeviceIndexRoute + '/_auth/room/': typeof AuthRoomIndexRoute + '/_auth/room/$roomName/': typeof AuthRoomRoomNameIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -124,6 +123,7 @@ export interface FileRouteTypes { | '/apps' | '/blacklist' | '/command' + | '/dashboard' | '/device' | '/room' | '/room/$roomName' @@ -135,6 +135,7 @@ export interface FileRouteTypes { | '/apps' | '/blacklist' | '/command' + | '/dashboard' | '/device' | '/room' | '/room/$roomName' @@ -142,32 +143,25 @@ export interface FileRouteTypes { | '__root__' | '/' | '/_auth' - | '/_authenticated' - | '/_auth/login/' - | '/_authenticated/agent/' - | '/_authenticated/apps/' - | '/_authenticated/blacklist/' - | '/_authenticated/command/' - | '/_authenticated/device/' - | '/_authenticated/room/' - | '/_authenticated/room/$roomName/' + | '/(auth)/login/' + | '/_auth/agent/' + | '/_auth/apps/' + | '/_auth/blacklist/' + | '/_auth/command/' + | '/_auth/dashboard/' + | '/_auth/device/' + | '/_auth/room/' + | '/_auth/room/$roomName/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute AuthRoute: typeof AuthRouteWithChildren - AuthenticatedRoute: typeof AuthenticatedRouteWithChildren + authLoginIndexRoute: typeof authLoginIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/_authenticated': { - id: '/_authenticated' - path: '' - fullPath: '' - preLoaderRoute: typeof AuthenticatedRouteImport - parentRoute: typeof rootRouteImport - } '/_auth': { id: '/_auth' path: '' @@ -182,103 +176,100 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } - '/_authenticated/room/': { - id: '/_authenticated/room/' + '/_auth/room/': { + id: '/_auth/room/' path: '/room' fullPath: '/room' - preLoaderRoute: typeof AuthenticatedRoomIndexRouteImport - parentRoute: typeof AuthenticatedRoute - } - '/_authenticated/device/': { - id: '/_authenticated/device/' - path: '/device' - fullPath: '/device' - preLoaderRoute: typeof AuthenticatedDeviceIndexRouteImport - parentRoute: typeof AuthenticatedRoute - } - '/_authenticated/command/': { - id: '/_authenticated/command/' - path: '/command' - fullPath: '/command' - preLoaderRoute: typeof AuthenticatedCommandIndexRouteImport - parentRoute: typeof AuthenticatedRoute - } - '/_authenticated/blacklist/': { - id: '/_authenticated/blacklist/' - path: '/blacklist' - fullPath: '/blacklist' - preLoaderRoute: typeof AuthenticatedBlacklistIndexRouteImport - parentRoute: typeof AuthenticatedRoute - } - '/_authenticated/apps/': { - id: '/_authenticated/apps/' - path: '/apps' - fullPath: '/apps' - preLoaderRoute: typeof AuthenticatedAppsIndexRouteImport - parentRoute: typeof AuthenticatedRoute - } - '/_authenticated/agent/': { - id: '/_authenticated/agent/' - path: '/agent' - fullPath: '/agent' - preLoaderRoute: typeof AuthenticatedAgentIndexRouteImport - parentRoute: typeof AuthenticatedRoute - } - '/_auth/login/': { - id: '/_auth/login/' - path: '/login' - fullPath: '/login' - preLoaderRoute: typeof AuthLoginIndexRouteImport + preLoaderRoute: typeof AuthRoomIndexRouteImport parentRoute: typeof AuthRoute } - '/_authenticated/room/$roomName/': { - id: '/_authenticated/room/$roomName/' + '/_auth/device/': { + id: '/_auth/device/' + path: '/device' + fullPath: '/device' + preLoaderRoute: typeof AuthDeviceIndexRouteImport + parentRoute: typeof AuthRoute + } + '/_auth/dashboard/': { + id: '/_auth/dashboard/' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof AuthDashboardIndexRouteImport + parentRoute: typeof AuthRoute + } + '/_auth/command/': { + id: '/_auth/command/' + path: '/command' + fullPath: '/command' + preLoaderRoute: typeof AuthCommandIndexRouteImport + parentRoute: typeof AuthRoute + } + '/_auth/blacklist/': { + id: '/_auth/blacklist/' + path: '/blacklist' + fullPath: '/blacklist' + preLoaderRoute: typeof AuthBlacklistIndexRouteImport + parentRoute: typeof AuthRoute + } + '/_auth/apps/': { + id: '/_auth/apps/' + path: '/apps' + fullPath: '/apps' + preLoaderRoute: typeof AuthAppsIndexRouteImport + parentRoute: typeof AuthRoute + } + '/_auth/agent/': { + id: '/_auth/agent/' + path: '/agent' + fullPath: '/agent' + preLoaderRoute: typeof AuthAgentIndexRouteImport + parentRoute: typeof AuthRoute + } + '/(auth)/login/': { + id: '/(auth)/login/' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof authLoginIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/room/$roomName/': { + id: '/_auth/room/$roomName/' path: '/room/$roomName' fullPath: '/room/$roomName' - preLoaderRoute: typeof AuthenticatedRoomRoomNameIndexRouteImport - parentRoute: typeof AuthenticatedRoute + preLoaderRoute: typeof AuthRoomRoomNameIndexRouteImport + parentRoute: typeof AuthRoute } } } interface AuthRouteChildren { - AuthLoginIndexRoute: typeof AuthLoginIndexRoute + AuthAgentIndexRoute: typeof AuthAgentIndexRoute + AuthAppsIndexRoute: typeof AuthAppsIndexRoute + AuthBlacklistIndexRoute: typeof AuthBlacklistIndexRoute + AuthCommandIndexRoute: typeof AuthCommandIndexRoute + AuthDashboardIndexRoute: typeof AuthDashboardIndexRoute + AuthDeviceIndexRoute: typeof AuthDeviceIndexRoute + AuthRoomIndexRoute: typeof AuthRoomIndexRoute + AuthRoomRoomNameIndexRoute: typeof AuthRoomRoomNameIndexRoute } const AuthRouteChildren: AuthRouteChildren = { - AuthLoginIndexRoute: AuthLoginIndexRoute, + AuthAgentIndexRoute: AuthAgentIndexRoute, + AuthAppsIndexRoute: AuthAppsIndexRoute, + AuthBlacklistIndexRoute: AuthBlacklistIndexRoute, + AuthCommandIndexRoute: AuthCommandIndexRoute, + AuthDashboardIndexRoute: AuthDashboardIndexRoute, + AuthDeviceIndexRoute: AuthDeviceIndexRoute, + AuthRoomIndexRoute: AuthRoomIndexRoute, + AuthRoomRoomNameIndexRoute: AuthRoomRoomNameIndexRoute, } const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren) -interface AuthenticatedRouteChildren { - AuthenticatedAgentIndexRoute: typeof AuthenticatedAgentIndexRoute - AuthenticatedAppsIndexRoute: typeof AuthenticatedAppsIndexRoute - AuthenticatedBlacklistIndexRoute: typeof AuthenticatedBlacklistIndexRoute - AuthenticatedCommandIndexRoute: typeof AuthenticatedCommandIndexRoute - AuthenticatedDeviceIndexRoute: typeof AuthenticatedDeviceIndexRoute - AuthenticatedRoomIndexRoute: typeof AuthenticatedRoomIndexRoute - AuthenticatedRoomRoomNameIndexRoute: typeof AuthenticatedRoomRoomNameIndexRoute -} - -const AuthenticatedRouteChildren: AuthenticatedRouteChildren = { - AuthenticatedAgentIndexRoute: AuthenticatedAgentIndexRoute, - AuthenticatedAppsIndexRoute: AuthenticatedAppsIndexRoute, - AuthenticatedBlacklistIndexRoute: AuthenticatedBlacklistIndexRoute, - AuthenticatedCommandIndexRoute: AuthenticatedCommandIndexRoute, - AuthenticatedDeviceIndexRoute: AuthenticatedDeviceIndexRoute, - AuthenticatedRoomIndexRoute: AuthenticatedRoomIndexRoute, - AuthenticatedRoomRoomNameIndexRoute: AuthenticatedRoomRoomNameIndexRoute, -} - -const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren( - AuthenticatedRouteChildren, -) - const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AuthRoute: AuthRouteWithChildren, - AuthenticatedRoute: AuthenticatedRouteWithChildren, + authLoginIndexRoute: authLoginIndexRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/src/routes/_auth/login/index.tsx b/src/routes/(auth)/login/index.tsx similarity index 75% rename from src/routes/_auth/login/index.tsx rename to src/routes/(auth)/login/index.tsx index e6d2e1a..e09a844 100644 --- a/src/routes/_auth/login/index.tsx +++ b/src/routes/(auth)/login/index.tsx @@ -1,4 +1,4 @@ -import { createFileRoute, redirect } from '@tanstack/react-router' +import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router' import { Card, CardContent, @@ -14,6 +14,8 @@ import { formOptions, useForm, } from '@tanstack/react-form' +import { useLogin } from '@/hooks/queries' +import { toast } from 'sonner' interface LoginFormProps { username: string @@ -29,26 +31,34 @@ const formOpts = formOptions({ defaultValues: defaultInput, }) -export const Route = createFileRoute('/_auth/login/')({ +export const Route = createFileRoute('/(auth)/login/')({ beforeLoad: async ({ context }) => { - const { authToken } = context.auth - if (authToken) throw redirect({ to: '/' }) + const { token } = context.auth + if (token) throw redirect({ to: '/' }) }, component: LoginForm, }) function LoginForm() { + const navigate = useNavigate() + const loginMutation = useLogin() + const form = useForm({ ...formOpts, onSubmit: async ({ value }) => { - console.log('Submitting login form with values:', value) + try { + await loginMutation.mutateAsync({ + username: value.username, + password: value.password, + }) - // Giả lập đăng nhập - if (value.username === 'admin' && value.password === '123456') { - alert('Đăng nhập thành công!') - // Thêm xử lý lưu token, redirect... - } else { - alert('Tài khoản hoặc mật khẩu không đúng.') + toast.success('Đăng nhập thành công!') + navigate({ to: '/' }) + } catch (error: any) { + console.error('Login error:', error) + toast.error( + error.response?.data?.message || 'Tài khoản hoặc mật khẩu không đúng.' + ) } }, }) @@ -110,8 +120,8 @@ function LoginForm() { )} - diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 8f65101..b9fe598 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,22 +1,37 @@ -import { Outlet, createRootRouteWithContext, HeadContent } from '@tanstack/react-router' -import type { AuthTokenProps } from '@/hooks/useAuthtoken' +import ErrorRoute from "@/components/pages/error-route"; +import NotFound from "@/components/pages/not-found"; +import { type IAuthContext } from "@/types/auth"; +import { QueryClient } from "@tanstack/react-query"; +import { + createRootRouteWithContext, + HeadContent, + Outlet, +} from "@tanstack/react-router"; -export interface RouterContext { - auth: AuthTokenProps +export interface BreadcrumbItem { + title: string; + path: string; } -export const Route = createRootRouteWithContext()({ - head: () => ({ - meta: [ - { title: "Quản lý phòng máy" }, - { name: "description", content: "Ứng dụng quản lý thiết bị và phần mềm" }, - ], - }), - component: () => ( - <> - - - - ), -}) +export interface MyRouterContext { + auth: IAuthContext; + queryClient: QueryClient; + breadcrumbs?: BreadcrumbItem[]; +} +export const Route = createRootRouteWithContext()({ + component: () => { + return ( + <> + + + + ); + }, + notFoundComponent: () => { + return ; + }, + errorComponent: ({ error }) => { + return ; + }, +}); diff --git a/src/routes/_auth.tsx b/src/routes/_auth.tsx index 486b5d3..32f4ed4 100644 --- a/src/routes/_auth.tsx +++ b/src/routes/_auth.tsx @@ -1,16 +1,21 @@ -import {createFileRoute, Outlet, redirect} from '@tanstack/react-router' +import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' +import AppLayout from '@/layouts/app-layout' export const Route = createFileRoute('/_auth')({ - beforeLoad: async ({context}) => { - const {authToken} = context.auth - if (authToken) { - throw redirect({to: '/'}) - } - }, - component:AuthLayout , + + // beforeLoad: async ({context}) => { + // const {token} = context.auth + // if (token == null) { + // throw redirect({to: '/login'}) + // } + // }, + component: AuthenticatedLayout, }) -function AuthLayout() { + +function AuthenticatedLayout() { return ( + + ) } \ No newline at end of file diff --git a/src/routes/_authenticated/agent/index.tsx b/src/routes/_auth/agent/index.tsx similarity index 64% rename from src/routes/_authenticated/agent/index.tsx rename to src/routes/_auth/agent/index.tsx index 00cb6c8..9ca9899 100644 --- a/src/routes/_authenticated/agent/index.tsx +++ b/src/routes/_auth/agent/index.tsx @@ -1,30 +1,27 @@ import { createFileRoute } from "@tanstack/react-router"; import { AppManagerTemplate } from "@/template/app-manager-template"; -import { useQueryData } from "@/hooks/useQueryData"; -import { useMutationData } from "@/hooks/useMutationData"; -import { API_ENDPOINTS } from "@/config/api"; +import { + useGetAgentVersion, + useGetRoomList, + useUploadSoftware, + useUpdateAgent, +} from "@/hooks/queries"; import { toast } from "sonner"; import type { ColumnDef } from "@tanstack/react-table"; import type { AxiosProgressEvent } from "axios"; import type { Version } from "@/types/file"; -export const Route = createFileRoute("/_authenticated/agent/")({ +export const Route = createFileRoute("/_auth/agent/")({ head: () => ({ meta: [{ title: "Quản lý Agent" }] }), component: AgentsPage, }); function AgentsPage() { // Lấy danh sách version - const { data, isLoading } = useQueryData({ - queryKey: ["agent-version"], - url: API_ENDPOINTS.APP_VERSION.GET_VERSION, - }); + const { data, isLoading } = useGetAgentVersion(); // Lấy danh sách phòng - const { data: roomData } = useQueryData({ - queryKey: ["rooms"], - url: API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST, - }); + const { data: roomData } = useGetRoomList(); const versionList: Version[] = Array.isArray(data) ? data @@ -32,44 +29,32 @@ function AgentsPage() { ? [data] : []; - const uploadMutation = useMutationData({ - url: API_ENDPOINTS.APP_VERSION.UPLOAD, - method: "POST", - invalidate: [["agent-version"]], - onSuccess: () => toast.success("Upload thành công!"), - onError: (error) => { - console.error("Upload error:", error); - toast.error("Upload thất bại!"); - }, - }); + const uploadMutation = useUploadSoftware(); - const updateMutation = useMutationData({ - url: "", - method: "POST", - onSuccess: () => toast.success("Đã gửi yêu cầu update!"), - onError: (error) => { - console.error("Update mutation error:", error); - toast.error("Gửi yêu cầu thất bại!"); - }, - }); + const updateMutation = useUpdateAgent(); const handleUpload = async ( fd: FormData, config?: { onUploadProgress?: (e: AxiosProgressEvent) => void } ) => { - return uploadMutation.mutateAsync({ - data: fd, - config, - }); + try { + await uploadMutation.mutateAsync({ + formData: fd, + onUploadProgress: config?.onUploadProgress, + }); + toast.success("Upload thành công!"); + } catch (error: any) { + console.error("Upload error:", error); + toast.error("Upload thất bại!"); + } }; const handleUpdate = async (roomNames: string[]) => { try { for (const roomName of roomNames) { await updateMutation.mutateAsync({ - url: API_ENDPOINTS.DEVICE_COMM.UPDATE_AGENT(roomName), - method: "POST", - data: undefined + roomName, + data: {} }); } toast.success("Đã gửi yêu cầu update cho các phòng đã chọn!"); diff --git a/src/routes/_authenticated/apps/index.tsx b/src/routes/_auth/apps/index.tsx similarity index 68% rename from src/routes/_authenticated/apps/index.tsx rename to src/routes/_auth/apps/index.tsx index 76487fa..d87a81a 100644 --- a/src/routes/_authenticated/apps/index.tsx +++ b/src/routes/_auth/apps/index.tsx @@ -1,30 +1,30 @@ import { createFileRoute } from "@tanstack/react-router"; import { AppManagerTemplate } from "@/template/app-manager-template"; -import { useQueryData } from "@/hooks/useQueryData"; -import { useMutationData } from "@/hooks/useMutationData"; -import { API_ENDPOINTS } from "@/config/api"; +import { + useGetSoftwareList, + useGetRoomList, + useUploadSoftware, + useDeleteFile, + useAddRequiredFile, + useDeleteRequiredFile, + useInstallMsi, + useDownloadFiles, +} from "@/hooks/queries"; import { toast } from "sonner"; import type { ColumnDef } from "@tanstack/react-table"; -import { useState } from "react"; import type { AxiosProgressEvent } from "axios"; import type { Version } from "@/types/file"; import { Check, X } from "lucide-react"; +import { useState } from "react"; -export const Route = createFileRoute("/_authenticated/apps/")({ +export const Route = createFileRoute("/_auth/apps/")({ head: () => ({ meta: [{ title: "Quản lý phần mềm" }] }), component: AppsComponent, }); function AppsComponent() { - const { data, isLoading } = useQueryData({ - queryKey: ["software-version"], - url: API_ENDPOINTS.APP_VERSION.GET_SOFTWARE, - }); - - const { data: roomData } = useQueryData({ - queryKey: ["rooms"], - url: API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST, - }); + const { data, isLoading } = useGetSoftwareList(); + const { data: roomData } = useGetRoomList(); const versionList: Version[] = Array.isArray(data) ? data @@ -34,72 +34,17 @@ function AppsComponent() { const [table, setTable] = useState(); - const uploadMutation = useMutationData({ - url: API_ENDPOINTS.APP_VERSION.UPLOAD, - method: "POST", - invalidate: [["software-version"]], - onSuccess: () => toast.success("Upload thành công!"), - onError: (error) => { - console.error("Upload error:", error); - toast.error("Upload thất bại!"); - }, - }); + const uploadMutation = useUploadSoftware(); - const installMutation = useMutationData<{ MsiFileIds: number[] }>({ - url: "", - method: "POST", - onSuccess: () => toast.success("Đã gửi yêu cầu cài đặt file!"), - onError: (error) => { - console.error("Install error:", error); - toast.error("Gửi yêu cầu thất bại!"); - }, - }); + const installMutation = useInstallMsi(); - const downloadMutation = useMutationData<{ MsiFileIds: number[] }>({ - url: "", - method: "POST", - onSuccess: () => toast.success("Đã gửi yêu cầu tải file!"), - onError: (error) => { - console.error("Download error:", error); - toast.error("Gửi yêu cầu thất bại!"); - }, - }); + const downloadMutation = useDownloadFiles(); - const deleteMutation = useMutationData<{ MsiFileIds: number[] }>({ - url: API_ENDPOINTS.APP_VERSION.DELETE_FILES + "", - method: "POST", - invalidate: [["software-version"]], - onSuccess: () => toast.success("Xóa phần mềm thành công!"), - onError: (error) => { - console.error("Delete error:", error); - toast.error("Xóa phần mềm thất bại!"); - }, - }); + const deleteMutation = useDeleteFile(); - const addRequiredFileMutation = useMutationData<{ - fileName: string; - version: string; - }>({ - url: API_ENDPOINTS.APP_VERSION.ADD_REQUIRED_FILE, - method: "POST", - invalidate: [["software-version"]], - onSuccess: () => toast.success("Thêm file vào danh sách thành công!"), - onError: (error) => { - console.error("Add required file error:", error); - toast.error("Thêm file vào danh sách thất bại!"); - }, - }); + const addRequiredFileMutation = useAddRequiredFile(); - const deleteRequiredFileMutation = useMutationData<{ id: number }>({ - url: "", - method: "POST", - invalidate: [["software-version"]], - onSuccess: () => toast.success("Xóa file khỏi danh sách thành công!"), - onError: (error) => { - console.error("Delete required file error:", error); - toast.error("Xóa file khỏi danh sách thất bại!"); - }, - }); + const deleteRequiredFileMutation = useDeleteRequiredFile(); // Cột bảng const columns: ColumnDef[] = [ @@ -163,10 +108,16 @@ function AppsComponent() { fd: FormData, config?: { onUploadProgress?: (e: AxiosProgressEvent) => void } ) => { - return uploadMutation.mutateAsync({ - data: fd, - config, - }); + try { + await uploadMutation.mutateAsync({ + formData: fd, + onUploadProgress: config?.onUploadProgress, + }); + toast.success("Upload thành công!"); + } catch (error: any) { + console.error("Upload error:", error); + toast.error("Upload thất bại!"); + } }; // Callback khi chọn phòng @@ -187,7 +138,7 @@ function AppsComponent() { try { for (const roomName of roomNames) { await installMutation.mutateAsync({ - url: API_ENDPOINTS.DEVICE_COMM.INSTALL_MSI(roomName), + roomName, data: { MsiFileIds }, }); } @@ -205,7 +156,7 @@ function AppsComponent() { const selectedRows = table.getSelectedRowModel().rows; if (selectedRows.length === 0) { - toast.error("Vui lòng chọn ít nhất một file để cài đặt!"); + toast.error("Vui lòng chọn ít nhất một file để tải!"); return; } @@ -214,13 +165,13 @@ function AppsComponent() { try { for (const roomName of roomNames) { await downloadMutation.mutateAsync({ - url: API_ENDPOINTS.DEVICE_COMM.DOWNLOAD_FILES(roomName), + roomName, data: { MsiFileIds }, }); } - toast.success("Đã gửi yêu cầu cài đặt phần mềm cho các phòng đã chọn!"); + toast.success("Đã gửi yêu cầu tải file cho các phòng đã chọn!"); } catch (e) { - toast.error("Có lỗi xảy ra khi cài đặt!"); + toast.error("Có lỗi xảy ra khi tải!"); } }; @@ -235,6 +186,16 @@ function AppsComponent() { toast.error("Vui lòng chọn ít nhất một file để xóa!"); return; } + + try { + for (const row of selectedRows) { + const { id } = row.original; + await deleteMutation.mutateAsync(id); + } + toast.success("Xóa phần mềm thành công!"); + } catch (e) { + toast.error("Xóa phần mềm thất bại!"); + } }; const handleDeleteFromRequiredList = async () => { @@ -245,11 +206,9 @@ function AppsComponent() { try { for (const row of selectedRows) { const { id } = row.original; - await deleteRequiredFileMutation.mutateAsync({ - data: { id }, - url: API_ENDPOINTS.APP_VERSION.DELETE_REQUIRED_FILE(id), - }); + await deleteRequiredFileMutation.mutateAsync(id); } + toast.success("Xóa file khỏi danh sách thành công!"); if (table) { table.setRowSelection({}); } @@ -267,11 +226,9 @@ function AppsComponent() { try { for (const row of selectedRows) { const { id } = row.original; - await deleteMutation.mutateAsync({ - data: { MsiFileIds: [id] }, - url: API_ENDPOINTS.APP_VERSION.DELETE_FILES(id), - }); + await deleteMutation.mutateAsync(id); } + toast.success("Xóa phần mềm từ server thành công!"); if (table) { table.setRowSelection({}); } @@ -297,9 +254,11 @@ function AppsComponent() { for (const row of selectedRows) { const { fileName, version } = row.original; await addRequiredFileMutation.mutateAsync({ - data: { fileName, version }, + fileName, + version, }); } + toast.success("Thêm file vào danh sách thành công!"); table.setRowSelection({}); } catch (e) { console.error("Add required file error:", e); diff --git a/src/routes/_authenticated/blacklist/index.tsx b/src/routes/_auth/blacklist/index.tsx similarity index 61% rename from src/routes/_authenticated/blacklist/index.tsx rename to src/routes/_auth/blacklist/index.tsx index c6a3f9a..a6307ab 100644 --- a/src/routes/_authenticated/blacklist/index.tsx +++ b/src/routes/_auth/blacklist/index.tsx @@ -1,7 +1,10 @@ -import { API_ENDPOINTS } from "@/config/api"; -import { useMutationData } from "@/hooks/useMutationData"; -import { useDeleteData } from "@/hooks/useDeleteData"; -import { useQueryData } from "@/hooks/useQueryData"; +import { + useGetBlacklist, + useGetRoomList, + useAddBlacklist, + useDeleteBlacklist, + useUpdateDeviceBlacklist, +} from "@/hooks/queries"; import { createFileRoute } from "@tanstack/react-router"; import type { ColumnDef } from "@tanstack/react-table"; import type { Blacklist } from "@/types/black-list"; @@ -9,7 +12,7 @@ import { BlackListManagerTemplate } from "@/template/table-manager-template"; import { toast } from "sonner"; import { useState } from "react"; -export const Route = createFileRoute("/_authenticated/blacklist/")({ +export const Route = createFileRoute("/_auth/blacklist/")({ head: () => ({ meta: [{ title: "Danh sách các ứng dụng bị chặn" }] }), component: BlacklistComponent, }); @@ -18,16 +21,10 @@ function BlacklistComponent() { const [selectedRows, setSelectedRows] = useState>(new Set()); // Lấy danh sách blacklist - const { data, isLoading } = useQueryData({ - queryKey: ["blacklist"], - url: API_ENDPOINTS.APP_VERSION.GET_VERSION, - }); + const { data, isLoading } = useGetBlacklist(); // Lấy danh sách phòng - const { data: roomData } = useQueryData({ - queryKey: ["rooms"], - url: API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST, - }); + const { data: roomData = [] } = useGetRoomList(); const blacklist: Blacklist[] = Array.isArray(data) ? (data as Blacklist[]) @@ -70,7 +67,7 @@ function BlacklistComponent() { { - if (e.target.checked) { + if (e.target.checked && data) { const allIds = data.map((item: { id: number }) => item.id); setSelectedRows(new Set(allIds)); } else { @@ -97,42 +94,21 @@ function BlacklistComponent() { }, ]; - // API thêm blacklist - const addNewBlacklistMutation = useMutationData({ - url: "", - method: "POST", - onSuccess: () => toast.success("Thêm mới thành công!"), - onError: () => toast.error("Thêm mới thất bại!"), - }); - - // API cập nhật thiết bị - const updateDeviceMutation = useMutationData({ - url: "", - method: "POST", - onSuccess: () => toast.success("Cập nhật thành công!"), - onError: () => toast.error("Cập nhật thất bại!"), - }); - - // API xoá - const deleteBlacklistMutation = useDeleteData({ - invalidate: [["blacklist"]], - onSuccess: () => toast.success("Xóa thành công!"), - onError: () => toast.error("Xóa thất bại!"), - }); + // API mutations + const addNewBlacklistMutation = useAddBlacklist(); + const deleteBlacklistMutation = useDeleteBlacklist(); + const updateDeviceMutation = useUpdateDeviceBlacklist(); // Thêm blacklist - const handleAddNewBlacklist = async (blacklist: { + const handleAddNewBlacklist = async (blacklistData: { appName: string; processName: string; }) => { try { - await addNewBlacklistMutation.mutateAsync({ - url: API_ENDPOINTS.APP_VERSION.ADD_BLACKLIST, - method: "POST", - config: { headers: { "Content-Type": "application/json" } }, - data: undefined, - }); - } catch { + await addNewBlacklistMutation.mutateAsync(blacklistData); + toast.success("Thêm mới thành công!"); + } catch (error: any) { + console.error("Add blacklist error:", error); toast.error("Thêm mới thất bại!"); } }; @@ -141,27 +117,28 @@ function BlacklistComponent() { const handleDeleteBlacklist = async () => { try { for (const blacklistId of selectedRows) { - await deleteBlacklistMutation.mutateAsync({ - url: - API_ENDPOINTS.APP_VERSION.DELETE_BLACKLIST(blacklistId), - config: { headers: { "Content-Type": "application/json" } }, - }); + await deleteBlacklistMutation.mutateAsync(blacklistId); } + toast.success("Xóa thành công!"); setSelectedRows(new Set()); - } catch {} + } catch (error: any) { + console.error("Delete blacklist error:", error); + toast.error("Xóa thất bại!"); + } }; const handleUpdateDevice = async (target: string | string[]) => { const targets = Array.isArray(target) ? target : [target]; try { - for (const deviceId of targets) { + for (const roomName of targets) { await updateDeviceMutation.mutateAsync({ - url: API_ENDPOINTS.DEVICE_COMM.UPDATE_BLACKLIST(deviceId), - data: undefined, + roomName, + data: {}, }); - toast.success(`Đã gửi cập nhật cho ${deviceId}`); + toast.success(`Đã gửi cập nhật cho ${roomName}`); } - } catch (e) { + } catch (e: any) { + console.error("Update device error:", e); toast.error("Có lỗi xảy ra khi cập nhật!"); } }; @@ -175,6 +152,7 @@ function BlacklistComponent() { isLoading={isLoading} rooms={roomData} onAdd={handleAddNewBlacklist} + onDelete={handleDeleteBlacklist} onUpdate={handleUpdateDevice} /> ); diff --git a/src/routes/_authenticated/command/index.tsx b/src/routes/_auth/command/index.tsx similarity index 72% rename from src/routes/_authenticated/command/index.tsx rename to src/routes/_auth/command/index.tsx index eedf115..9e7b794 100644 --- a/src/routes/_authenticated/command/index.tsx +++ b/src/routes/_auth/command/index.tsx @@ -2,13 +2,18 @@ import { createFileRoute } from "@tanstack/react-router"; import { useState } from "react"; import { CommandSubmitTemplate } from "@/template/command-submit-template"; import { CommandRegistryForm, type CommandRegistryFormData } from "@/components/forms/command-registry-form"; -import { useQueryData } from "@/hooks/useQueryData"; -import { useMutationData } from "@/hooks/useMutationData"; -import { API_ENDPOINTS, BASE_URL } from "@/config/api"; -import type { ColumnDef } from "@tanstack/react-table"; +import { + useGetCommandList, + useGetRoomList, + useAddCommand, + useUpdateCommand, + useDeleteCommand, + useSendCommand, +} from "@/hooks/queries"; import { toast } from "sonner"; import { Check, X, Edit2, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; +import type { ColumnDef } from "@tanstack/react-table"; import type { ShellCommandData } from "@/components/forms/command-form"; interface CommandRegistry { @@ -22,7 +27,7 @@ interface CommandRegistry { updatedAt?: string; } -export const Route = createFileRoute("/_authenticated/command/")({ +export const Route = createFileRoute("/_auth/command/")({ head: () => ({ meta: [{ title: "Gửi lệnh từ xa" }] }), component: CommandPage, }); @@ -33,16 +38,10 @@ function CommandPage() { const [table, setTable] = useState(); // Fetch commands - const { data: commands = [], isLoading } = useQueryData({ - queryKey: ["commands"], - url: API_ENDPOINTS.COMMAND.GET_COMMANDS, - }); + const { data: commands = [], isLoading } = useGetCommandList(); // Fetch rooms - const { data: roomData } = useQueryData({ - queryKey: ["rooms"], - url: API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST, - }); + const { data: roomData = [] } = useGetRoomList(); const commandList: CommandRegistry[] = Array.isArray(commands) ? commands.map((cmd: any) => ({ @@ -50,63 +49,13 @@ function CommandPage() { qoS: cmd.qoS ?? 0, isRetained: cmd.isRetained ?? false, })) - : commands - ? [{ - ...commands, - qoS: commands.qoS ?? 0, - isRetained: commands.isRetained ?? false, - }] - : []; + : []; - // Add command mutation - const addCommandMutation = useMutationData({ - url: API_ENDPOINTS.COMMAND.ADD_COMMAND, - method: "POST", - invalidate: [["commands"]], - onSuccess: () => toast.success("Thêm lệnh thành công!"), - onError: (error) => { - console.error("Add command error:", error); - toast.error("Thêm lệnh thất bại!"); - }, - }); - - // Update command mutation - const updateCommandMutation = useMutationData({ - url: "", - method: "POST", - invalidate: [["commands"]], - onSuccess: () => toast.success("Cập nhật lệnh thành công!"), - onError: (error) => { - console.error("Update command error:", error); - toast.error("Cập nhật lệnh thất bại!"); - }, - }); - - // Delete command mutation - const deleteCommandMutation = useMutationData({ - url: "", - method: "DELETE", - invalidate: [["commands"]], - onSuccess: () => toast.success("Xóa lệnh thành công!"), - onError: (error) => { - console.error("Delete command error:", error); - toast.error("Xóa lệnh thất bại!"); - }, - }); - - // Execute command mutation - const executeCommandMutation = useMutationData<{ - commandIds?: number[]; - command?: ShellCommandData; - }>({ - url: "", - method: "POST", - onSuccess: () => toast.success("Gửi yêu cầu thực thi lệnh thành công!"), - onError: (error) => { - console.error("Execute command error:", error); - toast.error("Gửi yêu cầu thực thi thất bại!"); - }, - }); + // Mutations + const addCommandMutation = useAddCommand(); + const updateCommandMutation = useUpdateCommand(); + const deleteCommandMutation = useDeleteCommand(); + const sendCommandMutation = useSendCommand(); // Columns for command table const columns: ColumnDef[] = [ @@ -214,20 +163,24 @@ function CommandPage() { // Handle form submit const handleFormSubmit = async (data: CommandRegistryFormData) => { - if (selectedCommand) { - // Update - await updateCommandMutation.mutateAsync({ - url: BASE_URL + API_ENDPOINTS.COMMAND.UPDATE_COMMAND(selectedCommand.id), - data, - }); - } else { - // Add - await addCommandMutation.mutateAsync({ - data, - }); + try { + if (selectedCommand) { + // Update + await updateCommandMutation.mutateAsync({ + commandId: selectedCommand.id, + data, + }); + } else { + // Add + await addCommandMutation.mutateAsync(data); + } + setIsDialogOpen(false); + setSelectedCommand(null); + toast.success(selectedCommand ? "Cập nhật lệnh thành công!" : "Thêm lệnh thành công!"); + } catch (error) { + console.error("Form submission error:", error); + toast.error(selectedCommand ? "Cập nhật lệnh thất bại!" : "Thêm lệnh thất bại!"); } - setIsDialogOpen(false); - setSelectedCommand(null); }; // Handle delete @@ -235,12 +188,11 @@ function CommandPage() { if (!confirm("Bạn có chắc muốn xóa lệnh này?")) return; try { - await deleteCommandMutation.mutateAsync({ - url: API_ENDPOINTS.COMMAND.DELETE_COMMAND(commandId), - data: null, - }); + await deleteCommandMutation.mutateAsync(commandId); + toast.success("Xóa lệnh thành công!"); } catch (error) { console.error("Delete error:", error); + toast.error("Xóa lệnh thất bại!"); } }; @@ -269,8 +221,8 @@ function CommandPage() { console.log("[DEBUG] Sending to:", target, "Data:", apiData); - await executeCommandMutation.mutateAsync({ - url: API_ENDPOINTS.DEVICE_COMM.SEND_COMMAND(target), + await sendCommandMutation.mutateAsync({ + roomName: target, data: apiData as any, }); } @@ -299,8 +251,8 @@ function CommandPage() { console.log("[DEBUG] Sending custom to:", target, "Data:", apiData); - await executeCommandMutation.mutateAsync({ - url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.SEND_COMMAND(target), + await sendCommandMutation.mutateAsync({ + roomName: target, data: apiData as any, }); } @@ -337,7 +289,7 @@ function CommandPage() { } onExecuteSelected={handleExecuteSelected} onExecuteCustom={handleExecuteCustom} - isExecuting={executeCommandMutation.isPending} + isExecuting={sendCommandMutation.isPending} rooms={roomData} /> ); diff --git a/src/routes/_auth/dashboard/index.tsx b/src/routes/_auth/dashboard/index.tsx new file mode 100644 index 0000000..da62a32 --- /dev/null +++ b/src/routes/_auth/dashboard/index.tsx @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_auth/dashboard/')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/(auth)/dashboard/"!
+} diff --git a/src/routes/_authenticated/device/index.tsx b/src/routes/_auth/device/index.tsx similarity index 79% rename from src/routes/_authenticated/device/index.tsx rename to src/routes/_auth/device/index.tsx index 2ffcd63..bf16ea4 100644 --- a/src/routes/_authenticated/device/index.tsx +++ b/src/routes/_auth/device/index.tsx @@ -1,6 +1,6 @@ import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/_authenticated/device/')({ +export const Route = createFileRoute('/_auth/device/')({ head: () => ({ meta: [{ title: 'Danh sách tất cả thiết bị' }] }), component: AllDevicesComponent, }) diff --git a/src/routes/_authenticated/room/$roomName/index.tsx b/src/routes/_auth/room/$roomName/index.tsx similarity index 90% rename from src/routes/_authenticated/room/$roomName/index.tsx rename to src/routes/_auth/room/$roomName/index.tsx index 3b07d6a..d0e1ef1 100644 --- a/src/routes/_authenticated/room/$roomName/index.tsx +++ b/src/routes/_auth/room/$roomName/index.tsx @@ -3,16 +3,15 @@ import { useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { LayoutGrid, TableIcon, Monitor, FolderCheck, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { useQueryData } from "@/hooks/useQueryData"; +import { useGetDeviceFromRoom } from "@/hooks/queries"; import { useDeviceEvents } from "@/hooks/useDeviceEvents"; import { useClientFolderStatus } from "@/hooks/useClientFolderStatus"; -import { API_ENDPOINTS } from "@/config/api"; import { DeviceGrid } from "@/components/grids/device-grid"; import { DeviceTable } from "@/components/tables/device-table"; import { useMachineNumber } from "@/hooks/useMachineNumber"; import { toast } from "sonner"; -export const Route = createFileRoute("/_authenticated/room/$roomName/")({ +export const Route = createFileRoute("/_auth/room/$roomName/")({ head: ({ params }) => ({ meta: [{ title: `Danh sách thiết bị phòng ${params.roomName}` }], }), @@ -20,7 +19,7 @@ export const Route = createFileRoute("/_authenticated/room/$roomName/")({ }); function RoomDetailPage() { - const { roomName } = useParams({ from: "/_authenticated/room/$roomName/" }); + const { roomName } = useParams({ from: "/_auth/room/$roomName/" }); const [viewMode, setViewMode] = useState<"grid" | "table">("grid"); const [isCheckingFolder, setIsCheckingFolder] = useState(false); @@ -30,18 +29,16 @@ function RoomDetailPage() { // Folder status from SSE const folderStatuses = useClientFolderStatus(roomName); - const { data: devices = [] } = useQueryData({ - queryKey: ["devices", roomName], - url: API_ENDPOINTS.DEVICE_COMM.GET_DEVICE_FROM_ROOM(roomName), - }); + const { data: devices = [] } = useGetDeviceFromRoom(roomName); const parseMachineNumber = useMachineNumber(); const handleCheckFolderStatus = async () => { try { setIsCheckingFolder(true); + // Trigger folder status check via the service const response = await fetch( - API_ENDPOINTS.DEVICE_COMM.REQUEST_GET_CLIENT_FOLDER_STATUS(roomName), + `/api/device-comm/request-get-client-folder-status?roomName=${encodeURIComponent(roomName)}`, { method: "POST", } @@ -55,6 +52,7 @@ function RoomDetailPage() { } catch (error) { console.error("Check folder error:", error); toast.error("Lỗi khi kiểm tra thư mục!"); + } finally { setIsCheckingFolder(false); } }; diff --git a/src/routes/_authenticated/room/index.tsx b/src/routes/_auth/room/index.tsx similarity index 96% rename from src/routes/_authenticated/room/index.tsx rename to src/routes/_auth/room/index.tsx index 30702d5..1bb9245 100644 --- a/src/routes/_authenticated/room/index.tsx +++ b/src/routes/_auth/room/index.tsx @@ -1,5 +1,4 @@ -import { API_ENDPOINTS } from "@/config/api"; -import { useQueryData } from "@/hooks/useQueryData"; +import { useGetRoomList } from "@/hooks/queries"; import { useDeviceEvents } from "@/hooks/useDeviceEvents"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { @@ -32,7 +31,7 @@ import React from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -export const Route = createFileRoute("/_authenticated/room/")({ +export const Route = createFileRoute("/_auth/room/")({ head: () => ({ meta: [{ title: "Danh sách phòng" }], }), @@ -42,10 +41,7 @@ export const Route = createFileRoute("/_authenticated/room/")({ function RoomComponent() { const navigate = useNavigate(); - const { data: roomData = [], isLoading } = useQueryData({ - queryKey: ["rooms"], - url: API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST, - }); + const { data: roomData = [], isLoading } = useGetRoomList(); const [sorting, setSorting] = React.useState([]); diff --git a/src/routes/_authenticated.tsx b/src/routes/_authenticated.tsx deleted file mode 100644 index 3f5734c..0000000 --- a/src/routes/_authenticated.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' -import AppLayout from '@/layouts/app-layout' - -export const Route = createFileRoute('/_authenticated')({ - // Kiểm tra auth trước khi render - // beforeLoad: async ({context}) => { - // const {authToken} = context.auth - // if (!authToken) { - // throw redirect({to: '/login'}) - // } - // }, - component: AuthenticatedLayout, -}) - -function AuthenticatedLayout() { - return ( - - - - ) -} \ No newline at end of file diff --git a/src/services/app-version.service.ts b/src/services/app-version.service.ts new file mode 100644 index 0000000..04f5aa0 --- /dev/null +++ b/src/services/app-version.service.ts @@ -0,0 +1,127 @@ +import axios, { type AxiosProgressEvent } from "axios"; +import { API_ENDPOINTS } from "@/config/api"; +import type { Version } from "@/types/file"; + +/** + * Lấy danh sách phiên bản agent + */ +export async function getAgentVersion(): Promise { + const response = await axios.get(API_ENDPOINTS.APP_VERSION.GET_VERSION); + return response.data; +} + +/** + * Lấy danh sách phần mềm + */ +export async function getSoftwareList(): Promise { + const response = await axios.get(API_ENDPOINTS.APP_VERSION.GET_SOFTWARE); + return response.data; +} + +/** + * Upload file phần mềm/agent + * @param formData - FormData chứa file + * @param onUploadProgress - Callback theo dõi tiến độ upload + */ +export async function uploadSoftware( + formData: FormData, + onUploadProgress?: (progressEvent: AxiosProgressEvent) => void +): Promise<{ message: string }> { + const response = await axios.post(API_ENDPOINTS.APP_VERSION.UPLOAD, formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + onUploadProgress, + }); + return response.data; +} + +/** + * Lấy danh sách blacklist + */ +export async function getBlacklist(): Promise { + const response = await axios.get(API_ENDPOINTS.APP_VERSION.GET_BLACKLIST); + return response.data; +} + +/** + * Thêm ứng dụng vào blacklist + * @param data - Dữ liệu thêm blacklist + */ +export async function addBlacklist(data: any): Promise<{ message: string }> { + const response = await axios.post(API_ENDPOINTS.APP_VERSION.ADD_BLACKLIST, data); + return response.data; +} + +/** + * Xóa ứng dụng khỏi blacklist + * @param appId - ID ứng dụng + */ +export async function deleteBlacklist(appId: number): Promise<{ message: string }> { + const response = await axios.delete( + API_ENDPOINTS.APP_VERSION.DELETE_BLACKLIST(appId) + ); + return response.data; +} + +/** + * Cập nhật blacklist + * @param appId - ID ứng dụng + * @param data - Dữ liệu cập nhật + */ +export async function updateBlacklist(appId: string, data: any): Promise<{ message: string }> { + const response = await axios.put( + API_ENDPOINTS.APP_VERSION.UPDATE_BLACKLIST(appId), + data + ); + return response.data; +} + +/** + * Yêu cầu cập nhật blacklist + * @param data - Dữ liệu yêu cầu + */ +export async function requestUpdateBlacklist(data: any): Promise<{ message: string }> { + const response = await axios.post( + API_ENDPOINTS.APP_VERSION.REQUEST_UPDATE_BLACKLIST, + data + ); + return response.data; +} + +/** + * Lấy danh sách file bắt buộc + */ +export async function getRequiredFiles(): Promise { + const response = await axios.get(API_ENDPOINTS.APP_VERSION.GET_REQUIRED_FILES); + return response.data; +} + +/** + * Thêm file bắt buộc + * @param data - Dữ liệu file + */ +export async function addRequiredFile(data: any): Promise<{ message: string }> { + const response = await axios.post(API_ENDPOINTS.APP_VERSION.ADD_REQUIRED_FILE, data); + return response.data; +} + +/** + * Xóa file bắt buộc + * @param fileId - ID file + */ +export async function deleteRequiredFile(fileId: number): Promise<{ message: string }> { + const response = await axios.delete( + API_ENDPOINTS.APP_VERSION.DELETE_REQUIRED_FILE(fileId) + ); + return response.data; +} + +/** + * Xóa file từ server + * @param fileId - ID file + */ +export async function deleteFile(fileId: number): Promise<{ message: string }> { + const response = await axios.delete(API_ENDPOINTS.APP_VERSION.DELETE_FILES(fileId)); + return response.data; +} diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts new file mode 100644 index 0000000..ba55917 --- /dev/null +++ b/src/services/auth.service.ts @@ -0,0 +1,86 @@ +import axios from "axios"; +import { API_ENDPOINTS } from "@/config/api"; +import type { LoginResquest, LoginResponse } from "@/types/auth"; + +/** + * Đăng nhập + * @param credentials - Thông tin đăng nhập + * @returns Response chứa token, name, username, access, role + */ +export async function login(credentials: LoginResquest): Promise { + const response = await axios.post( + API_ENDPOINTS.AUTH.LOGIN, + credentials + ); + return response.data; +} + +/** + * Đăng xuất + */ +export async function logout(): Promise { + await axios.delete(API_ENDPOINTS.AUTH.LOGOUT); +} + +/** + * Kiểm tra phiên đăng nhập + * @param token - Access token + * @returns Response kiểm tra phiên + */ +export async function ping(token?: string): Promise<{ message: string; code: number }> { + const response = await axios.get(API_ENDPOINTS.AUTH.PING, { + params: token ? { token } : undefined, + }); + return response.data; +} + +/** + * Lấy CSRF token + * @returns CSRF token + */ +export async function getCsrfToken(): Promise<{ token: string }> { + const response = await axios.get(API_ENDPOINTS.AUTH.CSRF_TOKEN); + return response.data; +} + +/** + * Thay đổi mật khẩu của user hiện tại + * @param data - Dữ liệu thay đổi mật khẩu {currentPassword, newPassword} + */ +export async function changePassword(data: { + currentPassword: string; + newPassword: string; +}): Promise<{ message: string }> { + const response = await axios.put(API_ENDPOINTS.AUTH.CHANGE_PASSWORD, data); + return response.data; +} + +/** + * Admin thay đổi mật khẩu của user khác + * @param data - Dữ liệu {username, newPassword} + */ +export async function changePasswordAdmin(data: { + username: string; + newPassword: string; +}): Promise<{ message: string }> { + const response = await axios.put( + API_ENDPOINTS.AUTH.CHANGE_PASSWORD_ADMIN, + data + ); + return response.data; +} + +/** + * Tạo tài khoản mới + * @param data - Dữ liệu tạo tài khoản + */ +export async function createAccount(data: { + userName: string; + password: string; + name: string; + roleId: number; + accessBuildings?: number[]; +}): Promise<{ message: string }> { + const response = await axios.post(API_ENDPOINTS.AUTH.CREATE_ACCOUNT, data); + return response.data; +} diff --git a/src/services/command.service.ts b/src/services/command.service.ts new file mode 100644 index 0000000..5604ab4 --- /dev/null +++ b/src/services/command.service.ts @@ -0,0 +1,43 @@ +import axios from "axios"; +import { API_ENDPOINTS } from "@/config/api"; + +/** + * Lấy danh sách lệnh + */ +export async function getCommandList(): Promise { + const response = await axios.get(API_ENDPOINTS.COMMAND.GET_COMMANDS); + return response.data; +} + +/** + * Thêm lệnh mới + * @param data - Dữ liệu lệnh + */ +export async function addCommand(data: any): Promise { + const response = await axios.post(API_ENDPOINTS.COMMAND.ADD_COMMAND, data); + return response.data; +} + +/** + * Cập nhật lệnh + * @param commandId - ID lệnh + * @param data - Dữ liệu cập nhật + */ +export async function updateCommand(commandId: number, data: any): Promise { + const response = await axios.put( + API_ENDPOINTS.COMMAND.UPDATE_COMMAND(commandId), + data + ); + return response.data; +} + +/** + * Xóa lệnh + * @param commandId - ID lệnh + */ +export async function deleteCommand(commandId: number): Promise { + const response = await axios.delete( + API_ENDPOINTS.COMMAND.DELETE_COMMAND(commandId) + ); + return response.data; +} diff --git a/src/services/device-comm.service.ts b/src/services/device-comm.service.ts new file mode 100644 index 0000000..2092069 --- /dev/null +++ b/src/services/device-comm.service.ts @@ -0,0 +1,115 @@ +import axios from "axios"; +import { API_ENDPOINTS } from "@/config/api"; +import type { DeviceHealthCheck } from "@/types/device"; + +/** + * Lấy tất cả thiết bị trong hệ thống + */ +export async function getAllDevices(): Promise { + const response = await axios.get(API_ENDPOINTS.DEVICE_COMM.GET_ALL_DEVICES); + return response.data; +} + +/** + * Lấy danh sách phòng + */ +export async function getRoomList(): Promise { + const response = await axios.get(API_ENDPOINTS.DEVICE_COMM.GET_ROOM_LIST); + return response.data; +} + +/** + * Lấy danh sách thiết bị trong phòng + * @param roomName - Tên phòng + */ +export async function getDeviceFromRoom(roomName: string): Promise { + const response = await axios.get( + API_ENDPOINTS.DEVICE_COMM.GET_DEVICE_FROM_ROOM(roomName) + ); + return response.data; +} + +/** + * Tải file về từ phòng + * @param roomName - Tên phòng + * @param data - Dữ liệu yêu cầu + */ +export async function downloadFiles(roomName: string, data: any): Promise { + const response = await axios.post( + API_ENDPOINTS.DEVICE_COMM.DOWNLOAD_FILES(roomName), + data + ); + return response.data; +} + +/** + * Cài đặt MSI file cho phòng + * @param roomName - Tên phòng + * @param data - Dữ liệu MSI + */ +export async function installMsi(roomName: string, data: any): Promise { + const response = await axios.post( + API_ENDPOINTS.DEVICE_COMM.INSTALL_MSI(roomName), + data + ); + return response.data; +} + +/** + * Cập nhật agent cho phòng + * @param roomName - Tên phòng + * @param data - Dữ liệu cập nhật + */ +export async function updateAgent(roomName: string, data: any): Promise { + const response = await axios.post( + API_ENDPOINTS.DEVICE_COMM.UPDATE_AGENT(roomName), + data + ); + return response.data; +} + +/** + * Cập nhật blacklist cho phòng + * @param roomName - Tên phòng + * @param data - Dữ liệu blacklist + */ +export async function updateBlacklist(roomName: string, data: any): Promise { + const response = await axios.post( + API_ENDPOINTS.DEVICE_COMM.UPDATE_BLACKLIST(roomName), + data + ); + return response.data; +} + +/** + * Gửi lệnh shell cho phòng + * @param roomName - Tên phòng + * @param data - Dữ liệu lệnh + */ +export async function sendCommand(roomName: string, data: any): Promise { + const response = await axios.post( + API_ENDPOINTS.DEVICE_COMM.SEND_COMMAND(roomName), + data + ); + return response.data; +} + +/** + * Thay đổi phòng của thiết bị + * @param data - Dữ liệu chuyển phòng + */ +export async function changeDeviceRoom(data: any): Promise { + const response = await axios.post(API_ENDPOINTS.DEVICE_COMM.CHANGE_DEVICE_ROOM, data); + return response.data; +} + +/** + * Lấy trạng thái folder client + * @param roomName - Tên phòng + */ +export async function getClientFolderStatus(roomName: string): Promise { + const response = await axios.get( + API_ENDPOINTS.DEVICE_COMM.REQUEST_GET_CLIENT_FOLDER_STATUS(roomName) + ); + return response.data; +} diff --git a/src/services/device.service.ts b/src/services/device.service.ts deleted file mode 100644 index d60cbbc..0000000 --- a/src/services/device.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import axios from "axios"; -import { queryClient } from "@/main"; -import { API_ENDPOINTS } from "@/config/api"; -import type { DeviceHealthCheck } from "@/types/device"; - -export async function fetchDevicesFromRoom( - roomName: string -): Promise { - const data = await queryClient.ensureQueryData({ - queryKey: ["devices-from-room", roomName], - queryFn: async () => { - const response = await axios.get( - API_ENDPOINTS.DEVICE_COMM.GET_DEVICE_FROM_ROOM(roomName) - ); - return response.data ?? []; - }, - staleTime: 1000 * 60 * 3, - }); - - return data; -} diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..1d8c66d --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,12 @@ +// Auth API Services +export * as authService from "./auth.service"; + +// App Version API Services +export * as appVersionService from "./app-version.service"; + +// Device Communication API Services +export * as deviceCommService from "./device-comm.service"; + +// Command API Services +export * as commandService from "./command.service"; + diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts new file mode 100644 index 0000000..15db26b --- /dev/null +++ b/src/stores/authStore.ts @@ -0,0 +1,93 @@ +import { create } from "zustand"; +import { type IAuthContext } from "@/types/auth"; +import { useEffect } from "react"; + +export interface AuthState + extends Omit { + setAuthenticated: (value: boolean) => void; + setAuth: ( + username: string, + token: string, + name: string, + acs: number[], + role: { roleName: string; priority: number } + ) => void; + initialize: () => void; +} + +const getStoredUser = () => localStorage.getItem("username"); + +export const useAuthStore = create((set) => ({ + isAuthenticated: false, + username: "", + token: "", + name: "", + acs: [], + role: { + roleName: "", + priority: -1, + }, + + initialize: () => { + const username = getStoredUser(); + const token = localStorage.getItem("token") || ""; + const name = localStorage.getItem("name") || ""; + const acsString = localStorage.getItem("acs"); + const acs = acsString ? acsString.split(",").map(Number) : []; + const roleName = localStorage.getItem("role") || ""; + const priority = localStorage.getItem("priority") || "-1"; + + set({ + username: username || "", + token, + name, + acs, + role: { + roleName, + priority: parseInt(priority), + }, + isAuthenticated: !!username, + }); + }, + + setAuth: ( + username: string, + token: string, + name: string, + acs: number[], + role: { roleName: string; priority: number } + ) => set({ username, token, name, acs, role }), + + setAuthenticated: (value: boolean) => set({ isAuthenticated: value }), + + logout: () => { + localStorage.removeItem("token"); + localStorage.removeItem("name"); + localStorage.removeItem("username"); + localStorage.removeItem("acs"); + localStorage.removeItem("role"); + localStorage.removeItem("priority"); + + set({ + isAuthenticated: false, + username: "", + token: "", + name: "", + acs: [], + role: { + roleName: "", + priority: -1, + }, + }); + }, +})); + +// Hook to initialize auth store +export function useAuthStoreInitializer() { + const initialize = useAuthStore((state) => state.initialize); + + useEffect(() => { + const cleanup = initialize(); + return cleanup; + }, [initialize]); +} diff --git a/src/template/app-manager-template.tsx b/src/template/app-manager-template.tsx index a3c6a69..dcef799 100644 --- a/src/template/app-manager-template.tsx +++ b/src/template/app-manager-template.tsx @@ -20,7 +20,7 @@ import { DeviceSearchDialog } from "@/components/bars/device-searchbar"; import { UploadVersionForm } from "@/components/forms/upload-file-form"; import type { Room } from "@/types/room"; import { mapRoomsToSelectItems } from "@/helpers/mapRoomToSelectItems"; -import { fetchDevicesFromRoom } from "@/services/device.service"; +import { getDeviceFromRoom } from "@/services/device-comm.service"; interface AppManagerTemplateProps { title: string; @@ -241,7 +241,7 @@ export function AppManagerTemplate({ setTimeout(() => window.location.reload(), 500); }} rooms={rooms} - fetchDevices={fetchDevicesFromRoom} + fetchDevices={getDeviceFromRoom} onSelect={async (deviceIds) => { if (!onUpdate) { setDialogOpen(false); @@ -299,7 +299,7 @@ export function AppManagerTemplate({ setTimeout(() => window.location.reload(), 500); }} rooms={rooms} - fetchDevices={fetchDevicesFromRoom} + fetchDevices={getDeviceFromRoom} onSelect={async (deviceIds) => { if (!onDownload) { setDialogOpen(false); diff --git a/src/template/command-submit-template.tsx b/src/template/command-submit-template.tsx index 647e57c..27fd81c 100644 --- a/src/template/command-submit-template.tsx +++ b/src/template/command-submit-template.tsx @@ -24,7 +24,7 @@ import { RequestUpdateMenu } from "@/components/menu/request-update-menu"; import { SelectDialog } from "@/components/dialogs/select-dialog"; import { DeviceSearchDialog } from "@/components/bars/device-searchbar"; import { mapRoomsToSelectItems } from "@/helpers/mapRoomToSelectItems"; -import { fetchDevicesFromRoom } from "@/services/device.service"; +import { getDeviceFromRoom } from "@/services/device-comm.service"; import type { Room } from "@/types/room"; import { toast } from "sonner"; @@ -335,7 +335,7 @@ export function CommandSubmitTemplate({ setTimeout(() => window.location.reload(), 500); }} rooms={rooms} - fetchDevices={fetchDevicesFromRoom} + fetchDevices={getDeviceFromRoom} onSelect={async (deviceIds) => { if (!onExecuteSelected) { setDialogOpen2(false); @@ -393,7 +393,7 @@ export function CommandSubmitTemplate({ setTimeout(() => window.location.reload(), 500); }} rooms={rooms} - fetchDevices={fetchDevicesFromRoom} + fetchDevices={getDeviceFromRoom} onSelect={async (deviceIds) => { try { await handleExecuteCustom(deviceIds); diff --git a/src/template/form-submit-template.tsx b/src/template/form-submit-template.tsx index 072ca3c..9d7d68e 100644 --- a/src/template/form-submit-template.tsx +++ b/src/template/form-submit-template.tsx @@ -13,7 +13,7 @@ import { SelectDialog } from "@/components/dialogs/select-dialog"; import { DeviceSearchDialog } from "@/components/bars/device-searchbar"; import type { Room } from "@/types/room"; import { mapRoomsToSelectItems } from "@/helpers/mapRoomToSelectItems"; -import { fetchDevicesFromRoom } from "@/services/device.service"; +import { getDeviceFromRoom } from "@/services/device-comm.service"; interface FormSubmitTemplateProps { title: string; @@ -141,7 +141,7 @@ export function FormSubmitTemplate({ setDialogType(null); }} rooms={rooms} - fetchDevices={fetchDevicesFromRoom} + fetchDevices={getDeviceFromRoom} onSelect={async (deviceIds) => { if (!onSubmit) return; try { diff --git a/src/template/table-manager-template.tsx b/src/template/table-manager-template.tsx index 8d8348b..033f1c1 100644 --- a/src/template/table-manager-template.tsx +++ b/src/template/table-manager-template.tsx @@ -18,7 +18,7 @@ import { BlacklistForm } from "@/components/forms/black-list-form"; import type { BlacklistFormData } from "@/types/black-list"; import type { Room } from "@/types/room"; import { mapRoomsToSelectItems } from "@/helpers/mapRoomToSelectItems"; -import { fetchDevicesFromRoom } from "@/services/device.service"; +import { getDeviceFromRoom } from "@/services/device-comm.service"; interface BlackListManagerTemplateProps { title: string; @@ -141,7 +141,7 @@ export function BlackListManagerTemplate({ open={dialogOpen && dialogType === "device"} onClose={() => setDialogOpen(false)} rooms={rooms} - fetchDevices={fetchDevicesFromRoom} // ⬅ thêm vào đây + fetchDevices={getDeviceFromRoom} // ⬅ thêm vào đây onSelect={(deviceIds) => onUpdate && onUpdate(deviceIds)} /> )} diff --git a/src/types/app-sidebar.ts b/src/types/app-sidebar.ts new file mode 100644 index 0000000..8f9fec3 --- /dev/null +++ b/src/types/app-sidebar.ts @@ -0,0 +1,59 @@ +import { AppWindow, Building, CircleX, Home, Terminal } from "lucide-react"; +import { PermissionEnum } from "./permission"; + +enum AppSidebarSectionCode { + DASHBOARD = 1, + DEVICES = 2, + DOOR = 4, + DOOR_LAYOUT = 5, + BUILDING_DASHBOARD = 6, + SETUP_DOOR = 7, + DOOR_STATUS = 8, + DEPARTMENTS = 9, + DEPARTMENT_PATHS = 10, + SCHEDULES = 11, + ACCESS_STATUS = 12, + ACCESS_HISTORY = 13, + CONFIG_MANAGER, + APP_VERSION_MANAGER, + DEVICES_APP_VERSION, + HEALTHCHEAK, + LIST_ROLES, + ACCOUNT_PERMISSION, + LIST_ACCOUNT, + DOOR_WARNING, + COMMAND_HISTORY, + ACCESS_ILLEGAL, + ZONES, + MANTRAP, + ROLES, + DEVICES_SYNC_BIO, +} + +export const appSidebarSection = { + versions: ["1.0.1", "1.1.0-alpha", "2.0.0-beta1"], + navMain: [ + { title: "Dashboard", to: "/", icon: Home }, + { + title: "Danh sách phòng", + to: "/room", + icon: Building, + }, + { + title: "Quản lý Agent", + to: "/agent", + icon: AppWindow, + }, + { + title: "Quản lý phần mềm", + to: "/apps", + icon: AppWindow, + }, + { title: "Gửi lệnh CMD", to: "/command", icon: Terminal }, + { + title: "Danh sách đen", + to: "/blacklist", + icon: CircleX, + }, + ], +}; diff --git a/src/types/auth.ts b/src/types/auth.ts new file mode 100644 index 0000000..a059cb3 --- /dev/null +++ b/src/types/auth.ts @@ -0,0 +1,30 @@ +export interface IAuthContext { + isAuthenticated: boolean; + logout: () => void; + username: string; + token: string; + name: string; + acs: number[]; + role: { + roleName: string; + priority: number; + }; +} + +export type LoginResquest = { + username: string; + password: string; +}; + +export type LoginResponse = { + token: string | null; + name: string | null; + username: string | null; + access: number[] | null; + message: string | null; + code: number | null; + role: { + roleName: string; + priority: number; + }; +}; diff --git a/src/App.tsx b/src/types/command-registry.ts similarity index 100% rename from src/App.tsx rename to src/types/command-registry.ts diff --git a/src/types/permission.ts b/src/types/permission.ts new file mode 100644 index 0000000..350731c --- /dev/null +++ b/src/types/permission.ts @@ -0,0 +1,144 @@ +export type Permission = { + id: number; + name: string; + code: string; + parentId: number | null; + enum: PermissionEnum; +}; + +export type PermissionOnRole = { + permisionId: number; + roleId: string; + isChecked: number; + permissionName: string; + permissionCode: string; + permissionEnum: PermissionEnum; + parentId: number | null; +}; + +export enum PermissionEnum { + //ACCESS_OPERATION + ACCESS_OPERATION = 10, + VIEW_ACCESSES = 11, + VIEW_ACCESS_HISTORY = 12, + + //APP_CONFIG_OPERATION + APP_CONFIG_OPERATION = 20, + CREATE_APP_CONFIG = 21, + VIEW_APP_CONFIG = 22, + EDIT_APP_CONFIG = 23, + DEL_APP_CONFIG = 24, + + //BIOMETRIC_OPERATION + BIOMETRIC_OPERATION = 30, + VIEW_GUEST = 31, + GET_BIO = 32, + GET_SEND_BIO_STATUS = 33, + + //BUILDING_OPERATION + BUILDING_OPERATION = 40, + VIEW_BUILDING = 41, + CREATE_BUILDING = 42, + EDIT_BUILDING = 43, + CREATE_LV = 45, + DEL_BUILDING = 44, + + //COMMAND_OPERATION + COMMAND_OPERATION = 50, + VIEW_COMMAND = 51, + + //DEPARTMENT_OPERATION + DEPARTMENT_OPERATION = 60, + VIEW_DEP = 61, + CREATE_DEP = 62, + EDIT_DEP = 63, + DEL_DEP = 64, + VIEW_PATH = 65, + + //DEVICE_OPERATION + DEVICE_OPERATION = 70, + DEL_DEVICE = 71, + EDIT_DEVICE = 73, + VIEW_DEVICE = 74, + + //DOOR_OPERATION + DOOR_OPERATION = 80, + SET_DOOR_POSITION = 85, + RESET_DOOR_POSITION = 86, + VIEW_DOOR = 81, + ADD_DOOR = 82, + EDIT_DOOR = 83, + DEL_DOOR = 84, + ADD_DEVICE_TO_DOOR = 87, + REMOVE_DEVICE_FROM_DOOR = 88, + + SEND_COMMAND = 801, + SEND_EMERGENCY = 803, + CONTROL_DOOR = 805, + + //LEVEL_OPERATION + LEVEL_OPERATION = 90, + UPLOAD_LAYOUT = 91, + VIEW_LEVEL_IN_BUILDING = 92, + EDIT_LV = 93, + DEL_LV = 94, + + //PATH_OPERATION + PATH_OPERATION = 100, + CREATE_PATH = 102, + EDIT_PATH = 103, + DEL_PATH = 104, + + //PERMISSION_OPERATION + PERMISSION_OPERATION = 110, + VIEW_ALL_PER = 111, + CRE_PER = 112, + DEL_PER = 114, + VIEW_ACCOUNT_BUILDING = 115, + EDIT_ACCOUNT_BUILDING = 116, + + //ZONE_OPERATION + ZONE_OPERATION = 120, + CREATE_ZONE = 122, + EDIT_ZONE = 123, + DEL_ZONE = 124, + VIEW_ZONE = 121, + + //SCHEDULE_OPERATION + SCHEDULE_OPERATION = 130, + DEL_SCHEDULE = 134, + CREATE_SCHEDULE = 132, + EDIT_SCHEDULE = 133, + VIEW_ALL_SCHEDULE = 131, + + //WARNING_OPERATION + WARNING_OPERATION = 140, + VIEW_WARNING = 141, + + //USER_OPERATION + USER_OPERATION = 150, + VIEW_USER_ROLE = 151, + VIEW_USER = 152, + EDIT_USER_ROLE = 153, + CRE_USER = 154, + + //ROLE_OPERATION + ROLE_OPERATION = 160, + VIEW_ROLES = 161, + CRE_ROLE = 162, + VIEW_ROLE_PER = 165, + EDIT_ROLE_PER = 163, + DEL_ROLE = 164, + + // APP VERSION + APP_VERSION_OPERATION = 170, + VIEW_APP_VERSION = 171, + UPLOAD_APK = 172, + + CHANGE_PASSWORD = 2, + + //Undefined + UNDEFINED = 9999, + + ALLOW_ALL = 0 +}