diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7841d76ae..044d32bbe 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,6 +33,7 @@ body: options: # The below comment is used to insert a new version with on-release.yml #NEXT_VERSION# + - 1.42.1 - 1.42.0 - 1.41.0 - 1.40.0 diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e09f51e56..eddf9b48d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,12 +3,12 @@ extends: ["config:recommended"], packageRules: [ { - matchPackagePatterns: ["^@homarr/"], + matchPackageNames: ["^@homarr/"], enabled: false, }, // Reenable once https://github.com/privatenumber/tsx/issues/737 is fixed { - matchPackagePatterns: ["tsx"], + matchPackageNames: ["tsx"], enabled: false, }, { diff --git a/.github/workflows/on-pr-renovate-validate.yml b/.github/workflows/on-pr-renovate-validate.yml new file mode 100644 index 000000000..a6096c033 --- /dev/null +++ b/.github/workflows/on-pr-renovate-validate.yml @@ -0,0 +1,18 @@ +name: "[Renovate] Validate configuration" + +permissions: + contents: read + +on: + pull_request: + branches: ["*"] + paths: [".github/renovate.json5"] + +jobs: + renovate-validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - run: | + npx --yes --package renovate -- \ + renovate-config-validator --strict .github/renovate.json5 diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 91d735420..215ba0f1a 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -50,15 +50,15 @@ "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0", - "@mantine/colors-generator": "^8.3.4", - "@mantine/core": "^8.3.4", - "@mantine/dropzone": "^8.3.4", - "@mantine/hooks": "^8.3.4", - "@mantine/modals": "^8.3.4", - "@mantine/tiptap": "^8.3.4", + "@mantine/colors-generator": "^8.3.5", + "@mantine/core": "^8.3.5", + "@mantine/dropzone": "^8.3.5", + "@mantine/hooks": "^8.3.5", + "@mantine/modals": "^8.3.5", + "@mantine/tiptap": "^8.3.5", "@million/lint": "1.0.14", "@tabler/icons-react": "^3.35.0", - "@tanstack/react-query": "^5.90.2", + "@tanstack/react-query": "^5.90.5", "@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-next-experimental": "^5.90.2", "@trpc/client": "^11.6.0", @@ -76,7 +76,7 @@ "glob": "^11.0.3", "jotai": "^2.15.0", "mantine-react-table": "2.0.0-beta.9", - "next": "15.5.5", + "next": "15.5.6", "postcss-preset-mantine": "^1.18.0", "prismjs": "^1.30.0", "react": "19.2.0", @@ -85,7 +85,7 @@ "react-simple-code-editor": "^0.14.1", "sass": "^1.93.2", "superjson": "2.2.2", - "swagger-ui-react": "^5.29.4", + "swagger-ui-react": "^5.29.5", "use-deep-compare-effect": "^1.8.1", "zod": "^4.1.12" }, @@ -94,13 +94,13 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/chroma-js": "3.1.1", - "@types/node": "^22.18.10", + "@types/node": "^22.18.11", "@types/prismjs": "^1.26.5", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "@types/swagger-ui-react": "^5.18.0", "concurrently": "^9.2.1", - "eslint": "^9.37.0", + "eslint": "^9.38.0", "node-loader": "^2.1.0", "prettier": "^3.6.2", "typescript": "^5.9.3" 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 1d7f8aed8..ce6a24c28 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -19,6 +19,7 @@ import { } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; +import { useSession } from "@homarr/auth/client"; import { useRequiredBoard } from "@homarr/boards/context"; import { useEditMode } from "@homarr/boards/edit-mode"; import { revalidatePathActionAsync } from "@homarr/common/client"; @@ -62,6 +63,7 @@ export const BoardContentHeaderActions = () => { }; const AddMenu = () => { + const { data: session } = useSession(); const { openModal: openCategoryEditModal } = useModalAction(CategoryEditModal); const { openModal: openItemSelectModal } = useModalAction(ItemSelectModal); const { openModal: openAppSelectModal } = useModalAction(AppSelectModal); @@ -96,12 +98,13 @@ const AddMenu = () => { const handleSelectApp = useCallback(() => { openAppSelectModal({ - onSelect: (appId) => { + onSelect: (app) => { createItem({ kind: "app", - options: { appId }, + options: { appId: app.id }, }); }, + withCreate: session?.user.permissions.includes("app-create") ?? false, }); }, [openAppSelectModal, createItem]); diff --git a/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx index 8f1b576c9..9dd9dbceb 100644 --- a/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/integrations/edit/[id]/_integration-edit-form.tsx @@ -3,16 +3,18 @@ import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Alert, Button, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core"; -import { IconInfoCircle } from "@tabler/icons-react"; -import type { z } from "zod/v4"; +import { Alert, Anchor, Button, ButtonGroup, Fieldset, Group, Stack, Text, TextInput } from "@mantine/core"; +import { IconInfoCircle, IconPencil, IconPlus, IconUnlink } from "@tabler/icons-react"; +import { z } from "zod/v4"; import type { RouterOutputs } from "@homarr/api"; import { clientApi } from "@homarr/api/client"; +import { useSession } from "@homarr/auth/client"; import { revalidatePathActionAsync } from "@homarr/common/client"; import { getAllSecretKindOptions, getDefaultSecretKinds } from "@homarr/definitions"; import { useZodForm } from "@homarr/form"; -import { useConfirmModal } from "@homarr/modals"; +import { useConfirmModal, useModalAction } from "@homarr/modals"; +import { AppSelectModal } from "@homarr/modals-collection"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useI18n } from "@homarr/translation/client"; import { integrationUpdateSchema } from "@homarr/validation/integration"; @@ -27,6 +29,19 @@ interface EditIntegrationForm { integration: RouterOutputs["integration"]["byId"]; } +const formSchema = integrationUpdateSchema.omit({ id: true, appId: true }).and( + z.object({ + app: z + .object({ + id: z.string(), + name: z.string(), + iconUrl: z.string(), + href: z.string().nullable(), + }) + .nullable(), + }), +); + export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { const t = useI18n(); const { openConfirmModal } = useConfirmModal(); @@ -40,7 +55,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { const hasUrlSecret = initialSecretsKinds.includes("url"); const router = useRouter(); - const form = useZodForm(integrationUpdateSchema.omit({ id: true }), { + const form = useZodForm(formSchema, { initialValues: { name: integration.name, url: integration.url, @@ -48,6 +63,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { kind, value: integration.secrets.find((secret) => secret.kind === kind)?.value ?? "", })), + app: integration.app ?? null, }, }); const { mutateAsync, isPending } = clientApi.integration.update.useMutation(); @@ -55,7 +71,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { const secretsMap = new Map(integration.secrets.map((secret) => [secret.kind, secret])); - const handleSubmitAsync = async (values: FormType) => { + const handleSubmitAsync = async ({ app, ...values }: FormType) => { const url = hasUrlSecret ? new URL(values.secrets.find((secret) => secret.kind === "url")?.value ?? values.url).origin : values.url; @@ -68,6 +84,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { kind: secret.kind, value: secret.value === "" ? null : secret.value, })), + appId: app?.id ?? null, }, { onSuccess: (data) => { @@ -102,7 +119,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => { form.values.secrets.length === initialSecretsKinds.length; return ( -