diff --git a/.github/workflows/deployment-docker-image.yml b/.github/workflows/deployment-docker-image.yml
index 3611ae31e..a3bfd26df 100644
--- a/.github/workflows/deployment-docker-image.yml
+++ b/.github/workflows/deployment-docker-image.yml
@@ -38,12 +38,12 @@ jobs:
node-version: [20]
steps:
- name: Discord notification
- if: ${{ github.events.inputs.send-notifications }}
+ if: ${{ github.events.inputs.send-notifications || true }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
with:
- args: "Deployment of an image has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
+ args: "Deployment of an image has been triggered: [run ${{ github.run_number }}](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>)"
- uses: actions/checkout@v4
- name: Get Next Version
id: semver
@@ -52,7 +52,7 @@ jobs:
token: ${{ github.token }}
branch: dev
- name: Discord notification
- if: ${{ github.events.inputs.send-notifications }}
+ if: ${{ github.events.inputs.send-notifications || true }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
@@ -65,9 +65,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- cache: "pnpm"
- name: Discord notification
- if: ${{ github.events.inputs.send-notifications }}
+ if: ${{ github.events.inputs.send-notifications || true }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
@@ -97,19 +96,40 @@ jobs:
- name: Build and push
id: buildPushAction
uses: docker/build-push-action@v6
+ if: ${{ github.events.inputs.push-image == 'true' || github.events.inputs.push-image == null }}
with:
platforms: linux/amd64,linux/arm64
context: .
- push: ${{ github.events.inputs.push-image && 'true' || 'false' }}
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ network: host
+ env:
+ SKIP_ENV_VALIDATION: true
+ - name: Build
+ id: buildPushDryAction
+ uses: docker/build-push-action@v6
+ if: ${{ github.events.inputs.push-image == 'false' }}
+ with:
+ platforms: linux/amd64,linux/arm64
+ context: .
+ push: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
network: host
env:
SKIP_ENV_VALIDATION: true
- name: Discord notification
- if: ${{ github.events.inputs.send-notifications }}
+ if: ${{ github.events.inputs.send-notifications || true && (github.events.inputs.push-image == 'true' || github.events.inputs.push-image == null) }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
uses: Ilshidur/action-discord@master
with:
- args: "Deployment of image has completed. Image ID is '${{ steps.buildPushAction.outputs.imageid }}'. This was a dry run."
+ args: "Deployment of image has completed. Image ID is '${{ steps.buildPushAction.outputs.imageid }}'."
+ - name: Discord notification
+ if: ${{ github.events.inputs.send-notifications || true && !(github.events.inputs.push-image == 'true' || github.events.inputs.push-image == null) }}
+ env:
+ DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+ uses: Ilshidur/action-discord@master
+ with:
+ args: "Deployment of image has completed. Image ID is '${{ steps.buildPushDryAction.outputs.imageid }}'. This was a dry run."
diff --git a/Dockerfile b/Dockerfile
index a0e2c5949..635a03172 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -61,7 +61,8 @@ RUN corepack enable pnpm && pnpm build
FROM base AS runner
WORKDIR /app
-RUN apk add --no-cache redis bash
+# gettext is required for envsubst
+RUN apk add --no-cache redis nginx bash gettext
RUN mkdir /appdata
RUN mkdir /appdata/db
RUN mkdir /appdata/redis
@@ -79,6 +80,11 @@ RUN chmod +x /usr/bin/homarr
# Don't run production as root
RUN chown -R nextjs:nodejs /appdata
+RUN mkdir -p /var/cache/nginx && chown -R nextjs:nodejs /var/cache/nginx && \
+ mkdir -p /var/log/nginx && chown -R nextjs:nodejs /var/log/nginx && \
+ mkdir -p /var/lib/nginx && chown -R nextjs:nodejs /var/lib/nginx && \
+ touch /run/nginx/nginx.pid && chown -R nextjs:nodejs /run/nginx/nginx.pid && \
+ mkdir -p /etc/nginx/templates /etc/nginx/ssl/certs && chown -R nextjs:nodejs /etc/nginx
USER nextjs
COPY --from=installer /app/apps/nextjs/next.config.mjs .
@@ -97,6 +103,8 @@ COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.next/static ./apps
COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/public ./apps/nextjs/public
COPY --chown=nextjs:nodejs scripts/run.sh ./run.sh
COPY --chown=nextjs:nodejs packages/redis/redis.conf /app/redis.conf
+COPY --chown=nextjs:nodejs nginx.conf /etc/nginx/templates/nginx.conf
+
ENV DB_URL='/appdata/db/db.sqlite'
ENV DB_DIALECT='sqlite'
diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json
index 7b96c572f..fea56abdf 100644
--- a/apps/nextjs/package.json
+++ b/apps/nextjs/package.json
@@ -27,6 +27,7 @@
"@homarr/integrations": "workspace:^0.1.0",
"@homarr/log": "workspace:^",
"@homarr/modals": "workspace:^0.1.0",
+ "@homarr/modals-collection": "workspace:^0.1.0",
"@homarr/notifications": "workspace:^0.1.0",
"@homarr/old-schema": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
@@ -42,7 +43,7 @@
"@mantine/tiptap": "^7.12.2",
"@million/lint": "1.0.0-rc.84",
"@t3-oss/env-nextjs": "^0.11.1",
- "@tabler/icons-react": "^3.16.0",
+ "@tabler/icons-react": "^3.17.0",
"@tanstack/react-query": "^5.56.2",
"@tanstack/react-query-devtools": "^5.56.2",
"@tanstack/react-query-next-experimental": "5.56.2",
@@ -59,16 +60,16 @@
"dotenv": "^16.4.5",
"flag-icons": "^7.2.3",
"glob": "^11.0.0",
- "jotai": "^2.9.3",
+ "jotai": "^2.10.0",
"mantine-react-table": "2.0.0-beta.6",
- "next": "^14.2.11",
+ "next": "^14.2.13",
"postcss-preset-mantine": "^1.17.0",
"prismjs": "^1.29.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.0.13",
"react-simple-code-editor": "^0.14.1",
- "sass": "^1.78.0",
+ "sass": "^1.79.2",
"superjson": "2.2.1",
"swagger-ui-react": "^5.17.14",
"use-deep-compare-effect": "^1.8.1"
@@ -80,7 +81,7 @@
"@types/chroma-js": "2.4.4",
"@types/node": "^20.16.5",
"@types/prismjs": "^1.26.4",
- "@types/react": "^18.3.5",
+ "@types/react": "^18.3.8",
"@types/react-dom": "^18.3.0",
"@types/swagger-ui-react": "^4.18.3",
"concurrently": "^9.0.1",
diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx
index 8583ac24f..c08542590 100644
--- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx
+++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx
@@ -20,7 +20,10 @@ import type { AppRouter } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
const wsClient = createWSClient({
- url: typeof window === "undefined" ? "ws://localhost:3001" : `ws://${window.location.hostname}:3001`,
+ url:
+ typeof window === "undefined"
+ ? "ws://localhost:3001/websockets"
+ : `ws://${window.location.hostname}:${window.location.port}/websockets`,
});
export function TRPCReactProvider(props: PropsWithChildren) {
diff --git a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx
index 6b144d35a..0c745c656 100644
--- a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx
+++ b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx
@@ -7,6 +7,7 @@ import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Te
import { useDisclosure } from "@mantine/hooks";
import { signIn } from "@homarr/auth/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
import type { useForm } from "@homarr/form";
import { useZodForm } from "@homarr/form";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
@@ -14,8 +15,6 @@ import { useScopedI18n } from "@homarr/translation/client";
import type { z } from "@homarr/validation";
import { validation } from "@homarr/validation";
-import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
-
interface LoginFormProps {
providers: string[];
oidcClientName: string;
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 27980758b..0782c1f94 100644
--- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx
+++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx
@@ -15,11 +15,11 @@ import {
} from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
import { useModalAction } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n, useScopedI18n } from "@homarr/translation/client";
-import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
import { ItemSelectModal } from "~/components/board/items/item-select-modal";
import { useBoardPermissions } from "~/components/board/permissions/client";
import { useCategoryActions } from "~/components/board/sections/category/category-actions";
diff --git a/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx b/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx
index dd20241d3..bdc520579 100644
--- a/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/apps/_app-delete-button.tsx
@@ -6,12 +6,11 @@ import { IconTrash } from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
import { useConfirmModal } from "@homarr/modals";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
-import { revalidatePathActionAsync } from "../../../revalidatePathAction";
-
interface AppDeleteButtonProps {
app: RouterOutputs["app"]["all"][number];
}
diff --git a/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx b/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx
index 5fda90040..9c34c160a 100644
--- a/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/apps/edit/[id]/_app-edit-form.tsx
@@ -5,12 +5,12 @@ import { useRouter } from "next/navigation";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import type { TranslationFunction } from "@homarr/translation";
import { useScopedI18n } from "@homarr/translation/client";
import type { validation, z } from "@homarr/validation";
-import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
import { AppForm } from "../../_form";
interface AppEditFormProps {
diff --git a/apps/nextjs/src/app/[locale]/manage/apps/new/_app-new-form.tsx b/apps/nextjs/src/app/[locale]/manage/apps/new/_app-new-form.tsx
index 171dec027..42a9ea40f 100644
--- a/apps/nextjs/src/app/[locale]/manage/apps/new/_app-new-form.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/apps/new/_app-new-form.tsx
@@ -4,12 +4,12 @@ import { useCallback } from "react";
import { useRouter } from "next/navigation";
import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import type { TranslationFunction } from "@homarr/translation";
import { useScopedI18n } from "@homarr/translation/client";
import type { validation, z } from "@homarr/validation";
-import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
import { AppForm } from "../_form";
export const AppNewForm = () => {
diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx
index 0da1a442d..dd7794db8 100644
--- a/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/board-card-menu-dropdown.tsx
@@ -7,10 +7,10 @@ import { IconHome, IconSettings, IconTrash } from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
+import { revalidatePathActionAsync } from "@homarr/common/client";
import { useConfirmModal } from "@homarr/modals";
import { useScopedI18n } from "@homarr/translation/client";
-import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
import { useBoardPermissions } from "~/components/board/permissions/client";
const iconProps = {
diff --git a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx
index 864c5ea83..0aa295481 100644
--- a/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/boards/_components/create-board-button.tsx
@@ -1,53 +1,21 @@
"use client";
-import { useCallback } from "react";
import { Affix, Button, Group, Menu } from "@mantine/core";
import { IconCategoryPlus, IconChevronDown, IconFileImport } from "@tabler/icons-react";
-import { clientApi } from "@homarr/api/client";
import { useModalAction } from "@homarr/modals";
+import { AddBoardModal, ImportBoardModal } from "@homarr/modals-collection";
import { useI18n } from "@homarr/translation/client";
import { BetaBadge } from "@homarr/ui";
-import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
-import { AddBoardModal } from "~/components/manage/boards/add-board-modal";
-import { ImportBoardModal } from "~/components/manage/boards/import-board-modal";
-
-interface CreateBoardButtonProps {
- boardNames: string[];
-}
-
-export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
+export const CreateBoardButton = () => {
const t = useI18n();
const { openModal: openAddModal } = useModalAction(AddBoardModal);
const { openModal: openImportModal } = useModalAction(ImportBoardModal);
- const { mutateAsync, isPending } = clientApi.board.createBoard.useMutation({
- onSettled: async () => {
- await revalidatePathActionAsync("/manage/boards");
- },
- });
-
- const onCreateClick = useCallback(() => {
- openAddModal({
- onSuccess: async (values) => {
- await mutateAsync({
- name: values.name,
- columnCount: values.columnCount,
- isPublic: values.isPublic,
- });
- },
- boardNames,
- });
- }, [mutateAsync, boardNames, openAddModal]);
-
- const onImportClick = useCallback(() => {
- openImportModal({ boardNames });
- }, [openImportModal, boardNames]);
-
const buttonGroupContent = (
<>
- } onClick={onCreateClick} loading={isPending}>
+ } onClick={openAddModal}>
{t("management.page.board.action.new.label")}