Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84f7ed97fc | ||
|
|
dc1e1891fb |
No files matched your search
@@ -1,7 +1,14 @@
|
|||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import SuperJSON from "superjson";
|
||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
import { eq } from "@homarr/db";
|
||||||
import { getServerSettingsAsync } from "@homarr/db/queries";
|
import { getServerSettingsAsync } from "@homarr/db/queries";
|
||||||
|
import { boards, items } from "@homarr/db/schema";
|
||||||
|
|
||||||
import type { WidgetOptionsSettings } from "../../../../widgets/src";
|
import type { WidgetOptionsSettings } from "../../../../widgets/src";
|
||||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc";
|
||||||
|
import { throwIfActionForbiddenAsync } from "../board/board-access";
|
||||||
|
|
||||||
export const optionsRouter = createTRPCRouter({
|
export const optionsRouter = createTRPCRouter({
|
||||||
getWidgetOptionSettings: publicProcedure.query(async ({ ctx }): Promise<WidgetOptionsSettings> => {
|
getWidgetOptionSettings: publicProcedure.query(async ({ ctx }): Promise<WidgetOptionsSettings> => {
|
||||||
@@ -16,4 +23,33 @@ export const optionsRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
saveItemOptions: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
itemId: z.string(),
|
||||||
|
boardId: z.string(),
|
||||||
|
newOptions: z.record(z.string(), z.unknown()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.boardId), "modify");
|
||||||
|
|
||||||
|
const item = await ctx.db.query.items.findFirst({
|
||||||
|
where: eq(items.id, input.itemId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item?.boardId !== input.boardId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Specified item was not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = SuperJSON.parse<Record<string, unknown>>(item.options);
|
||||||
|
Object.assign(options, input.newOptions);
|
||||||
|
await ctx.db
|
||||||
|
.update(items)
|
||||||
|
.set({ options: SuperJSON.stringify(options) })
|
||||||
|
.where(eq(items.id, input.itemId));
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
@@ -98,7 +98,6 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/tags/puid"
|
| "/docs/tags/puid"
|
||||||
| "/docs/tags/redis"
|
| "/docs/tags/redis"
|
||||||
| "/docs/tags/responsive"
|
| "/docs/tags/responsive"
|
||||||
| "/docs/tags/restore"
|
|
||||||
| "/docs/tags/roles"
|
| "/docs/tags/roles"
|
||||||
| "/docs/tags/search-engines"
|
| "/docs/tags/search-engines"
|
||||||
| "/docs/tags/search"
|
| "/docs/tags/search"
|
||||||
@@ -180,6 +179,7 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/integrations/github"
|
| "/docs/integrations/github"
|
||||||
| "/docs/integrations/gitlab"
|
| "/docs/integrations/gitlab"
|
||||||
| "/docs/integrations/glances"
|
| "/docs/integrations/glances"
|
||||||
|
| "/docs/integrations/gotify"
|
||||||
| "/docs/integrations/home-assistant"
|
| "/docs/integrations/home-assistant"
|
||||||
| "/docs/integrations/ical"
|
| "/docs/integrations/ical"
|
||||||
| "/docs/integrations/immich"
|
| "/docs/integrations/immich"
|
||||||
@@ -233,7 +233,6 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/management/search-engines"
|
| "/docs/management/search-engines"
|
||||||
| "/docs/management/settings"
|
| "/docs/management/settings"
|
||||||
| "/docs/management/tasks"
|
| "/docs/management/tasks"
|
||||||
| "/docs/management/tools/backup"
|
|
||||||
| "/docs/management/users"
|
| "/docs/management/users"
|
||||||
| "/docs/widgets/anchor-note"
|
| "/docs/widgets/anchor-note"
|
||||||
| "/docs/widgets/app"
|
| "/docs/widgets/app"
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
.beszelStatsSystemToggle {
|
.beszelStatsSystemToggle {
|
||||||
opacity: 0;
|
position: absolute;
|
||||||
transition: opacity 150ms ease;
|
top: 6px;
|
||||||
}
|
left: 6px;
|
||||||
|
z-index: 1;
|
||||||
.beszelStatsWrapper:hover .beszelStatsSystemToggle {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { ActionIcon, Button, Center, Menu, ScrollArea, Select, SimpleGrid, Stack, Text } from "@mantine/core";
|
import { Button, Center, Menu, ScrollArea, Select, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||||
import { IconPlugConnectedX, IconServer, IconQuestionMark } from "@tabler/icons-react";
|
import { IconPlugConnectedX, IconServer, IconQuestionMark } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
import { useSession } from "@homarr/auth/client";
|
||||||
|
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||||
|
import { useOptionalBoard } from "@homarr/boards/context";
|
||||||
import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types";
|
import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
@@ -49,9 +52,15 @@ export default function BeszelSystemStatsWidget({
|
|||||||
integrationIds,
|
integrationIds,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
width,
|
width,
|
||||||
|
boardId,
|
||||||
|
itemId,
|
||||||
setOptions,
|
setOptions,
|
||||||
}: WidgetComponentProps<"beszelSystemStats">) {
|
}: WidgetComponentProps<"beszelSystemStats">) {
|
||||||
const t = useScopedI18n("widget.beszelSystemStats");
|
const t = useScopedI18n("widget.beszelSystemStats");
|
||||||
|
const board = useOptionalBoard();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const hasChangeAccess = board ? constructBoardPermissions(board, session).hasChangeAccess : false;
|
||||||
|
const { mutate: saveItemOptions } = clientApi.widget.options.saveItemOptions.useMutation();
|
||||||
const { data: systemsResult = [], isPending: systemsPending } = clientApi.widget.beszel.getSystems.useQuery(
|
const { data: systemsResult = [], isPending: systemsPending } = clientApi.widget.beszel.getSystems.useQuery(
|
||||||
{ integrationIds },
|
{ integrationIds },
|
||||||
{ staleTime: 30 * 1000 },
|
{ staleTime: 30 * 1000 },
|
||||||
@@ -68,8 +77,13 @@ export default function BeszelSystemStatsWidget({
|
|||||||
const systemReady = !systemsPending && (selectedSystem === "" || systemExists);
|
const systemReady = !systemsPending && (selectedSystem === "" || systemExists);
|
||||||
|
|
||||||
const handleSelectSystem = useCallback(
|
const handleSelectSystem = useCallback(
|
||||||
(value: string) => setOptions({ newOptions: { systemId: value } }),
|
(value: string) => {
|
||||||
[setOptions],
|
setOptions({ newOptions: { systemId: value } });
|
||||||
|
if (hasChangeAccess && boardId && itemId) {
|
||||||
|
saveItemOptions({ boardId, itemId, newOptions: { systemId: value } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setOptions, hasChangeAccess, boardId, itemId, saveItemOptions],
|
||||||
);
|
);
|
||||||
|
|
||||||
const showDocker = options.showDockerCpu || options.showDockerMemory || options.showDockerNetwork;
|
const showDocker = options.showDockerCpu || options.showDockerMemory || options.showDockerNetwork;
|
||||||
@@ -208,40 +222,27 @@ export default function BeszelSystemStatsWidget({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea
|
<ScrollArea h="100%" style={{ pointerEvents: (isEditMode && "none") || undefined }}>
|
||||||
h="100%"
|
|
||||||
className={classes.beszelStatsWrapper}
|
|
||||||
style={{ pointerEvents: (isEditMode && "none") || undefined }}
|
|
||||||
>
|
|
||||||
<Stack gap="md" p="sm">
|
<Stack gap="md" p="sm">
|
||||||
{!isEditMode && systems.length > 1 && (
|
{!isEditMode && systems.length > 1 && (
|
||||||
<Menu
|
<Menu position="bottom-start" withArrow shadow="md" withinPortal>
|
||||||
trigger="click-hover"
|
|
||||||
openDelay={100}
|
|
||||||
closeDelay={300}
|
|
||||||
position="bottom-start"
|
|
||||||
withArrow
|
|
||||||
shadow="md"
|
|
||||||
withinPortal
|
|
||||||
>
|
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<ActionIcon
|
<Button
|
||||||
variant="subtle"
|
variant="default"
|
||||||
size="sm"
|
size="compact-xs"
|
||||||
|
leftSection={<IconServer size={14} />}
|
||||||
className={classes.beszelStatsSystemToggle}
|
className={classes.beszelStatsSystemToggle}
|
||||||
style={{ position: "absolute", top: 6, left: 6, zIndex: 1 }}
|
|
||||||
title={selectedLabel}
|
|
||||||
>
|
>
|
||||||
<IconServer size={14} />
|
{selectedLabel}
|
||||||
</ActionIcon>
|
</Button>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
{systems.map((s) => (
|
{systems.map((s) => (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key={s.value}
|
key={s.value}
|
||||||
fz="xs"
|
fz="xs"
|
||||||
fw={(s.value === selectedSystem && 600) || 400}
|
fw={s.value === selectedSystem ? 600 : 400}
|
||||||
c={(s.value !== selectedSystem && "dimmed") || undefined}
|
c={s.value !== selectedSystem ? "dimmed" : undefined}
|
||||||
onClick={() => handleSelectSystem(s.value)}
|
onClick={() => handleSelectSystem(s.value)}
|
||||||
>
|
>
|
||||||
{s.label}
|
{s.label}
|
||||||
|
|||||||
Reference in new issue
Block a user