mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
Update scanner (frontend)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { ax } from '@/renderer/lib/axios';
|
import { ax } from '@/renderer/lib/axios';
|
||||||
import { SortOrder } from '@/types';
|
import { SortOrder } from '@/renderer/types';
|
||||||
import {
|
import {
|
||||||
AlbumDetailResponse,
|
AlbumDetailResponse,
|
||||||
AlbumListResponse,
|
AlbumListResponse,
|
||||||
@@ -25,13 +25,19 @@ export type AlbumListParams = PaginationParams & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getAlbumDetail = async (
|
const getAlbumDetail = async (
|
||||||
query: { albumId: number; serverId: string },
|
query: { albumId: string; serverId: string },
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
) => {
|
) => {
|
||||||
const { data } = await ax.get<AlbumDetailResponse>(
|
const { data } = await ax.get<AlbumDetailResponse>(
|
||||||
`/servers/${query.serverId}/albums/${query.albumId}`,
|
`/servers/${query.serverId}/albums/${query.albumId}`,
|
||||||
{ signal }
|
{ signal }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// const songs = data.data.songs?.map((s) => ({
|
||||||
|
// ...s,
|
||||||
|
// streamUrl:
|
||||||
|
// }));
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// import axios from 'axios';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { LoginResponse, PingResponse } from './types';
|
import { LoginResponse, PingResponse } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
import { tasksApi } from '@/renderer/api/tasks.api';
|
||||||
import { albumsApi } from './albums.api';
|
import { albumsApi } from './albums.api';
|
||||||
import { authApi } from './auth.api';
|
import { authApi } from './auth.api';
|
||||||
import { serversApi } from './servers.api';
|
import { serversApi } from './servers.api';
|
||||||
import { usersApi } from './users.api';
|
import { usersApi } from './users.api';
|
||||||
|
|
||||||
|
export * from './sockets.api';
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
albums: albumsApi,
|
albums: albumsApi,
|
||||||
auth: authApi,
|
auth: authApi,
|
||||||
servers: serversApi,
|
servers: serversApi,
|
||||||
|
tasks: tasksApi,
|
||||||
users: usersApi,
|
users: usersApi,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,13 +3,19 @@ import { AlbumListParams } from './albums.api';
|
|||||||
export const queryKeys = {
|
export const queryKeys = {
|
||||||
albums: {
|
albums: {
|
||||||
detail: (albumId: string) => ['albums', albumId] as const,
|
detail: (albumId: string) => ['albums', albumId] as const,
|
||||||
list: (params: AlbumListParams) => ['albums', 'list', params] as const,
|
list: (serverId: string, params: AlbumListParams) =>
|
||||||
|
['albums', 'list', serverId, params] as const,
|
||||||
root: ['albums'],
|
root: ['albums'],
|
||||||
songList: (albumId: string) => ['albums', albumId, 'songs'] as const,
|
songList: (albumId: string) => ['albums', albumId, 'songs'] as const,
|
||||||
},
|
},
|
||||||
ping: (url: string) => ['ping', url] as const,
|
ping: (url: string) => ['ping', url] as const,
|
||||||
servers: {
|
servers: {
|
||||||
list: () => ['servers', 'list'] as const,
|
list: (params?: any) => ['servers', 'list', params] as const,
|
||||||
|
root: ['servers'],
|
||||||
|
},
|
||||||
|
tasks: {
|
||||||
|
list: () => ['tasks', 'list'] as const,
|
||||||
|
root: ['tasks'],
|
||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
detail: (userId: string) => ['users', userId] as const,
|
detail: (userId: string) => ['users', userId] as const,
|
||||||
|
|||||||
@@ -9,14 +9,21 @@ import { ax } from '@/renderer/lib/axios';
|
|||||||
|
|
||||||
export type ServerListResponse = BaseResponse<Server[]>;
|
export type ServerListResponse = BaseResponse<Server[]>;
|
||||||
|
|
||||||
const getServerList = async (signal?: AbortSignal) => {
|
const getServerList = async (
|
||||||
const { data } = await ax.get<ServerListResponse>('/servers', { signal });
|
params?: { enabled?: boolean },
|
||||||
|
signal?: AbortSignal
|
||||||
|
) => {
|
||||||
|
const { data } = await ax.get<ServerListResponse>('/servers', {
|
||||||
|
params,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateServerBody = {
|
export type CreateServerBody = {
|
||||||
legacy?: boolean;
|
legacy?: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
noCredential?: boolean;
|
||||||
password: string;
|
password: string;
|
||||||
type: ServerType;
|
type: ServerType;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -30,6 +37,15 @@ const createServer = async (body: CreateServerBody) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteServer = async (options: { query: { serverId: string } }) => {
|
||||||
|
const { query } = options;
|
||||||
|
const { data } = await ax.post<NullResponse>(
|
||||||
|
`/servers/${query.serverId}`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
const updateServer = async (
|
const updateServer = async (
|
||||||
query: { serverId: string },
|
query: { serverId: string },
|
||||||
body: Partial<CreateServerBody>
|
body: Partial<CreateServerBody>
|
||||||
@@ -78,12 +94,68 @@ const disableUrl = async (query: { serverId: string; urlId: string }) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const deleteFolder = async (query: { serverId: string; folderId: string }) => {
|
||||||
|
// const { data } = await ax.delete<NullResponse>(
|
||||||
|
// `/servers/${query.serverId}/folder/${query.folderId}`
|
||||||
|
// );
|
||||||
|
// return data;
|
||||||
|
// };
|
||||||
|
|
||||||
|
const enableFolder = async (query: { folderId: string; serverId: string }) => {
|
||||||
|
const { data } = await ax.post<NullResponse>(
|
||||||
|
`/servers/${query.serverId}/folder/${query.folderId}/enable`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableFolder = async (query: { folderId: string; serverId: string }) => {
|
||||||
|
const { data } = await ax.post<NullResponse>(
|
||||||
|
`/servers/${query.serverId}/folder/${query.folderId}/disable`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ScanServerBody = {
|
||||||
|
serverFolderId?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const quickScan = async (options: {
|
||||||
|
body: ScanServerBody;
|
||||||
|
query: { serverId: string };
|
||||||
|
}) => {
|
||||||
|
const { body, query } = options;
|
||||||
|
const { data } = await ax.post<NullResponse>(
|
||||||
|
`/servers/${query.serverId}/scan`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fullScan = async (options: {
|
||||||
|
body: ScanServerBody;
|
||||||
|
query: { serverId: string };
|
||||||
|
}) => {
|
||||||
|
const { body, query } = options;
|
||||||
|
const { data } = await ax.post<NullResponse>(
|
||||||
|
`/servers/${query.serverId}/full-scan`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const serversApi = {
|
export const serversApi = {
|
||||||
createServer,
|
createServer,
|
||||||
createUrl,
|
createUrl,
|
||||||
|
deleteServer,
|
||||||
deleteUrl,
|
deleteUrl,
|
||||||
|
disableFolder,
|
||||||
disableUrl,
|
disableUrl,
|
||||||
|
enableFolder,
|
||||||
enableUrl,
|
enableUrl,
|
||||||
|
fullScan,
|
||||||
getServerList,
|
getServerList,
|
||||||
|
quickScan,
|
||||||
updateServer,
|
updateServer,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const jfAuthenticate = async (options: {
|
|||||||
{ pw: password, username },
|
{ pw: password, username },
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'X-Emby-Authorization': `MediaBrowser Client="Sonixd", Device="PC", DeviceId="Sonixd", Version="1.0.0-alpha1"`,
|
'X-Emby-Authorization': `MediaBrowser Client="Feishin", Device="PC", DeviceId="Feishin", Version="1.0.0-alpha1"`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -76,7 +76,7 @@ const ssAuthenticate = async (options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
`${cleanServerUrl}/rest/ping.view?v=1.13.0&c=sonixd&f=json&${token}`
|
`${cleanServerUrl}/rest/ping.view?v=1.13.0&c=Feishin&f=json&${token}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return { token, ...data };
|
return { token, ...data };
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { io } from 'socket.io-client';
|
||||||
|
|
||||||
|
const { username } = JSON.parse(
|
||||||
|
localStorage.getItem('store_authentication') || '{}'
|
||||||
|
).state.permissions;
|
||||||
|
|
||||||
|
export const socket = io('http://localhost:8843', {
|
||||||
|
query: { username },
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { BaseResponse, NullResponse, Task } from '@/renderer/api/types';
|
||||||
|
import { ax } from '@/renderer/lib/axios';
|
||||||
|
|
||||||
|
export type TaskListResponse = BaseResponse<Task[]>;
|
||||||
|
|
||||||
|
const getActiveTasks = async (signal?: AbortSignal) => {
|
||||||
|
const { data } = await ax.get<TaskListResponse>('/tasks', {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelAllTasks = async () => {
|
||||||
|
const { data } = await ax.post<NullResponse>('/tasks/cancel', {});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TaskResponse = BaseResponse<Task>;
|
||||||
|
|
||||||
|
const cancelTask = async (query: { taskId: string }) => {
|
||||||
|
const { data } = await ax.post<TaskResponse>(
|
||||||
|
`/tasks/${query.taskId}/cancel`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tasksApi = {
|
||||||
|
cancelAllTasks,
|
||||||
|
cancelTask,
|
||||||
|
getActiveTasks,
|
||||||
|
};
|
||||||
@@ -84,6 +84,7 @@ export type Server = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
noCredential: boolean;
|
||||||
remoteUserId: string;
|
remoteUserId: string;
|
||||||
serverFolders?: RelatedServerFolder[];
|
serverFolders?: RelatedServerFolder[];
|
||||||
serverPermissions?: RelatedServerPermission[];
|
serverPermissions?: RelatedServerPermission[];
|
||||||
@@ -96,6 +97,7 @@ export type Server = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type RelatedServerFolder = {
|
export type RelatedServerFolder = {
|
||||||
|
enabled: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
lastScannedAt: string | null;
|
lastScannedAt: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -282,6 +284,32 @@ export type RelatedArtist = {
|
|||||||
remoteId: string;
|
remoteId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RelatedServer = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: ServerType;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RelatedUser = {
|
||||||
|
enabled: boolean;
|
||||||
|
id: string;
|
||||||
|
isAdmin: boolean;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Task = {
|
||||||
|
createdAt: string;
|
||||||
|
id: string;
|
||||||
|
isCompleted: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
message: string;
|
||||||
|
server: RelatedServer | null;
|
||||||
|
type: TaskType;
|
||||||
|
updatedAt: string;
|
||||||
|
user: RelatedUser | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type PingResponse = BaseResponse<Ping>;
|
export type PingResponse = BaseResponse<Ping>;
|
||||||
|
|
||||||
export type LoginResponse = BaseResponse<Login>;
|
export type LoginResponse = BaseResponse<Login>;
|
||||||
|
|||||||
@@ -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 { ReactNode } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Group } from '@mantine/core';
|
import { Group } from '@mantine/core';
|
||||||
import { FiActivity } from 'react-icons/fi';
|
import { Text } from '@/renderer/components';
|
||||||
import { Button, Text } from '@/renderer/components';
|
import { ActivityMenu } from '@/renderer/features/titlebar/components/activity-menu';
|
||||||
import { AppMenu } from '@/renderer/features/titlebar/components/app-menu';
|
import { AppMenu } from '@/renderer/features/titlebar/components/app-menu';
|
||||||
import { useAuthStore } from '@/renderer/store';
|
import { useAuthStore } from '@/renderer/store';
|
||||||
import { Font } from '@/renderer/styles';
|
import { Font } from '@/renderer/styles';
|
||||||
@@ -69,14 +69,7 @@ export const Titlebar = ({ children }: TitlebarProps) => {
|
|||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
{isAuthenticated && (
|
{isAuthenticated && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<ActivityMenu />
|
||||||
px={5}
|
|
||||||
size="xs"
|
|
||||||
sx={{ color: 'var(--titlebar-fg)' }}
|
|
||||||
variant="subtle"
|
|
||||||
>
|
|
||||||
<FiActivity size={15} />
|
|
||||||
</Button>
|
|
||||||
<AppMenu />
|
<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 { 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 { useNavigate } from 'react-router';
|
||||||
import { Button, DropdownMenu } from '@/renderer/components';
|
import { Button, DropdownMenu } from '@/renderer/components';
|
||||||
import {
|
import {
|
||||||
@@ -15,11 +21,16 @@ export const AppMenu = () => {
|
|||||||
const logout = useAuthStore((state) => state.logout);
|
const logout = useAuthStore((state) => state.logout);
|
||||||
const currentServer = useAuthStore((state) => state.currentServer);
|
const currentServer = useAuthStore((state) => state.currentServer);
|
||||||
const setCurrentServer = useAuthStore((state) => state.setCurrentServer);
|
const setCurrentServer = useAuthStore((state) => state.setCurrentServer);
|
||||||
|
const serverCredentials = useAuthStore((state) => state.serverCredentials);
|
||||||
const permissions = usePermissions();
|
const permissions = usePermissions();
|
||||||
const { data: servers } = useServerList();
|
const { data: servers } = useServerList();
|
||||||
|
|
||||||
const serverList =
|
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 = () => {
|
const handleLogout = () => {
|
||||||
logout();
|
logout();
|
||||||
@@ -49,7 +60,7 @@ export const AppMenu = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu withinPortal position="bottom" width={200}>
|
<DropdownMenu withArrow withinPortal position="bottom" width={200}>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<Button
|
<Button
|
||||||
px={5}
|
px={5}
|
||||||
@@ -62,24 +73,38 @@ export const AppMenu = () => {
|
|||||||
</DropdownMenu.Target>
|
</DropdownMenu.Target>
|
||||||
<DropdownMenu.Dropdown>
|
<DropdownMenu.Dropdown>
|
||||||
<DropdownMenu.Label>Server switcher</DropdownMenu.Label>
|
<DropdownMenu.Label>Server switcher</DropdownMenu.Label>
|
||||||
{serverList.map((s) => (
|
{serverList.map((s) => {
|
||||||
<DropdownMenu.Item
|
const requiresCredential = !serverCredentials.some(
|
||||||
key={`server-${s.id}`}
|
(c) => c.serverId === s.id && c.enabled
|
||||||
rightSection={
|
);
|
||||||
s.id === currentServer?.id ? <RiArrowLeftLine /> : undefined
|
|
||||||
}
|
return (
|
||||||
sx={{
|
<DropdownMenu.Item
|
||||||
color:
|
key={`server-${s.id}`}
|
||||||
s.id === currentServer?.id ? 'var(--primary-color)' : undefined,
|
disabled={requiresCredential}
|
||||||
}}
|
rightSection={
|
||||||
onClick={() => handleSetCurrentServer(s.id)}
|
s.id === currentServer?.id ? <RiArrowLeftLine /> : undefined
|
||||||
>
|
}
|
||||||
{s.label}
|
sx={{
|
||||||
</DropdownMenu.Item>
|
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.Divider />
|
||||||
<DropdownMenu.Item disabled>Search</DropdownMenu.Item>
|
<DropdownMenu.Item disabled>Search</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item>Configure</DropdownMenu.Item>
|
<DropdownMenu.Item>Settings</DropdownMenu.Item>
|
||||||
<DropdownMenu.Divider />
|
<DropdownMenu.Divider />
|
||||||
{permissions.createServer && (
|
{permissions.createServer && (
|
||||||
<DropdownMenu.Item onClick={handleAddServerModal}>
|
<DropdownMenu.Item onClick={handleAddServerModal}>
|
||||||
|
|||||||
Reference in New Issue
Block a user