mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-17 06:00:20 +02:00
Update scanner (frontend)
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export * from './queries/task-list';
|
||||
export * from './mutations/cancel-all-tasks';
|
||||
export * from './mutations/cancel-task';
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
import { api } from '@/renderer/api';
|
||||
import { queryKeys } from '@/renderer/api/query-keys';
|
||||
import { TaskListResponse } from '@/renderer/api/tasks.api';
|
||||
import { ApiError, NullResponse } from '@/renderer/api/types';
|
||||
|
||||
export const useCancelAllTasks = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation<
|
||||
NullResponse,
|
||||
AxiosError<ApiError>,
|
||||
null,
|
||||
{ previous: TaskListResponse | undefined }
|
||||
>({
|
||||
mutationFn: () => api.tasks.cancelAllTasks(),
|
||||
onError: (_err, _variables, context) => {
|
||||
if (!context?.previous) return;
|
||||
queryClient.setQueryData(queryKeys.servers.list(), context.previous);
|
||||
},
|
||||
onMutate: () => {
|
||||
const queryKey = queryKeys.tasks.list();
|
||||
queryClient.cancelQueries(queryKey);
|
||||
const previous = queryClient.getQueryData<TaskListResponse>(queryKey);
|
||||
|
||||
if (!previous) return undefined;
|
||||
|
||||
queryClient.setQueryData(queryKey, { ...previous, data: [] });
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(queryKeys.tasks.list());
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AxiosError } from 'axios';
|
||||
import { api } from '@/renderer/api';
|
||||
import { queryKeys } from '@/renderer/api/query-keys';
|
||||
import { TaskListResponse, TaskResponse } from '@/renderer/api/tasks.api';
|
||||
import { ApiError } from '@/renderer/api/types';
|
||||
|
||||
export const useCancelTask = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation<
|
||||
TaskResponse,
|
||||
AxiosError<ApiError>,
|
||||
{ query: { taskId: string } },
|
||||
{ previous: TaskListResponse | undefined }
|
||||
>({
|
||||
mutationFn: ({ query }) => api.tasks.cancelTask(query),
|
||||
onError: (_err, _variables, context) => {
|
||||
if (!context?.previous) return;
|
||||
queryClient.setQueryData(queryKeys.servers.list(), context.previous);
|
||||
},
|
||||
onMutate: () => {
|
||||
const queryKey = queryKeys.tasks.list();
|
||||
queryClient.cancelQueries(queryKey);
|
||||
const previous = queryClient.getQueryData<TaskListResponse>(queryKey);
|
||||
|
||||
if (!previous) return undefined;
|
||||
|
||||
queryClient.setQueryData(queryKey, { ...previous, data: [] });
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(queryKeys.tasks.list());
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '@/renderer/api';
|
||||
import { queryKeys } from '@/renderer/api/query-keys';
|
||||
import { TaskListResponse } from '@/renderer/api/tasks.api';
|
||||
import { QueryOptions } from '@/renderer/lib/react-query';
|
||||
|
||||
export const useTaskList = (options?: QueryOptions<TaskListResponse>) => {
|
||||
return useQuery({
|
||||
queryFn: ({ signal }) => api.tasks.getActiveTasks(signal),
|
||||
queryKey: queryKeys.tasks.list(),
|
||||
select: useCallback((data: TaskListResponse) => {
|
||||
return {
|
||||
...data,
|
||||
data: data.data.map((task) => {
|
||||
return { ...task, note: `${task.server?.name} - ${task.message}` };
|
||||
}),
|
||||
};
|
||||
}, []),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Group } from '@mantine/core';
|
||||
import { FiActivity } from 'react-icons/fi';
|
||||
import { Button, Text } from '@/renderer/components';
|
||||
import { Text } from '@/renderer/components';
|
||||
import { ActivityMenu } from '@/renderer/features/titlebar/components/activity-menu';
|
||||
import { AppMenu } from '@/renderer/features/titlebar/components/app-menu';
|
||||
import { useAuthStore } from '@/renderer/store';
|
||||
import { Font } from '@/renderer/styles';
|
||||
@@ -69,14 +69,7 @@ export const Titlebar = ({ children }: TitlebarProps) => {
|
||||
<Group spacing="xs">
|
||||
{isAuthenticated && (
|
||||
<>
|
||||
<Button
|
||||
px={5}
|
||||
size="xs"
|
||||
sx={{ color: 'var(--titlebar-fg)' }}
|
||||
variant="subtle"
|
||||
>
|
||||
<FiActivity size={15} />
|
||||
</Button>
|
||||
<ActivityMenu />
|
||||
<AppMenu />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Group } from '@mantine/core';
|
||||
import { FiActivity } from 'react-icons/fi';
|
||||
import { RiRefreshLine } from 'react-icons/ri';
|
||||
import { socket } from '@/renderer/api';
|
||||
import { Button, Popover, Text } from '@/renderer/components';
|
||||
import { useTaskList } from '@/renderer/features/tasks';
|
||||
import { rotating } from '@/renderer/styles';
|
||||
|
||||
const StyledActivitySvg = styled(RiRefreshLine)`
|
||||
${rotating}
|
||||
animation: rotating 1s linear infinite;
|
||||
`;
|
||||
|
||||
export const ActivityMenu = () => {
|
||||
const [isTaskRunning, setIsTaskRunning] = useState(false);
|
||||
const { data: tasks, refetch } = useTaskList({
|
||||
onSuccess: (data) => {
|
||||
if (data.data.length === 0) {
|
||||
return setIsTaskRunning(false);
|
||||
}
|
||||
|
||||
return setIsTaskRunning(true);
|
||||
},
|
||||
refetchInterval: isTaskRunning ? 5000 : undefined,
|
||||
});
|
||||
|
||||
// const cancelTask = useCancelTask();
|
||||
// const cancelAllTasks = useCancelAllTasks();
|
||||
|
||||
// const handleCancelTask = (taskId: string) => {
|
||||
// cancelTask.mutate(
|
||||
// { query: { taskId } },
|
||||
// {
|
||||
// onSuccess: () => {
|
||||
// toast.info({ message: 'Task cancelled' });
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
// };
|
||||
|
||||
// const handleCancelAllTasks = (taskId: string) => {
|
||||
// cancelAllTasks.mutate(null, {
|
||||
// onSuccess: () => {
|
||||
// toast.info({ message: 'All tasks cancelled' });
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
socket.on('task:started', () => {
|
||||
setTimeout(() => refetch(), 1000);
|
||||
setIsTaskRunning(true);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.off('task:started');
|
||||
};
|
||||
}, [refetch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover withArrow withinPortal>
|
||||
<Popover.Target>
|
||||
<Button
|
||||
px={5}
|
||||
size="xs"
|
||||
sx={{ color: 'var(--titlebar-fg)' }}
|
||||
variant="subtle"
|
||||
>
|
||||
{isTaskRunning ? (
|
||||
<StyledActivitySvg size={15} />
|
||||
) : (
|
||||
<FiActivity size={15} />
|
||||
)}
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
{isTaskRunning ? (
|
||||
tasks?.data?.map((task) => (
|
||||
<Group key={task.id} position="apart">
|
||||
<Text>{task.note}</Text>
|
||||
</Group>
|
||||
))
|
||||
) : (
|
||||
<Text>No tasks running</Text>
|
||||
)}
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,11 @@
|
||||
import { Group } from '@mantine/core';
|
||||
import { openModal, closeAllModals } from '@mantine/modals';
|
||||
import { RiArrowLeftLine, RiLogoutBoxLine, RiMenu3Fill } from 'react-icons/ri';
|
||||
import {
|
||||
RiArrowLeftLine,
|
||||
RiLock2Line,
|
||||
RiLogoutBoxLine,
|
||||
RiMenu3Fill,
|
||||
} from 'react-icons/ri';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Button, DropdownMenu } from '@/renderer/components';
|
||||
import {
|
||||
@@ -15,11 +21,16 @@ export const AppMenu = () => {
|
||||
const logout = useAuthStore((state) => state.logout);
|
||||
const currentServer = useAuthStore((state) => state.currentServer);
|
||||
const setCurrentServer = useAuthStore((state) => state.setCurrentServer);
|
||||
const serverCredentials = useAuthStore((state) => state.serverCredentials);
|
||||
const permissions = usePermissions();
|
||||
const { data: servers } = useServerList();
|
||||
|
||||
const serverList =
|
||||
servers?.data?.map((s) => ({ id: s.id, label: `${s.name}` })) ?? [];
|
||||
servers?.data?.map((s) => ({
|
||||
id: s.id,
|
||||
label: `${s.name}`,
|
||||
noCredential: s.noCredential,
|
||||
})) ?? [];
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
@@ -49,7 +60,7 @@ export const AppMenu = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu withinPortal position="bottom" width={200}>
|
||||
<DropdownMenu withArrow withinPortal position="bottom" width={200}>
|
||||
<DropdownMenu.Target>
|
||||
<Button
|
||||
px={5}
|
||||
@@ -62,24 +73,38 @@ export const AppMenu = () => {
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
<DropdownMenu.Label>Server switcher</DropdownMenu.Label>
|
||||
{serverList.map((s) => (
|
||||
<DropdownMenu.Item
|
||||
key={`server-${s.id}`}
|
||||
rightSection={
|
||||
s.id === currentServer?.id ? <RiArrowLeftLine /> : undefined
|
||||
}
|
||||
sx={{
|
||||
color:
|
||||
s.id === currentServer?.id ? 'var(--primary-color)' : undefined,
|
||||
}}
|
||||
onClick={() => handleSetCurrentServer(s.id)}
|
||||
>
|
||||
{s.label}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
{serverList.map((s) => {
|
||||
const requiresCredential = !serverCredentials.some(
|
||||
(c) => c.serverId === s.id && c.enabled
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
key={`server-${s.id}`}
|
||||
disabled={requiresCredential}
|
||||
rightSection={
|
||||
s.id === currentServer?.id ? <RiArrowLeftLine /> : undefined
|
||||
}
|
||||
sx={{
|
||||
color:
|
||||
s.id === currentServer?.id
|
||||
? 'var(--primary-color)'
|
||||
: undefined,
|
||||
}}
|
||||
onClick={() => handleSetCurrentServer(s.id)}
|
||||
>
|
||||
<Group>
|
||||
{requiresCredential && (
|
||||
<RiLock2Line color="var(--danger-color)" />
|
||||
)}
|
||||
{s.label}
|
||||
</Group>
|
||||
</DropdownMenu.Item>
|
||||
);
|
||||
})}
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Item disabled>Search</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Configure</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Settings</DropdownMenu.Item>
|
||||
<DropdownMenu.Divider />
|
||||
{permissions.createServer && (
|
||||
<DropdownMenu.Item onClick={handleAddServerModal}>
|
||||
|
||||
Reference in New Issue
Block a user