fix(deps): update dependency eslint-plugin-react-hooks to v5 (#1280)
* fix(deps): update dependency eslint-plugin-react-hooks to v5 * fix: lint issues after reenabling hook rules * fix: format issues --------- Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ea43ed0ca4
commit
a87c937b69
@@ -49,6 +49,7 @@ export const BoardProvider = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReadySections((previous) => previous.filter((id) => data.sections.some((section) => section.id === id)));
|
setReadySections((previous) => previous.filter((id) => data.sections.some((section) => section.id === id)));
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [data.sections.length, setReadySections]);
|
}, [data.sections.length, setReadySections]);
|
||||||
|
|
||||||
const markAsReady = useCallback((id: string) => {
|
const markAsReady = useCallback((id: string) => {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const ApiKeysManagement = ({ apiKeys }: ApiKeysManagementProps) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[t],
|
||||||
);
|
);
|
||||||
|
|
||||||
const table = useMantineReactTable({
|
const table = useMantineReactTable({
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const UserProfileForm = ({ user }: UserProfileFormProps) => {
|
|||||||
id: user.id,
|
id: user.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[user.id, mutate],
|
[isProviderCredentials, mutate, user.id],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
|||||||
import { z } from "@homarr/validation";
|
import { z } from "@homarr/validation";
|
||||||
|
|
||||||
import type { Item } from "~/app/[locale]/boards/_types";
|
import type { Item } from "~/app/[locale]/boards/_types";
|
||||||
import { useItemActions } from "./item-actions";
|
|
||||||
|
|
||||||
interface InnerProps {
|
interface InnerProps {
|
||||||
gridStack: GridStack;
|
gridStack: GridStack;
|
||||||
@@ -21,7 +20,6 @@ export const ItemMoveModal = createModal<InnerProps>(({ actions, innerProps }) =
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
// Keep track of the maximum width based on the x offset
|
// Keep track of the maximum width based on the x offset
|
||||||
const maxWidthRef = useRef(innerProps.columnCount - innerProps.item.xOffset);
|
const maxWidthRef = useRef(innerProps.columnCount - innerProps.item.xOffset);
|
||||||
const { moveAndResizeItem } = useItemActions();
|
|
||||||
const form = useZodForm(
|
const form = useZodForm(
|
||||||
z.object({
|
z.object({
|
||||||
xOffset: z
|
xOffset: z
|
||||||
@@ -62,7 +60,7 @@ export const ItemMoveModal = createModal<InnerProps>(({ actions, innerProps }) =
|
|||||||
});
|
});
|
||||||
actions.closeModal();
|
actions.closeModal();
|
||||||
},
|
},
|
||||||
[moveAndResizeItem],
|
[actions, innerProps.gridStack, innerProps.item.id],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const GridStackItem = ({
|
|||||||
if (type !== "section") return;
|
if (type !== "section") return;
|
||||||
innerRef.current.gridstackNode.minW = minWidth;
|
innerRef.current.gridstackNode.minW = minWidth;
|
||||||
innerRef.current.gridstackNode.minH = minHeight;
|
innerRef.current.gridstackNode.minH = minHeight;
|
||||||
}, [minWidth, minHeight, innerRef]);
|
}, [minWidth, minHeight, innerRef, type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ export const useGridstack = (section: Omit<Section, "items">, itemIds: string[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only run this effect when the section items change
|
// Only run this effect when the section items change
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [itemIds.length, columnCount]);
|
}, [itemIds.length, columnCount]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
|
|||||||
router.refresh();
|
router.refresh();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [openModal, router]);
|
}, [logoutUrl, openModal, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu width={300} withArrow withinPortal>
|
<Menu width={300} withArrow withinPortal>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const ConfirmModal = createModal<Omit<ConfirmModalProps, "title">>(({ act
|
|||||||
actions.closeModal();
|
actions.closeModal();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[cancelProps?.onClick, onCancel, actions.closeModal],
|
[cancelProps, onCancel, closeOnCancel, actions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleConfirm = useCallback(
|
const handleConfirm = useCallback(
|
||||||
@@ -73,7 +73,7 @@ export const ConfirmModal = createModal<Omit<ConfirmModalProps, "title">>(({ act
|
|||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
},
|
},
|
||||||
[confirmProps?.onClick, onConfirm, actions.closeModal],
|
[confirmProps, onConfirm, closeOnConfirm, actions],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const ModalProvider = ({ children }: PropsWithChildren) => {
|
|||||||
(id: string, canceled?: boolean) => {
|
(id: string, canceled?: boolean) => {
|
||||||
dispatch({ type: "CLOSE", modalId: id, canceled });
|
dispatch({ type: "CLOSE", modalId: id, canceled });
|
||||||
},
|
},
|
||||||
[stateRef, dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const openModalInner: ModalContextProps["openModalInner"] = useCallback(
|
const openModalInner: ModalContextProps["openModalInner"] = useCallback(
|
||||||
@@ -63,10 +63,7 @@ export const ModalProvider = ({ children }: PropsWithChildren) => {
|
|||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCloseModal = useCallback(
|
const handleCloseModal = useCallback(() => state.current && closeModal(state.current.id), [closeModal, state]);
|
||||||
() => state.current && closeModal(state.current.id),
|
|
||||||
[closeModal, state.current?.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
const activeModals = state.modals.filter((modal) => modal.id === state.current?.id || modal.props.keepMounted);
|
const activeModals = state.modals.filter((modal) => modal.id === state.current?.id || modal.props.keepMounted);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const ChildrenActionItem = ({ childrenOptions, action, query }: ChildrenA
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Spotlight.Action renderRoot={renderRoot} onClick={onClick} className={classes.spotlightAction}>
|
<Spotlight.Action renderRoot={renderRoot} onClick={onClick} className={classes.spotlightAction}>
|
||||||
<action.component {...childrenOptions.option} />
|
<action.Component {...childrenOptions.option} />
|
||||||
</Spotlight.Action>
|
</Spotlight.Action>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const SpotlightGroupActionItem = <TOption extends Record<string, unknown>
|
|||||||
closeSpotlightOnTrigger={interaction.type !== "mode" && interaction.type !== "children"}
|
closeSpotlightOnTrigger={interaction.type !== "mode" && interaction.type !== "children"}
|
||||||
className={classes.spotlightAction}
|
className={classes.spotlightAction}
|
||||||
>
|
>
|
||||||
<group.component {...option} />
|
<group.Component {...option} />
|
||||||
</Spotlight.Action>
|
</Spotlight.Action>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const Spotlight = () => {
|
|||||||
|
|
||||||
{childrenOptions ? (
|
{childrenOptions ? (
|
||||||
<Group>
|
<Group>
|
||||||
<childrenOptions.detailComponent options={childrenOptions.option as never} />
|
<childrenOptions.DetailComponent options={childrenOptions.option as never} />
|
||||||
</Group>
|
</Group>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import type { ReactNode } from "react";
|
|||||||
import type { inferSearchInteractionDefinition } from "./interaction";
|
import type { inferSearchInteractionDefinition } from "./interaction";
|
||||||
|
|
||||||
export interface CreateChildrenOptionsProps<TParentOptions extends Record<string, unknown>> {
|
export interface CreateChildrenOptionsProps<TParentOptions extends Record<string, unknown>> {
|
||||||
detailComponent: ({ options }: { options: TParentOptions }) => ReactNode;
|
DetailComponent: ({ options }: { options: TParentOptions }) => ReactNode;
|
||||||
useActions: (options: TParentOptions, query: string) => ChildrenAction<TParentOptions>[];
|
useActions: (options: TParentOptions, query: string) => ChildrenAction<TParentOptions>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChildrenAction<TParentOptions extends Record<string, unknown>> {
|
export interface ChildrenAction<TParentOptions extends Record<string, unknown>> {
|
||||||
key: string;
|
key: string;
|
||||||
component: (option: TParentOptions) => JSX.Element;
|
Component: (option: TParentOptions) => JSX.Element;
|
||||||
useInteraction: (option: TParentOptions, query: string) => inferSearchInteractionDefinition<"link" | "javaScript">;
|
useInteraction: (option: TParentOptions, query: string) => inferSearchInteractionDefinition<"link" | "javaScript">;
|
||||||
hide?: boolean | ((option: TParentOptions) => boolean);
|
hide?: boolean | ((option: TParentOptions) => boolean);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type CommonSearchGroup<TOption extends Record<string, unknown>, TOptionProps ext
|
|||||||
// key path is used to define the path to a unique key in the option object
|
// key path is used to define the path to a unique key in the option object
|
||||||
keyPath: keyof TOption;
|
keyPath: keyof TOption;
|
||||||
title: stringOrTranslation;
|
title: stringOrTranslation;
|
||||||
component: (option: TOption) => JSX.Element;
|
Component: (option: TOption) => JSX.Element;
|
||||||
useInteraction: (option: TOption, query: string) => inferSearchInteractionDefinition<SearchInteraction>;
|
useInteraction: (option: TOption, query: string) => inferSearchInteractionDefinition<SearchInteraction>;
|
||||||
onKeyDown?: (
|
onKeyDown?: (
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const searchInteractions = [
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
useActions: CreateChildrenOptionsProps<any>["useActions"];
|
useActions: CreateChildrenOptionsProps<any>["useActions"];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
detailComponent: CreateChildrenOptionsProps<any>["detailComponent"];
|
DetailComponent: CreateChildrenOptionsProps<any>["DetailComponent"];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
option: any;
|
option: any;
|
||||||
}>(),
|
}>(),
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const appChildrenOptions = createChildrenOptions<App>({
|
|||||||
useActions: () => [
|
useActions: () => [
|
||||||
{
|
{
|
||||||
key: "open",
|
key: "open",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -34,7 +34,7 @@ const appChildrenOptions = createChildrenOptions<App>({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "edit",
|
key: "edit",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -47,7 +47,7 @@ const appChildrenOptions = createChildrenOptions<App>({
|
|||||||
useInteraction: interaction.link(({ id }) => ({ href: `/manage/apps/edit/${id}` })),
|
useInteraction: interaction.link(({ id }) => ({ href: `/manage/apps/edit/${id}` })),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
detailComponent: ({ options }) => {
|
DetailComponent: ({ options }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -75,7 +75,7 @@ const appChildrenOptions = createChildrenOptions<App>({
|
|||||||
export const appsSearchGroup = createGroup<App>({
|
export const appsSearchGroup = createGroup<App>({
|
||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
title: (t) => t("search.mode.appIntegrationBoard.group.app.title"),
|
title: (t) => t("search.mode.appIntegrationBoard.group.app.title"),
|
||||||
component: (app) => (
|
Component: (app) => (
|
||||||
<Group px="md" py="sm">
|
<Group px="md" py="sm">
|
||||||
<Avatar
|
<Avatar
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
|
|||||||
const actions: (ChildrenAction<Board> & { hidden?: boolean })[] = [
|
const actions: (ChildrenAction<Board> & { hidden?: boolean })[] = [
|
||||||
{
|
{
|
||||||
key: "open",
|
key: "open",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -37,7 +37,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "homeBoard",
|
key: "homeBoard",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -61,7 +61,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "settings",
|
key: "settings",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -78,7 +78,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
|
|||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
},
|
},
|
||||||
detailComponent: ({ options: board }) => {
|
DetailComponent: ({ options: board }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -102,7 +102,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
|
|||||||
export const boardsSearchGroup = createGroup<Board>({
|
export const boardsSearchGroup = createGroup<Board>({
|
||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
title: "Boards",
|
title: "Boards",
|
||||||
component: (board) => (
|
Component: (board) => (
|
||||||
<Group px="md" py="sm">
|
<Group px="md" py="sm">
|
||||||
{board.logoImageUrl ? (
|
{board.logoImageUrl ? (
|
||||||
<img src={board.logoImageUrl} alt={board.name} width={24} height={24} />
|
<img src={board.logoImageUrl} alt={board.name} width={24} height={24} />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { interaction } from "../../lib/interaction";
|
|||||||
export const integrationsSearchGroup = createGroup<{ id: string; kind: IntegrationKind; name: string }>({
|
export const integrationsSearchGroup = createGroup<{ id: string; kind: IntegrationKind; name: string }>({
|
||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
title: (t) => t("search.mode.appIntegrationBoard.group.integration.title"),
|
title: (t) => t("search.mode.appIntegrationBoard.group.integration.title"),
|
||||||
component: (integration) => (
|
Component: (integration) => (
|
||||||
<Group px="md" py="sm">
|
<Group px="md" py="sm">
|
||||||
<IntegrationAvatar size="sm" kind={integration.kind} />
|
<IntegrationAvatar size="sm" kind={integration.kind} />
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const languageChildrenOptions = createChildrenOptions<Record<string, unkn
|
|||||||
)
|
)
|
||||||
.map(({ localeKey, attributes }) => ({
|
.map(({ localeKey, attributes }) => ({
|
||||||
key: localeKey,
|
key: localeKey,
|
||||||
component() {
|
Component() {
|
||||||
return (
|
return (
|
||||||
<Group mx="md" my="sm" wrap="nowrap" justify="space-between" w="100%">
|
<Group mx="md" my="sm" wrap="nowrap" justify="space-between" w="100%">
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
@@ -53,7 +53,7 @@ export const languageChildrenOptions = createChildrenOptions<Record<string, unkn
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
detailComponent: () => {
|
DetailComponent: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const newIntegrationChildrenOptions = createChildrenOptions<Record<string
|
|||||||
)
|
)
|
||||||
.map(([kind, integrationDef]) => ({
|
.map(([kind, integrationDef]) => ({
|
||||||
key: kind,
|
key: kind,
|
||||||
component() {
|
Component() {
|
||||||
return (
|
return (
|
||||||
<Group mx="md" my="sm" wrap="nowrap" w="100%">
|
<Group mx="md" my="sm" wrap="nowrap" w="100%">
|
||||||
<IntegrationAvatar kind={kind} size="sm" />
|
<IntegrationAvatar kind={kind} size="sm" />
|
||||||
@@ -31,7 +31,7 @@ export const newIntegrationChildrenOptions = createChildrenOptions<Record<string
|
|||||||
useInteraction: interaction.link(() => ({ href: `/manage/integrations/new?kind=${kind}` })),
|
useInteraction: interaction.link(() => ({ href: `/manage/integrations/new?kind=${kind}` })),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
detailComponent() {
|
DetailComponent() {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const commandMode = {
|
|||||||
keyPath: "commandKey",
|
keyPath: "commandKey",
|
||||||
title: "Global commands",
|
title: "Global commands",
|
||||||
useInteraction: (option, query) => option.useInteraction(option, query),
|
useInteraction: (option, query) => option.useInteraction(option, query),
|
||||||
component: ({ icon: Icon, name }) => (
|
Component: ({ icon: Icon, name }) => (
|
||||||
<Group px="md" py="sm">
|
<Group px="md" py="sm">
|
||||||
<Icon stroke={1.5} />
|
<Icon stroke={1.5} />
|
||||||
<Text>{name}</Text>
|
<Text>{name}</Text>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>(
|
|||||||
useActions: () => [
|
useActions: () => [
|
||||||
{
|
{
|
||||||
key: "search",
|
key: "search",
|
||||||
component: ({ name }) => {
|
Component: ({ name }) => {
|
||||||
const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
|
const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,7 +30,7 @@ export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>(
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
detailComponent({ options }) {
|
DetailComponent({ options }) {
|
||||||
const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
|
const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
|
||||||
return (
|
return (
|
||||||
<Stack mx="md" my="sm">
|
<Stack mx="md" my="sm">
|
||||||
@@ -47,7 +47,7 @@ export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>(
|
|||||||
export const searchEnginesSearchGroups = createGroup<SearchEngine>({
|
export const searchEnginesSearchGroups = createGroup<SearchEngine>({
|
||||||
keyPath: "short",
|
keyPath: "short",
|
||||||
title: (t) => t("search.mode.external.group.searchEngine.title"),
|
title: (t) => t("search.mode.external.group.searchEngine.title"),
|
||||||
component: ({ iconUrl, name, short, description }) => {
|
Component: ({ iconUrl, name, short, description }) => {
|
||||||
return (
|
return (
|
||||||
<Group w="100%" wrap="nowrap" justify="space-between" align="center" px="md" py="xs">
|
<Group w="100%" wrap="nowrap" justify="space-between" align="center" px="md" py="xs">
|
||||||
<Group wrap="nowrap">
|
<Group wrap="nowrap">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const helpMode = {
|
|||||||
keyPath: "character",
|
keyPath: "character",
|
||||||
title: (t) => t("search.mode.help.group.mode.title"),
|
title: (t) => t("search.mode.help.group.mode.title"),
|
||||||
options: searchModesWithoutHelp.map(({ character, modeKey }) => ({ character, modeKey })),
|
options: searchModesWithoutHelp.map(({ character, modeKey }) => ({ character, modeKey })),
|
||||||
component: ({ modeKey, character }) => {
|
Component: ({ modeKey, character }) => {
|
||||||
const t = useScopedI18n(`search.mode.${modeKey}`);
|
const t = useScopedI18n(`search.mode.${modeKey}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -59,7 +59,7 @@ const helpMode = {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
component: (props) => (
|
Component: (props) => (
|
||||||
<Group px="md" py="xs" w="100%" wrap="nowrap" align="center">
|
<Group px="md" py="xs" w="100%" wrap="nowrap" align="center">
|
||||||
<props.icon />
|
<props.icon />
|
||||||
<Text>{props.label}</Text>
|
<Text>{props.label}</Text>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const pagesSearchGroup = createGroup<{
|
|||||||
}>({
|
}>({
|
||||||
keyPath: "path",
|
keyPath: "path",
|
||||||
title: (t) => t("search.mode.page.group.page.title"),
|
title: (t) => t("search.mode.page.group.page.title"),
|
||||||
component: ({ name, icon: Icon }) => (
|
Component: ({ name, icon: Icon }) => (
|
||||||
<Group px="md" py="sm">
|
<Group px="md" py="sm">
|
||||||
<Icon stroke={1.5} />
|
<Icon stroke={1.5} />
|
||||||
<Text>{name}</Text>
|
<Text>{name}</Text>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
|
|||||||
useActions: () => [
|
useActions: () => [
|
||||||
{
|
{
|
||||||
key: "detail",
|
key: "detail",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Group mx="md" my="sm">
|
<Group mx="md" my="sm">
|
||||||
@@ -29,7 +29,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "manageMember",
|
key: "manageMember",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Group mx="md" my="sm">
|
<Group mx="md" my="sm">
|
||||||
@@ -42,7 +42,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "managePermission",
|
key: "managePermission",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Group mx="md" my="sm">
|
<Group mx="md" my="sm">
|
||||||
@@ -54,7 +54,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
|
|||||||
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/groups/${id}/permissions` })),
|
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/groups/${id}/permissions` })),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
detailComponent: ({ options }) => {
|
DetailComponent: ({ options }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Stack mx="md" my="sm">
|
<Stack mx="md" my="sm">
|
||||||
@@ -71,7 +71,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
|
|||||||
export const groupsSearchGroup = createGroup<Group>({
|
export const groupsSearchGroup = createGroup<Group>({
|
||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
title: "Groups",
|
title: "Groups",
|
||||||
component: ({ name }) => (
|
Component: ({ name }) => (
|
||||||
<Group px="md" py="sm">
|
<Group px="md" py="sm">
|
||||||
<Text>{name}</Text>
|
<Text>{name}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const userChildrenOptions = createChildrenOptions<User>({
|
|||||||
useActions: () => [
|
useActions: () => [
|
||||||
{
|
{
|
||||||
key: "detail",
|
key: "detail",
|
||||||
component: () => {
|
Component: () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,7 +30,7 @@ const userChildrenOptions = createChildrenOptions<User>({
|
|||||||
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/${id}/general` })),
|
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/${id}/general` })),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
detailComponent: ({ options }) => {
|
DetailComponent: ({ options }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -49,7 +49,7 @@ const userChildrenOptions = createChildrenOptions<User>({
|
|||||||
export const usersSearchGroup = createGroup<User>({
|
export const usersSearchGroup = createGroup<User>({
|
||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
title: (t) => t("search.mode.userGroup.group.user.title"),
|
title: (t) => t("search.mode.userGroup.group.user.title"),
|
||||||
component: (user) => (
|
Component: (user) => (
|
||||||
<Group px="md" py="sm">
|
<Group px="md" py="sm">
|
||||||
<UserAvatar user={user} size="sm" />
|
<UserAvatar user={user} size="sm" />
|
||||||
<Text>{user.name}</Text>
|
<Text>{user.name}</Text>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const TablePagination = ({ total }: TablePaginationProps) => {
|
|||||||
(control: ControlType) => {
|
(control: ControlType) => {
|
||||||
return getItemProps(calculatePageFor(control, current, total));
|
return getItemProps(calculatePageFor(control, current, total));
|
||||||
},
|
},
|
||||||
[current],
|
[current, getItemProps, total],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
@@ -43,7 +43,7 @@ export const TablePagination = ({ total }: TablePaginationProps) => {
|
|||||||
params.set("page", page.toString());
|
params.set("page", page.toString());
|
||||||
replace(`${pathName}?${params.toString()}`);
|
replace(`${pathName}?${params.toString()}`);
|
||||||
},
|
},
|
||||||
[pathName, searchParams],
|
[pathName, replace, searchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const WidgetLocationInput = ({ property, kind }: CommonWidgetInputProps<"
|
|||||||
form.clearFieldError(`options.${property}.latitude`);
|
form.clearFieldError(`options.${property}.latitude`);
|
||||||
form.clearFieldError(`options.${property}.longitude`);
|
form.clearFieldError(`options.${property}.longitude`);
|
||||||
},
|
},
|
||||||
[handleChange],
|
[form, handleChange, property],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSearch = useCallback(() => {
|
const onSearch = useCallback(() => {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const WidgetMultiTextInput = ({ property, kind, options }: CommonWidgetIn
|
|||||||
success: validationResult.success,
|
success: validationResult.success,
|
||||||
result: validationResult,
|
result: validationResult,
|
||||||
};
|
};
|
||||||
}, [search]);
|
}, [options.validate, search]);
|
||||||
|
|
||||||
const error = React.useMemo(() => {
|
const error = React.useMemo(() => {
|
||||||
/* hide the error when nothing is being typed since "" is not valid but is not an explicit error */
|
/* hide the error when nothing is being typed since "" is not valid but is not an explicit error */
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import "../widgets-common.css";
|
import "../widgets-common.css";
|
||||||
|
|
||||||
import { useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import type { MantineStyleProp } from "@mantine/core";
|
import type { MantineStyleProp } from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
@@ -233,7 +233,19 @@ export default function DownloadClientsWidget({
|
|||||||
)
|
)
|
||||||
//flatMap already sorts by integration by nature, add sorting by integration type (usenet | torrent)
|
//flatMap already sorts by integration by nature, add sorting by integration type (usenet | torrent)
|
||||||
.sort(({ type: typeA }, { type: typeB }) => typeA.length - typeB.length),
|
.sort(({ type: typeA }, { type: typeB }) => typeA.length - typeB.length),
|
||||||
[currentItems, integrationIds, options],
|
[
|
||||||
|
currentItems,
|
||||||
|
integrationIds,
|
||||||
|
integrationsWithInteractions,
|
||||||
|
mutateDeleteItem,
|
||||||
|
mutatePauseItem,
|
||||||
|
mutateResumeItem,
|
||||||
|
options.activeTorrentThreshold,
|
||||||
|
options.categoryFilter,
|
||||||
|
options.filterIsWhitelist,
|
||||||
|
options.showCompletedTorrent,
|
||||||
|
options.showCompletedUsenet,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
//Flatten Clients Array for which each elements has the integration and general client infos.
|
//Flatten Clients Array for which each elements has the integration and general client infos.
|
||||||
@@ -278,7 +290,14 @@ export default function DownloadClientsWidget({
|
|||||||
({ status: statusA }, { status: statusB }) =>
|
({ status: statusA }, { status: statusB }) =>
|
||||||
(statusA?.type.length ?? Infinity) - (statusB?.type.length ?? Infinity),
|
(statusA?.type.length ?? Infinity) - (statusB?.type.length ?? Infinity),
|
||||||
),
|
),
|
||||||
[currentItems, integrationIds, options],
|
[
|
||||||
|
currentItems,
|
||||||
|
integrationIds,
|
||||||
|
integrationsWithInteractions,
|
||||||
|
options.applyFilterToRatio,
|
||||||
|
options.categoryFilter,
|
||||||
|
options.filterIsWhitelist,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
//Check existing types between torrents and usenet
|
//Check existing types between torrents and usenet
|
||||||
@@ -333,37 +352,40 @@ export default function DownloadClientsWidget({
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Base element in common with all columns
|
//Base element in common with all columns
|
||||||
const columnsDefBase = ({
|
const columnsDefBase = useCallback(
|
||||||
key,
|
({
|
||||||
showHeader,
|
key,
|
||||||
align,
|
showHeader,
|
||||||
}: {
|
align,
|
||||||
key: keyof ExtendedDownloadClientItem;
|
}: {
|
||||||
showHeader: boolean;
|
key: keyof ExtendedDownloadClientItem;
|
||||||
align?: "center" | "left" | "right" | "justify" | "char";
|
showHeader: boolean;
|
||||||
}): MRT_ColumnDef<ExtendedDownloadClientItem> => {
|
align?: "center" | "left" | "right" | "justify" | "char";
|
||||||
const style: MantineStyleProp = {
|
}): MRT_ColumnDef<ExtendedDownloadClientItem> => {
|
||||||
minWidth: 0,
|
const style: MantineStyleProp = {
|
||||||
width: "var(--column-width)",
|
minWidth: 0,
|
||||||
height: "var(--ratio-width)",
|
width: "var(--column-width)",
|
||||||
padding: "var(--space-size)",
|
height: "var(--ratio-width)",
|
||||||
transition: "unset",
|
padding: "var(--space-size)",
|
||||||
"--key-width": columnsRatios[key],
|
transition: "unset",
|
||||||
"--column-width": "calc((var(--key-width)/var(--total-width) * 100cqw))",
|
"--key-width": columnsRatios[key],
|
||||||
};
|
"--column-width": "calc((var(--key-width)/var(--total-width) * 100cqw))",
|
||||||
return {
|
};
|
||||||
id: key,
|
return {
|
||||||
accessorKey: key,
|
id: key,
|
||||||
header: key,
|
accessorKey: key,
|
||||||
size: columnsRatios[key],
|
header: key,
|
||||||
mantineTableBodyCellProps: { style, align },
|
size: columnsRatios[key],
|
||||||
mantineTableHeadCellProps: {
|
mantineTableBodyCellProps: { style, align },
|
||||||
style,
|
mantineTableHeadCellProps: {
|
||||||
align: isEditMode ? "center" : align,
|
style,
|
||||||
},
|
align: isEditMode ? "center" : align,
|
||||||
Header: () => (showHeader && !isEditMode ? <Text fw={700}>{t(`items.${key}.columnTitle`)}</Text> : ""),
|
},
|
||||||
};
|
Header: () => (showHeader && !isEditMode ? <Text fw={700}>{t(`items.${key}.columnTitle`)}</Text> : ""),
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
[isEditMode, t],
|
||||||
|
);
|
||||||
|
|
||||||
//Make columns and cell elements, Memoized to data with deps on data and EditMode
|
//Make columns and cell elements, Memoized to data with deps on data and EditMode
|
||||||
const columns = useMemo<MRT_ColumnDef<ExtendedDownloadClientItem>[]>(
|
const columns = useMemo<MRT_ColumnDef<ExtendedDownloadClientItem>[]>(
|
||||||
@@ -580,7 +602,7 @@ export default function DownloadClientsWidget({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[clickedIndex, isEditMode, data, integrationIds, options],
|
[columnsDefBase, t, tCommon],
|
||||||
);
|
);
|
||||||
|
|
||||||
//Table build and config
|
//Table build and config
|
||||||
@@ -704,10 +726,7 @@ interface ItemInfoModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalProps) => {
|
const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalProps) => {
|
||||||
const item = useMemo<ExtendedDownloadClientItem | undefined>(
|
const item = useMemo<ExtendedDownloadClientItem | undefined>(() => items[currentIndex], [items, currentIndex]);
|
||||||
() => items[currentIndex],
|
|
||||||
[items, currentIndex, opened],
|
|
||||||
);
|
|
||||||
const t = useScopedI18n("widget.downloads.states");
|
const t = useScopedI18n("widget.downloads.states");
|
||||||
//The use case for "No item found" should be impossible, hence no translation
|
//The use case for "No item found" should be impossible, hence no translation
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -57,22 +57,19 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
|||||||
throw new NoIntegrationSelectedError();
|
throw new NoIntegrationSelectedError();
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box h="100%" className="health-monitoring">
|
<Stack h="100%" gap="2.5cqmin" className="health-monitoring">
|
||||||
{healthData.map(({ integrationId, integrationName, healthInfo }) => {
|
{healthData.map(({ integrationId, integrationName, healthInfo }) => {
|
||||||
const memoryUsage = formatMemoryUsage(healthInfo.memAvailable, healthInfo.memUsed);
|
|
||||||
const disksData = matchFileSystemAndSmart(healthInfo.fileSystem, healthInfo.smart);
|
const disksData = matchFileSystemAndSmart(healthInfo.fileSystem, healthInfo.smart);
|
||||||
const { ref, width } = useElementSize();
|
const memoryUsage = formatMemoryUsage(healthInfo.memAvailable, healthInfo.memUsed);
|
||||||
const ringSize = width * 0.95;
|
|
||||||
const ringThickness = width / 10;
|
|
||||||
const progressSize = width * 0.2;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Stack
|
||||||
|
gap="2.5cqmin"
|
||||||
key={integrationId}
|
key={integrationId}
|
||||||
h="100%"
|
h="100%"
|
||||||
className={`health-monitoring-information health-monitoring-${integrationName}`}
|
className={`health-monitoring-information health-monitoring-${integrationName}`}
|
||||||
|
p="2.5cqmin"
|
||||||
>
|
>
|
||||||
<Card className="health-monitoring-information-card" m="2.5cqmin" p="2.5cqmin" withBorder>
|
<Card className="health-monitoring-information-card" p="2.5cqmin" withBorder>
|
||||||
<Flex
|
<Flex
|
||||||
className="health-monitoring-information-card-elements"
|
className="health-monitoring-information-card-elements"
|
||||||
h="100%"
|
h="100%"
|
||||||
@@ -155,95 +152,17 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Box>
|
</Box>
|
||||||
{options.cpu && (
|
{options.cpu && <CpuRing cpuUtilization={healthInfo.cpuUtilization} />}
|
||||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu">
|
|
||||||
<RingProgress
|
|
||||||
className="health-monitoring-cpu-utilization"
|
|
||||||
roundCaps
|
|
||||||
size={ringSize}
|
|
||||||
thickness={ringThickness}
|
|
||||||
label={
|
|
||||||
<Center style={{ flexDirection: "column" }}>
|
|
||||||
<Text
|
|
||||||
className="health-monitoring-cpu-utilization-value"
|
|
||||||
size="3cqmin"
|
|
||||||
>{`${healthInfo.cpuUtilization.toFixed(2)}%`}</Text>
|
|
||||||
<IconCpu className="health-monitoring-cpu-utilization-icon" size="7cqmin" />
|
|
||||||
</Center>
|
|
||||||
}
|
|
||||||
sections={[
|
|
||||||
{
|
|
||||||
value: Number(healthInfo.cpuUtilization.toFixed(2)),
|
|
||||||
color: progressColor(Number(healthInfo.cpuUtilization.toFixed(2))),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{healthInfo.cpuTemp && options.cpu && (
|
{healthInfo.cpuTemp && options.cpu && (
|
||||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu-temperature">
|
<CpuTempRing fahrenheit={options.fahrenheit} cpuTemp={healthInfo.cpuTemp} />
|
||||||
<RingProgress
|
|
||||||
ref={ref}
|
|
||||||
className="health-monitoring-cpu-temp"
|
|
||||||
roundCaps
|
|
||||||
size={ringSize}
|
|
||||||
thickness={ringThickness}
|
|
||||||
label={
|
|
||||||
<Center style={{ flexDirection: "column" }}>
|
|
||||||
<Text className="health-monitoring-cpu-temp-value" size="3cqmin">
|
|
||||||
{options.fahrenheit
|
|
||||||
? `${(healthInfo.cpuTemp * 1.8 + 32).toFixed(1)}°F`
|
|
||||||
: `${healthInfo.cpuTemp}°C`}
|
|
||||||
</Text>
|
|
||||||
<IconCpu className="health-monitoring-cpu-temp-icon" size="7cqmin" />
|
|
||||||
</Center>
|
|
||||||
}
|
|
||||||
sections={[
|
|
||||||
{
|
|
||||||
value: healthInfo.cpuTemp,
|
|
||||||
color: progressColor(healthInfo.cpuTemp),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{options.memory && (
|
|
||||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-memory">
|
|
||||||
<RingProgress
|
|
||||||
className="health-monitoring-memory-use"
|
|
||||||
roundCaps
|
|
||||||
size={ringSize}
|
|
||||||
thickness={ringThickness}
|
|
||||||
label={
|
|
||||||
<Center style={{ flexDirection: "column" }}>
|
|
||||||
<Text className="health-monitoring-memory-value" size="3cqmin">
|
|
||||||
{memoryUsage.memUsed.GB}GiB
|
|
||||||
</Text>
|
|
||||||
<IconBrain className="health-monitoring-memory-icon" size="7cqmin" />
|
|
||||||
</Center>
|
|
||||||
}
|
|
||||||
sections={[
|
|
||||||
{
|
|
||||||
value: Number(memoryUsage.memUsed.percent),
|
|
||||||
color: progressColor(Number(memoryUsage.memUsed.percent)),
|
|
||||||
tooltip: `${memoryUsage.memUsed.percent}%`,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
|
{options.memory && <MemoryRing available={healthInfo.memAvailable} used={healthInfo.memUsed} />}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
{options.fileSystem &&
|
{options.fileSystem &&
|
||||||
disksData.map((disk) => {
|
disksData.map((disk) => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className="health-monitoring-disk-card" key={disk.deviceName} p="2.5cqmin" withBorder>
|
||||||
className="health-monitoring-disk-card"
|
|
||||||
key={disk.deviceName}
|
|
||||||
m="2.5cqmin"
|
|
||||||
p="2.5cqmin"
|
|
||||||
withBorder
|
|
||||||
>
|
|
||||||
<Flex className="health-monitoring-disk-status" justify="space-between" align="center" m="1.5cqmin">
|
<Flex className="health-monitoring-disk-status" justify="space-between" align="center" m="1.5cqmin">
|
||||||
<Group gap="1cqmin">
|
<Group gap="1cqmin">
|
||||||
<IconServer className="health-monitoring-disk-icon" size="5cqmin" />
|
<IconServer className="health-monitoring-disk-icon" size="5cqmin" />
|
||||||
@@ -266,14 +185,14 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
|||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Progress.Root className="health-monitoring-disk-use" size={progressSize}>
|
<Progress.Root className="health-monitoring-disk-use" h="6cqmin">
|
||||||
<Tooltip label={disk.used}>
|
<Tooltip label={disk.used}>
|
||||||
<Progress.Section
|
<Progress.Section
|
||||||
value={disk.percentage}
|
value={disk.percentage}
|
||||||
color={progressColor(disk.percentage)}
|
color={progressColor(disk.percentage)}
|
||||||
className="health-monitoring-disk-use-percentage"
|
className="health-monitoring-disk-use-percentage"
|
||||||
>
|
>
|
||||||
<Progress.Label className="health-monitoring-disk-use-value">
|
<Progress.Label className="health-monitoring-disk-use-value" fz="2.5cqmin">
|
||||||
{t("widget.healthMonitoring.popover.used")}
|
{t("widget.healthMonitoring.popover.used")}
|
||||||
</Progress.Label>
|
</Progress.Label>
|
||||||
</Progress.Section>
|
</Progress.Section>
|
||||||
@@ -291,7 +210,7 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
|||||||
value={100 - disk.percentage}
|
value={100 - disk.percentage}
|
||||||
color="default"
|
color="default"
|
||||||
>
|
>
|
||||||
<Progress.Label className="health-monitoring-disk-available-value">
|
<Progress.Label className="health-monitoring-disk-available-value" fz="2.5cqmin">
|
||||||
{t("widget.healthMonitoring.popover.diskAvailable")}
|
{t("widget.healthMonitoring.popover.diskAvailable")}
|
||||||
</Progress.Label>
|
</Progress.Label>
|
||||||
</Progress.Section>
|
</Progress.Section>
|
||||||
@@ -300,10 +219,10 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +268,95 @@ export const matchFileSystemAndSmart = (fileSystems: FileSystem[], smartData: Sm
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CpuRing = ({ cpuUtilization }: { cpuUtilization: number }) => {
|
||||||
|
const { width, ref } = useElementSize();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu">
|
||||||
|
<RingProgress
|
||||||
|
className="health-monitoring-cpu-utilization"
|
||||||
|
roundCaps
|
||||||
|
size={width * 0.95}
|
||||||
|
thickness={width / 10}
|
||||||
|
label={
|
||||||
|
<Center style={{ flexDirection: "column" }}>
|
||||||
|
<Text
|
||||||
|
className="health-monitoring-cpu-utilization-value"
|
||||||
|
size="3cqmin"
|
||||||
|
>{`${cpuUtilization.toFixed(2)}%`}</Text>
|
||||||
|
<IconCpu className="health-monitoring-cpu-utilization-icon" size="7cqmin" />
|
||||||
|
</Center>
|
||||||
|
}
|
||||||
|
sections={[
|
||||||
|
{
|
||||||
|
value: Number(cpuUtilization.toFixed(2)),
|
||||||
|
color: progressColor(Number(cpuUtilization.toFixed(2))),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CpuTempRing = ({ fahrenheit, cpuTemp }: { fahrenheit: boolean; cpuTemp: number }) => {
|
||||||
|
const { width, ref } = useElementSize();
|
||||||
|
return (
|
||||||
|
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu-temperature">
|
||||||
|
<RingProgress
|
||||||
|
className="health-monitoring-cpu-temp"
|
||||||
|
roundCaps
|
||||||
|
size={width * 0.95}
|
||||||
|
thickness={width / 10}
|
||||||
|
label={
|
||||||
|
<Center style={{ flexDirection: "column" }}>
|
||||||
|
<Text className="health-monitoring-cpu-temp-value" size="3cqmin">
|
||||||
|
{fahrenheit ? `${(cpuTemp * 1.8 + 32).toFixed(1)}°F` : `${cpuTemp}°C`}
|
||||||
|
</Text>
|
||||||
|
<IconCpu className="health-monitoring-cpu-temp-icon" size="7cqmin" />
|
||||||
|
</Center>
|
||||||
|
}
|
||||||
|
sections={[
|
||||||
|
{
|
||||||
|
value: cpuTemp,
|
||||||
|
color: progressColor(cpuTemp),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MemoryRing = ({ available, used }: { available: string; used: string }) => {
|
||||||
|
const { width, ref } = useElementSize();
|
||||||
|
const memoryUsage = formatMemoryUsage(available, used);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box ref={ref} w="100%" h="100%" className="health-monitoring-memory">
|
||||||
|
<RingProgress
|
||||||
|
className="health-monitoring-memory-use"
|
||||||
|
roundCaps
|
||||||
|
size={width * 0.95}
|
||||||
|
thickness={width / 10}
|
||||||
|
label={
|
||||||
|
<Center style={{ flexDirection: "column" }}>
|
||||||
|
<Text className="health-monitoring-memory-value" size="3cqmin">
|
||||||
|
{memoryUsage.memUsed.GB}GiB
|
||||||
|
</Text>
|
||||||
|
<IconBrain className="health-monitoring-memory-icon" size="7cqmin" />
|
||||||
|
</Center>
|
||||||
|
}
|
||||||
|
sections={[
|
||||||
|
{
|
||||||
|
value: Number(memoryUsage.memUsed.percent),
|
||||||
|
color: progressColor(Number(memoryUsage.memUsed.percent)),
|
||||||
|
tooltip: `${memoryUsage.memUsed.percent}%`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const formatMemoryUsage = (memFree: string, memUsed: string) => {
|
export const formatMemoryUsage = (memFree: string, memUsed: string) => {
|
||||||
const memFreeBytes = Number(memFree);
|
const memFreeBytes = Number(memFree);
|
||||||
const memUsedBytes = Number(memUsed);
|
const memUsedBytes = Number(memUsed);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function MediaServerWidget({
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}),
|
}),
|
||||||
[mediaRequests, integrationIds],
|
[mediaRequests],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation();
|
const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation();
|
||||||
|
|||||||
@@ -189,17 +189,31 @@ export function Notebook({ options, isEditMode, boardId, itemId }: WidgetCompone
|
|||||||
|
|
||||||
addEventListener("onReadOnlyCheck", handleOnReadOnlyCheck);
|
addEventListener("onReadOnlyCheck", handleOnReadOnlyCheck);
|
||||||
|
|
||||||
const handleEditToggleCallback = (previous: boolean) => {
|
const handleContentUpdate = useCallback(
|
||||||
const current = !previous;
|
(contentUpdate: string) => {
|
||||||
if (!editor) return current;
|
setToSaveContent(contentUpdate);
|
||||||
editor.setEditable(current);
|
// This is not available in preview mode
|
||||||
|
if (boardId && itemId) {
|
||||||
|
void mutateAsync({ boardId, itemId, content: contentUpdate });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[boardId, itemId, mutateAsync],
|
||||||
|
);
|
||||||
|
|
||||||
handleContentUpdate(content);
|
const handleEditToggleCallback = useCallback(
|
||||||
|
(previous: boolean) => {
|
||||||
|
const current = !previous;
|
||||||
|
if (!editor) return current;
|
||||||
|
editor.setEditable(current);
|
||||||
|
|
||||||
return current;
|
handleContentUpdate(content);
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditCancelCallback = () => {
|
return current;
|
||||||
|
},
|
||||||
|
[content, editor, handleContentUpdate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEditCancelCallback = useCallback(() => {
|
||||||
if (!editor) return false;
|
if (!editor) return false;
|
||||||
editor.setEditable(false);
|
editor.setEditable(false);
|
||||||
|
|
||||||
@@ -207,20 +221,12 @@ export function Notebook({ options, isEditMode, boardId, itemId }: WidgetCompone
|
|||||||
editor.commands.setContent(toSaveContent);
|
editor.commands.setContent(toSaveContent);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}, [editor, toSaveContent]);
|
||||||
|
|
||||||
const handleEditCancel = useCallback(() => {
|
const handleEditCancel = useCallback(() => {
|
||||||
setIsEditing(handleEditCancelCallback);
|
setIsEditing(handleEditCancelCallback);
|
||||||
}, [setIsEditing, handleEditCancelCallback]);
|
}, [setIsEditing, handleEditCancelCallback]);
|
||||||
|
|
||||||
const handleContentUpdate = (contentUpdate: string) => {
|
|
||||||
setToSaveContent(contentUpdate);
|
|
||||||
// This is not available in preview mode
|
|
||||||
if (boardId && itemId) {
|
|
||||||
void mutateAsync({ boardId, itemId, content: contentUpdate });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditToggle = useCallback(() => {
|
const handleEditToggle = useCallback(() => {
|
||||||
setIsEditing(handleEditToggleCallback);
|
setIsEditing(handleEditToggleCallback);
|
||||||
}, [setIsEditing, handleEditToggleCallback]);
|
}, [setIsEditing, handleEditToggleCallback]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useCallback, useState } from "react";
|
||||||
import { Center, Stack, Text, UnstyledButton } from "@mantine/core";
|
import { Center, Stack, Text, UnstyledButton } from "@mantine/core";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
@@ -38,7 +38,7 @@ export default function SmartHomeEntityStateWidget({
|
|||||||
|
|
||||||
const attribute = options.entityUnit.length > 0 ? " " + options.entityUnit : "";
|
const attribute = options.entityUnit.length > 0 ? " " + options.entityUnit : "";
|
||||||
|
|
||||||
const handleClick = React.useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (isEditMode) {
|
if (isEditMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ export default function SmartHomeEntityStateWidget({
|
|||||||
entityId: options.entityId,
|
entityId: options.entityId,
|
||||||
integrationId: integrationIds[0] ?? "",
|
integrationId: integrationIds[0] ?? "",
|
||||||
});
|
});
|
||||||
}, []);
|
}, [integrationIds, isEditMode, mutate, options.clickable, options.entityId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnstyledButton
|
<UnstyledButton
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default function SmartHomeTriggerAutomationWidget({
|
|||||||
automationId: options.automationId,
|
automationId: options.automationId,
|
||||||
integrationId: integrationIds[0] ?? "",
|
integrationId: integrationIds[0] ?? "",
|
||||||
});
|
});
|
||||||
}, [isEditMode]);
|
}, [integrationIds, isEditMode, mutateAsync, options.automationId]);
|
||||||
return (
|
return (
|
||||||
<UnstyledButton onClick={handleClick} style={{ cursor: !isEditMode ? "pointer" : "initial" }} w="100%" h="100%">
|
<UnstyledButton onClick={handleClick} style={{ cursor: !isEditMode ? "pointer" : "initial" }} w="100%" h="100%">
|
||||||
{isShowSuccess && (
|
{isShowSuccess && (
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const Feed = ({ options }: Pick<WidgetComponentProps<"video">, "options">) => {
|
|||||||
() => undefined,
|
() => undefined,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [videoRef]);
|
}, [options.hasAutoPlay, options.hasControls, options.isMuted, videoRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group justify="center" w="100%" h="100%" pos="relative">
|
<Group justify="center" w="100%" h="100%" pos="relative">
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -24,7 +24,7 @@ importers:
|
|||||||
version: 4.3.2(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
|
version: 4.3.2(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
|
||||||
'@vitest/coverage-v8':
|
'@vitest/coverage-v8':
|
||||||
specifier: ^2.1.3
|
specifier: ^2.1.3
|
||||||
version: 2.1.3(vitest@2.1.3)
|
version: 2.1.3(vitest@2.1.3(@types/node@20.16.11)(@vitest/ui@2.1.3)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
|
||||||
'@vitest/ui':
|
'@vitest/ui':
|
||||||
specifier: ^2.1.3
|
specifier: ^2.1.3
|
||||||
version: 2.1.3(vitest@2.1.3)
|
version: 2.1.3(vitest@2.1.3)
|
||||||
@@ -1655,8 +1655,8 @@ importers:
|
|||||||
specifier: ^7.37.1
|
specifier: ^7.37.1
|
||||||
version: 7.37.1(eslint@9.12.0)
|
version: 7.37.1(eslint@9.12.0)
|
||||||
eslint-plugin-react-hooks:
|
eslint-plugin-react-hooks:
|
||||||
specifier: ^4.6.2
|
specifier: ^5.0.0
|
||||||
version: 4.6.2(eslint@9.12.0)
|
version: 5.0.0(eslint@9.12.0)
|
||||||
typescript-eslint:
|
typescript-eslint:
|
||||||
specifier: ^8.9.0
|
specifier: ^8.9.0
|
||||||
version: 8.9.0(eslint@9.12.0)(typescript@5.6.3)
|
version: 8.9.0(eslint@9.12.0)(typescript@5.6.3)
|
||||||
@@ -4795,11 +4795,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
|
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
|
||||||
|
|
||||||
eslint-plugin-react-hooks@4.6.2:
|
eslint-plugin-react-hooks@5.0.0:
|
||||||
resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==}
|
resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
|
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
||||||
|
|
||||||
eslint-plugin-react@7.37.1:
|
eslint-plugin-react@7.37.1:
|
||||||
resolution: {integrity: sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==}
|
resolution: {integrity: sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==}
|
||||||
@@ -9967,7 +9967,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@vitest/coverage-v8@2.1.3(vitest@2.1.3)':
|
'@vitest/coverage-v8@2.1.3(vitest@2.1.3(@types/node@20.16.11)(@vitest/ui@2.1.3)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ampproject/remapping': 2.3.0
|
'@ampproject/remapping': 2.3.0
|
||||||
'@bcoe/v8-coverage': 0.2.3
|
'@bcoe/v8-coverage': 0.2.3
|
||||||
@@ -11361,7 +11361,7 @@ snapshots:
|
|||||||
safe-regex-test: 1.0.3
|
safe-regex-test: 1.0.3
|
||||||
string.prototype.includes: 2.0.0
|
string.prototype.includes: 2.0.0
|
||||||
|
|
||||||
eslint-plugin-react-hooks@4.6.2(eslint@9.12.0):
|
eslint-plugin-react-hooks@5.0.0(eslint@9.12.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.12.0
|
eslint: 9.12.0
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.0",
|
"eslint-plugin-jsx-a11y": "^6.10.0",
|
||||||
"eslint-plugin-react": "^7.37.1",
|
"eslint-plugin-react": "^7.37.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"typescript-eslint": "^8.9.0"
|
"typescript-eslint": "^8.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
3
tooling/eslint/react.js
vendored
3
tooling/eslint/react.js
vendored
@@ -12,9 +12,6 @@ export default [
|
|||||||
rules: {
|
rules: {
|
||||||
...reactPlugin.configs["jsx-runtime"].rules,
|
...reactPlugin.configs["jsx-runtime"].rules,
|
||||||
...hooksPlugin.configs.recommended.rules,
|
...hooksPlugin.configs.recommended.rules,
|
||||||
// context.getSource is not a function
|
|
||||||
"react-hooks/rules-of-hooks": "off",
|
|
||||||
"react-hooks/exhaustive-deps": "off",
|
|
||||||
},
|
},
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
|
|||||||
Reference in New Issue
Block a user