diff --git a/.nvmrc b/.nvmrc index 6fa8dec4c..d5b283a3a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.13.0 +22.13.1 diff --git a/apps/nextjs/next.config.ts b/apps/nextjs/next.config.ts index 5a082309c..53148f725 100644 --- a/apps/nextjs/next.config.ts +++ b/apps/nextjs/next.config.ts @@ -2,6 +2,7 @@ import "@homarr/auth/env"; import "@homarr/db/env"; import "@homarr/common/env"; +import "@homarr/docker/env"; import type { NextConfig } from "next"; import MillionLint from "@million/lint"; diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 81ec38d17..4ac217d27 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -23,8 +23,10 @@ "@homarr/cron-job-status": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", + "@homarr/docker": "workspace:^0.1.0", "@homarr/form": "workspace:^0.1.0", - "@homarr/gridstack": "^1.11.3", + "@homarr/gridstack": "^1.12.0", + "@homarr/icons": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0", "@homarr/log": "workspace:^", "@homarr/modals": "workspace:^0.1.0", @@ -39,18 +41,18 @@ "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0", - "@mantine/colors-generator": "^7.16.0", - "@mantine/core": "^7.16.0", - "@mantine/dropzone": "^7.16.0", - "@mantine/hooks": "^7.16.0", - "@mantine/modals": "^7.16.0", - "@mantine/tiptap": "^7.16.0", + "@mantine/colors-generator": "^7.16.1", + "@mantine/core": "^7.16.1", + "@mantine/dropzone": "^7.16.1", + "@mantine/hooks": "^7.16.1", + "@mantine/modals": "^7.16.1", + "@mantine/tiptap": "^7.16.1", "@million/lint": "1.0.14", "@t3-oss/env-nextjs": "^0.11.1", - "@tabler/icons-react": "^3.28.1", - "@tanstack/react-query": "^5.64.1", - "@tanstack/react-query-devtools": "^5.64.1", - "@tanstack/react-query-next-experimental": "5.64.1", + "@tabler/icons-react": "^3.29.0", + "@tanstack/react-query": "^5.64.2", + "@tanstack/react-query-devtools": "^5.64.2", + "@tanstack/react-query-next-experimental": "5.64.2", "@trpc/client": "next", "@trpc/next": "next", "@trpc/react-query": "next", @@ -64,9 +66,9 @@ "dotenv": "^16.4.7", "flag-icons": "^7.3.2", "glob": "^11.0.1", - "jotai": "^2.11.0", + "jotai": "^2.11.1", "mantine-react-table": "2.0.0-beta.8", - "next": "15.1.5", + "next": "15.1.6", "postcss-preset-mantine": "^1.17.0", "prismjs": "^1.29.0", "react": "19.0.0", diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index 3d2042030..2588cf44a 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -94,6 +94,7 @@ export function TRPCReactProvider(props: PropsWithChildren) { false: unstable_httpBatchStreamLink({ transformer: superjson, url: getTrpcUrl(), + maxURLLength: 2083, // Suggested by tRPC: https://trpc.io/docs/client/links/httpBatchLink#setting-a-maximum-url-length headers: createHeadersCallbackForSource("nextjs-react (json)"), }), }), diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index b0657e1e1..74c617d09 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -2,6 +2,7 @@ import type { MouseEvent } from "react"; import { useCallback, useEffect } from "react"; +import Link from "next/link"; import { useRouter } from "next/navigation"; import { Group, Menu } from "@mantine/core"; import { useHotkeys } from "@mantine/hooks"; @@ -9,9 +10,11 @@ import { IconBox, IconBoxAlignTop, IconChevronDown, + IconLayoutBoard, IconPencil, IconPencilOff, IconPlus, + IconReplace, IconResize, IconSettings, } from "@tabler/icons-react"; @@ -49,6 +52,8 @@ export const BoardContentHeaderActions = () => { + + ); }; @@ -151,6 +156,32 @@ const EditModeMenu = () => { ); }; +const SelectBoardsMenu = () => { + const { data: boards = [] } = clientApi.board.getAllBoards.useQuery(); + + return ( + + + + + + + + {boards.map((board) => ( + } + > + {board.name} + + ))} + + + ); +}; + const usePreventLeaveWithDirty = (isDirty: boolean) => { const t = useI18n(); const { openConfirmModal } = useConfirmModal(); diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx index 188916e9e..bc5746292 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/new/_integration-new-dropdown.tsx @@ -15,7 +15,9 @@ export const IntegrationCreateDropdownContent = () => { const [search, setSearch] = useState(""); const filteredKinds = useMemo(() => { - return integrationKinds.filter((kind) => kind.includes(search.toLowerCase())); + return integrationKinds.filter((kind) => + getIntegrationName(kind).toLowerCase().includes(search.toLowerCase().trim()), + ); }, [search]); const handleSearch = React.useCallback( @@ -29,6 +31,7 @@ export const IntegrationCreateDropdownContent = () => { leftSection={} placeholder={t("integration.page.list.search")} value={search} + data-autofocus onChange={handleSearch} /> diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx index 38a0a2cdf..2daeea657 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/page.tsx @@ -102,7 +102,15 @@ export default async function IntegrationsPage(props: IntegrationsPageProps) { const IntegrationSelectMenu = ({ children }: PropsWithChildren) => { return ( - + {children} diff --git a/apps/nextjs/src/app/[locale]/manage/medias/_actions/copy-media.tsx b/apps/nextjs/src/app/[locale]/manage/medias/_actions/copy-media.tsx index 9c63c05bc..af0387482 100644 --- a/apps/nextjs/src/app/[locale]/manage/medias/_actions/copy-media.tsx +++ b/apps/nextjs/src/app/[locale]/manage/medias/_actions/copy-media.tsx @@ -15,6 +15,11 @@ export const CopyMedia = ({ media }: CopyMediaProps) => { const url = typeof window !== "undefined" ? `${window.location.origin}/api/user-medias/${media.id}` : ""; + // Clipboard only works on localhost or secure connections (https) + if (url.startsWith("http://") && !url.startsWith("http://localhost")) { + return null; + } + return ( {({ copy, copied }) => ( diff --git a/apps/nextjs/src/app/[locale]/manage/medias/page.tsx b/apps/nextjs/src/app/[locale]/manage/medias/page.tsx index 102e54cd8..e5c9b98e1 100644 --- a/apps/nextjs/src/app/[locale]/manage/medias/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/medias/page.tsx @@ -1,13 +1,28 @@ import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { Anchor, Group, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from "@mantine/core"; +import { + ActionIcon, + Anchor, + Group, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Title, + Tooltip, +} from "@mantine/core"; +import { IconExternalLink } from "@tabler/icons-react"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; import { auth } from "@homarr/auth/next"; import { humanFileSize } from "@homarr/common"; import type { inferSearchParamsFromSchema } from "@homarr/common/types"; +import { createLocalImageUrl } from "@homarr/icons/local"; import { getI18n } from "@homarr/translation/server"; import { SearchInput, TablePagination, UserAvatar } from "@homarr/ui"; import { z } from "@homarr/validation"; @@ -91,13 +106,14 @@ interface RowProps { const Row = async ({ media }: RowProps) => { const session = await auth(); + const t = await getI18n(); const canDelete = media.creatorId === session?.user.id || session?.user.permissions.includes("media-full-all"); return ( {media.name} { + + + + + {canDelete && } diff --git a/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx index 0be4bcac3..22c80fedf 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/certificates/page.tsx @@ -59,10 +59,17 @@ export default async function CertificatesPage({ params }: CertificatesPageProps {x509Certificates.map((cert) => ( - - - - {cert.x509.subject} + + + + + {cert.x509.subject} + {cert.fileName} diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx index 5398aaff3..b3d8663d2 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx @@ -16,7 +16,7 @@ import { MantineReactTable } from "mantine-react-table"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; import { useTimeAgo } from "@homarr/common"; -import type { DockerContainerState } from "@homarr/definitions"; +import type { ContainerState } from "@homarr/docker"; import { useModalAction } from "@homarr/modals"; import { AddDockerAppToHomarr } from "@homarr/modals-collection"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; @@ -246,9 +246,9 @@ const containerStates = { exited: "red", removing: "pink", dead: "dark", -} satisfies Record; +} satisfies Record; -const ContainerStateBadge = ({ state }: { state: DockerContainerState }) => { +const ContainerStateBadge = ({ state }: { state: ContainerState }) => { const t = useScopedI18n("docker.field.state.option"); return ( diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx index 1631189dc..c2a975daf 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_ping-icons-enabled.tsx @@ -51,7 +51,10 @@ export const PingIconsEnabled = ({ user }: PingIconsEnabledProps) => { return (
- +