diff --git a/.github/workflows/deployment-docker-image.yml b/.github/workflows/deployment-docker-image.yml index a3bfd26df..35275a8da 100644 --- a/.github/workflows/deployment-docker-image.yml +++ b/.github/workflows/deployment-docker-image.yml @@ -38,7 +38,7 @@ jobs: node-version: [20] steps: - name: Discord notification - if: ${{ github.events.inputs.send-notifications || true }} + if: ${{ github.events.inputs.send-notifications != false }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} uses: Ilshidur/action-discord@master @@ -52,7 +52,7 @@ jobs: token: ${{ github.token }} branch: dev - name: Discord notification - if: ${{ github.events.inputs.send-notifications || true }} + if: ${{ github.events.inputs.send-notifications != false }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} uses: Ilshidur/action-discord@master @@ -66,7 +66,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - name: Discord notification - if: ${{ github.events.inputs.send-notifications || true }} + if: ${{ github.events.inputs.send-notifications != false }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} uses: Ilshidur/action-discord@master @@ -120,14 +120,14 @@ jobs: env: SKIP_ENV_VALIDATION: true - name: Discord notification - if: ${{ github.events.inputs.send-notifications || true && (github.events.inputs.push-image == 'true' || github.events.inputs.push-image == null) }} + if: ${{ github.events.inputs.send-notifications != false && (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 }}'." - name: Discord notification - if: ${{ github.events.inputs.send-notifications || true && !(github.events.inputs.push-image == 'true' || github.events.inputs.push-image == null) }} + if: ${{ github.events.inputs.send-notifications != false && !(github.events.inputs.push-image == 'true' || github.events.inputs.push-image == null) }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} uses: Ilshidur/action-discord@master diff --git a/.gitignore b/.gitignore index 91c18132f..bdc55c860 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ yarn-error.log* apps/tasks/tasks.cjs apps/websocket/wssServer.cjs apps/nextjs/.million/ +packages/cli/cli.cjs #personal backgrounds diff --git a/.nvmrc b/.nvmrc index 3516580bb..2a393af59 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.17.0 +20.18.0 diff --git a/Dockerfile b/Dockerfile index f82ab6f50..fda2e1dc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 node:20.17.0-alpine AS base +FROM node:20.17.0-alpine AS base FROM base AS builder RUN apk add --no-cache libc6-compat @@ -46,6 +46,7 @@ COPY --from=builder /app/cli-out/full/ . # Copy static data as it is not part of the build COPY static-data ./static-data ARG SKIP_ENV_VALIDATION='true' +ARG CI='true' ARG DISABLE_REDIS_LOGS='true' RUN corepack enable pnpm && pnpm build @@ -58,6 +59,8 @@ RUN mkdir /appdata RUN mkdir /appdata/db RUN mkdir /appdata/redis VOLUME /appdata +RUN mkdir /secrets +VOLUME /secrets @@ -71,6 +74,7 @@ RUN chmod +x /usr/bin/homarr # Don't run production as root RUN chown -R nextjs:nodejs /appdata +RUN chown -R nextjs:nodejs /secrets 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 && \ @@ -93,6 +97,7 @@ COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.next/standalone ./ COPY --from=installer --chown=nextjs:nodejs /app/apps/nextjs/.next/static ./apps/nextjs/.next/static 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 scripts/generateEncryptionKey.js ./generateEncryptionKey.js COPY --chown=nextjs:nodejs packages/redis/redis.conf /app/redis.conf COPY --chown=nextjs:nodejs nginx.conf /etc/nginx/templates/nginx.conf diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 81c2c0f6e..9a8c7ec6c 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -30,23 +30,24 @@ "@homarr/modals-collection": "workspace:^0.1.0", "@homarr/notifications": "workspace:^0.1.0", "@homarr/old-schema": "workspace:^0.1.0", + "@homarr/redis": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0", "@homarr/spotlight": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0", "@homarr/ui": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0", - "@mantine/colors-generator": "^7.13.0", - "@mantine/core": "^7.13.0", - "@mantine/hooks": "^7.13.0", - "@mantine/modals": "^7.13.0", - "@mantine/tiptap": "^7.13.0", - "@million/lint": "1.0.0-rc.84", + "@mantine/colors-generator": "^7.13.2", + "@mantine/core": "^7.13.2", + "@mantine/hooks": "^7.13.2", + "@mantine/modals": "^7.13.2", + "@mantine/tiptap": "^7.13.2", + "@million/lint": "1.0.8", "@t3-oss/env-nextjs": "^0.11.1", - "@tabler/icons-react": "^3.17.0", - "@tanstack/react-query": "^5.56.2", - "@tanstack/react-query-devtools": "^5.58.0", - "@tanstack/react-query-next-experimental": "5.56.2", + "@tabler/icons-react": "^3.19.0", + "@tanstack/react-query": "^5.59.0", + "@tanstack/react-query-devtools": "^5.59.0", + "@tanstack/react-query-next-experimental": "5.59.0", "@trpc/client": "next", "@trpc/next": "next", "@trpc/react-query": "next", @@ -62,14 +63,14 @@ "glob": "^11.0.0", "jotai": "^2.10.0", "mantine-react-table": "2.0.0-beta.6", - "next": "^14.2.13", + "next": "^14.2.14", "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.79.3", + "sass": "^1.79.4", "superjson": "2.2.1", "swagger-ui-react": "^5.17.14", "use-deep-compare-effect": "^1.8.1" @@ -79,9 +80,9 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/chroma-js": "2.4.4", - "@types/node": "^20.16.7", + "@types/node": "^20.16.10", "@types/prismjs": "^1.26.4", - "@types/react": "^18.3.9", + "@types/react": "^18.3.11", "@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/mantine.tsx b/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx index 9a2f3aecb..fd01444d7 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx @@ -1,12 +1,14 @@ "use client"; -import { useState } from "react"; import type { PropsWithChildren } from "react"; +import { useState } from "react"; import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core"; import { createTheme, isMantineColorScheme, MantineProvider } from "@mantine/core"; +import dayjs from "dayjs"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; +import { parseCookies, setClientCookie } from "@homarr/common"; export const CustomMantineProvider = ({ children }: PropsWithChildren) => { const manager = useColorSchemeManager(); @@ -50,7 +52,8 @@ function useColorSchemeManager(): MantineColorSchemeManager { } try { - return (window.localStorage.getItem(key) as MantineColorScheme | undefined) ?? defaultValue; + const cookies = parseCookies(document.cookie); + return (cookies[key] as MantineColorScheme | undefined) ?? defaultValue; } catch { return defaultValue; } @@ -61,6 +64,7 @@ function useColorSchemeManager(): MantineColorSchemeManager { if (session) { mutateColorScheme({ colorScheme: value }); } + setClientCookie(key, value, { expires: dayjs().add(1, "year").toDate() }); window.localStorage.setItem(key, value); } catch (error) { console.warn("[@mantine/core] Local storage color scheme manager was unable to save color scheme.", error); diff --git a/apps/nextjs/src/app/[locale]/layout.tsx b/apps/nextjs/src/app/[locale]/layout.tsx index c1b37045b..92826b989 100644 --- a/apps/nextjs/src/app/[locale]/layout.tsx +++ b/apps/nextjs/src/app/[locale]/layout.tsx @@ -6,6 +6,8 @@ import "@homarr/spotlight/styles.css"; import "@homarr/ui/styles.css"; import "~/styles/scroll-area.scss"; +import { cookies } from "next/headers"; + import { env } from "@homarr/auth/env.mjs"; import { auth } from "@homarr/auth/next"; import { ModalProvider } from "@homarr/modals"; @@ -53,7 +55,7 @@ export const viewport: Viewport = { export default async function Layout(props: { children: React.ReactNode; params: { locale: string } }) { const session = await auth(); - const colorScheme = session?.user.colorScheme; + const colorScheme = cookies().get("homarr-color-scheme")?.value ?? "light"; const StackedProvider = composeWrappers([ (innerProps) => { diff --git a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx index 7ddf2816a..11aa2e8eb 100644 --- a/apps/nextjs/src/app/[locale]/manage/apps/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/apps/page.tsx @@ -105,7 +105,7 @@ const AppNoResults = async () => { {t("app.page.list.noResults.title")} - {t("app.page.list.noResults.description")} + {t("app.page.list.noResults.action")} ); diff --git a/apps/nextjs/src/app/[locale]/manage/layout.tsx b/apps/nextjs/src/app/[locale]/manage/layout.tsx index 58314d65a..b4d1564c4 100644 --- a/apps/nextjs/src/app/[locale]/manage/layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/layout.tsx @@ -15,6 +15,7 @@ import { IconPlug, IconQuestionMark, IconReport, + IconSearch, IconSettings, IconTool, IconUser, @@ -53,6 +54,11 @@ export default async function ManageLayout({ children }: PropsWithChildren) { href: "/manage/integrations", label: t("items.integrations"), }, + { + icon: IconSearch, + href: "/manage/search-engines", + label: t("items.searchEngies"), + }, { icon: IconUser, label: t("items.users.label"), diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx new file mode 100644 index 000000000..e937cf503 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/manage/search-engines/_form.tsx @@ -0,0 +1,73 @@ +"use client"; + +import Link from "next/link"; +import { Button, Grid, Group, Stack, Textarea, TextInput } from "@mantine/core"; + +import { useZodForm } from "@homarr/form"; +import type { TranslationFunction } from "@homarr/translation"; +import { useI18n } from "@homarr/translation/client"; +import type { z } from "@homarr/validation"; +import { validation } from "@homarr/validation"; + +import { IconPicker } from "~/components/icons/picker/icon-picker"; + +type FormType = z.infer; + +interface SearchEngineFormProps { + submitButtonTranslation: (t: TranslationFunction) => string; + initialValues?: FormType; + handleSubmit: (values: FormType) => void; + isPending: boolean; + disableShort?: boolean; +} + +export const SearchEngineForm = (props: SearchEngineFormProps) => { + const { submitButtonTranslation, handleSubmit, initialValues, isPending, disableShort } = props; + const t = useI18n(); + + const form = useZodForm(validation.searchEngine.manage, { + initialValues: initialValues ?? { + name: "", + short: "", + iconUrl: "", + urlTemplate: "", + description: "", + }, + }); + + return ( +
+ + + + + + + + + + + +