diff --git a/.cursor/rules/codebase-context.mdc b/.cursor/rules/codebase-context.mdc new file mode 100644 index 000000000..9b1ef995d --- /dev/null +++ b/.cursor/rules/codebase-context.mdc @@ -0,0 +1,191 @@ +--- +description: Comprehensive Homarr codebase architecture and conventions reference +alwaysApply: true +--- + +# Homarr Codebase Context + +Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI. + +## Tech Stack + +- **Monorepo**: pnpm workspaces + Turborepo +- **Framework**: Next.js (App Router, `output: "standalone"`) — T3 Stack +- **Language**: TypeScript throughout +- **ORM**: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL) +- **API**: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via `trpc-to-openapi` +- **Auth**: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers) +- **UI**: Mantine v7 (not Tailwind) + Tabler icons +- **State**: Jotai (atoms), TanStack Query (server state via tRPC) +- **Realtime**: WebSocket server (`ws`) for tRPC subscriptions, backed by Redis pub/sub +- **Cron**: `node-cron` in a standalone Fastify service (`apps/tasks`) +- **i18n**: next-intl with `[locale]` dynamic segment (prefix mode: `"never"`, locale from cookie) +- **Testing**: Vitest + jsdom, Playwright for E2E +- **Package manager**: pnpm 10.33.2, Node >= 24.15.0 + +## Repository Structure + +``` +homarr/ +├── apps/ +│ ├── nextjs/ # Main Next.js application (port 3000) +│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002) +│ └── websocket/ # Standalone tRPC WebSocket server (port 3001) +├── packages/ +│ ├── api/ # tRPC appRouter, procedures, OpenAPI +│ ├── auth/ # NextAuth config, providers, session, API keys +│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries +│ ├── core/ # Env validation, DB/Redis driver factories, logging +│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions +│ ├── widgets/ # All 39 dashboard widgets (definitions + components) +│ ├── integrations/ # Integration classes (HTTP clients to external apps) +│ ├── redis/ # Redis pub/sub channels, caching abstractions +│ ├── translation/ # next-intl setup, locale configs, lang JSON files +│ ├── ui/ # Shared Mantine components, theme, hooks +│ ├── validation/ # Shared zod schemas for API/forms +│ ├── common/ # Shared utilities, IDs, errors +│ ├── cron-jobs/ # Cron job implementations (25+ jobs) +│ ├── cron-jobs-core/ # Cron scheduling primitives +│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger) +│ ├── cron-job-status/ # Cron status via Redis +│ ├── boards/ # Board context, edit mode, cache updater +│ ├── modals/ # Modal primitives on Mantine +│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.) +│ ├── form/ # useZodForm (Mantine + zod resolver) +│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload) +│ ├── spotlight/ # Command palette / search with multiple modes +│ ├── request-handler/ # Server request handlers (feeds, integrations) +│ ├── notifications/ # Mantine notifications wrapper +│ ├── docker/ # Dockerode-based Docker access +│ ├── icons/ # Icon DB/repo integration +│ ├── image-proxy/ # Image proxy + caching +│ ├── ping/ # Reachability / ping utilities +│ ├── analytics/ # Server-side analytics (Umami) +│ ├── server-settings/ # Server setting keys/types +│ ├── settings/ # User-facing settings UI context +│ ├── old-import/ # Legacy Homarr import +│ ├── old-schema/ # Legacy Homarr zod schemas +│ └── cli/ # Node CLI for ops (brocli) +├── tooling/ +│ ├── eslint/ # Shared ESLint configs +│ ├── prettier/ # Shared Prettier config +│ ├── typescript/ # Base tsconfig +│ └── github/ # CI setup action +├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL) +├── e2e/ # E2E test specs +└── Dockerfile # Multi-stage production build +``` + +## Three Runtime Services + +| Service | Port | Role | +|---------|------|------| +| Next.js | 3000 | Main web app (SSR + API routes) | +| WebSocket | 3001 | tRPC subscriptions via `ws` | +| Tasks | 3002 | Cron job runner + management API via Fastify | + +In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header. + +## Package Dependency Layers + +1. **Foundation**: `core` (env, DB drivers, Redis client, logging) → no `@homarr` deps +2. **Domain**: `definitions` → `common` → `core` +3. **Data**: `db` → `core`, `definitions`, `common` +4. **Auth**: `auth` → `db`, `definitions`, `core`, `validation` +5. **Cross-cutting**: `translation`, `validation`, `redis`, `server-settings` +6. **Feature backends**: `integrations`, `request-handler`, `cron-jobs`, `docker`, `ping` +7. **API surface**: `api` → pulls in auth, db, redis, integrations, etc. +8. **UI stack**: `ui`, `modals`, `form`, `notifications`, `boards`, `spotlight` +9. **Composed features**: `widgets`, `modals-collection`, `forms-collection` + +## Key Patterns + +### tRPC + +- **Server caller** (RSC): `import { api } from "@homarr/api/server"` → cached caller with auth context +- **Client hooks**: `import { clientApi } from "@homarr/api/client"` → React Query hooks +- **Router structure**: `packages/api/src/root.ts` defines top-level namespaces (`user`, `board`, `widget`, `integration`, `docker`, `kubernetes`, etc.) +- **Procedures**: `publicProcedure`, `protectedProcedure`, `permissionRequiredProcedure`, `onboardingProcedure` +- **WebSocket subscriptions**: Client uses `wsLink` when `type === "subscription"`, connects to port 3001 + +### Multi-Database Support + +Drizzle schemas exist in three parallel implementations: +- `packages/db/schema/sqlite.ts` +- `packages/db/schema/mysql.ts` +- `packages/db/schema/postgresql.ts` + +Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-postgres`). Migrations are per-engine in `packages/db/migrations/{sqlite,mysql,postgresql}/`. + +### Widget System + +Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record`). Each widget: +- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })` +- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options +- Loads dynamically via `next/dynamic` +- Fetches data via tRPC client hooks (backed by cron job caches) + +### Integration System + +Integrations are defined in `packages/definitions/src/integration.ts` (`integrationDefs`) and instantiated via `createIntegrationAsync` in `packages/integrations/src/base/creator.ts`. Each extends abstract `Integration` class with `testConnectionAsync` and typed methods. + +### Cron Job System + +Jobs defined in `packages/cron-jobs/src/jobs/`. Each job: +- Uses `createCronJob` with a cron expression +- Publishes results to Redis channels +- Widget subscriptions consume those channels via tRPC subscriptions +- Job management (start/stop/trigger) via `cronJobApi` HTTP client to port 3002 + +### Modals + +Created with `createModal(component).withOptions({ defaultTitle })` from `@homarr/modals`. Opened via `useModalAction(SomeModal)`. Feature modals live in `packages/modals-collection/`. + +### Forms + +`useZodForm` from `@homarr/form` wraps Mantine `useForm` with zod validation. Shared form UIs in `packages/forms-collection/`. + +### Routing (App Router) + +- `apps/nextjs/src/app/[locale]/` — all UI routes under locale segment +- Route groups: `(home)`, `(content)`, `(board)` — don't affect URLs +- Key routes: `boards/[name]`, `manage/...` (admin), `auth/login`, `init` (onboarding), `widgets/[kind]` +- Locale driven by cookie (no URL prefix) + +### Auth Providers + +Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `oidc`. Database sessions with httpOnly cookies. API key auth for programmatic access. + +## Environment Variables + +Key env vars (from `.env.example`): +- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres` +- `DB_URL`: Database connection string +- `AUTH_SECRET`: NextAuth secret +- `SECRET_ENCRYPTION_KEY`: AES-256-CBC for integration secrets +- `AUTH_PROVIDERS`: Comma-separated (`credentials`, `ldap`, `oidc`) +- `CRON_JOB_API_KEY`: Shared key between Next.js and tasks service +- `REDIS_*`: Redis connection (host, port, password) +- `ENABLE_KUBERNETES`: Optional Kubernetes integration + +## Development Commands + +- `pnpm dev` — runs all three services in parallel via Turborepo +- `pnpm docker:dev` — starts Redis + MySQL + PostgreSQL containers +- `pnpm db:push` — push schema to SQLite (dev) +- `pnpm db:studio` — Drizzle Studio +- `pnpm test` — Vitest unit tests +- `pnpm test:e2e` — Playwright E2E +- `pnpm lint` / `pnpm format` — ESLint + Prettier +- `pnpm typecheck` — tsc --noEmit across all packages + +## Conventions + +- Workspace packages use `@homarr/` scope +- All versions managed via pnpm `catalog:` in workspace +- Path alias `~/*` → `./src/*` in Next.js app +- Mantine for all UI (no Tailwind) — primary color is red, `autoContrast: true` +- Icons from `@tabler/icons-react` +- Drag-and-drop via `@dnd-kit/*` +- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`) +- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom` diff --git a/.env.example b/.env.example index d643c1194..745d45954 100644 --- a/.env.example +++ b/.env.example @@ -45,6 +45,12 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE' TURBO_TELEMETRY_DISABLED=1 +# Comma-separated paths to Docker-compatible Unix sockets. +# Useful for connecting to multiple runtimes (Docker + Podman) simultaneously. +# When unset and no TCP hosts configured, defaults to /var/run/docker.sock. +# Example: DOCKER_SOCKET_PATHS=/var/run/docker.sock,/run/user/1000/podman/podman.sock +# DOCKER_SOCKET_PATHS= + # Enable kubernetes tool # ENABLE_KUBERNETES=true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d824200e4..c76536d88 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,6 +33,8 @@ body: options: # The below comment is used to insert a new version with on-release.yml #NEXT_VERSION# + - 1.60.1-beta.1 + - 1.60.0 - 1.59.3 - 1.59.2 - 1.59.1 diff --git a/.github/workflows/deployment-beta-release.yml b/.github/workflows/deployment-beta-release.yml new file mode 100644 index 000000000..fd79d1502 --- /dev/null +++ b/.github/workflows/deployment-beta-release.yml @@ -0,0 +1,104 @@ +name: "[Deployment] Beta Release" + +on: + workflow_dispatch: + inputs: + send-notifications: + type: boolean + required: false + default: true + description: Send notifications + +permissions: + contents: write + pull-requests: write + +jobs: + create-and-merge-pr: + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: Discord notification + if: ${{ github.event.inputs.send-notifications }} + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + uses: Ilshidur/action-discord@0.4.0 + with: + args: "Beta release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Get Next Version + id: semver + uses: ietf-tools/semver-action@v1 + with: + token: ${{ github.token }} + branch: next + - name: Create pull request + id: create-pull-request + run: 'gh pr create --title "chore(release): release ${{ steps.semver.outputs.next }}-beta" --body "Detected change to be ``${{ steps.semver.outputs.bump }}``
Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}-beta``" --base beta --head next --label automerge' + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + - name: Discord notification + if: ${{ github.event.inputs.send-notifications }} + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + uses: Ilshidur/action-discord@0.4.0 + with: + args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }}-beta (new behaviour: ${{ steps.semver.outputs.bump }})" + - name: Obtain token + id: obtainApprovalToken + uses: actions/create-github-app-token@v3 + with: + private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }} + client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }} + permission-pull-requests: write + - name: Approve PR + env: + GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }} + run: | + gh pr review --approve --body "Automatically approved by GitHub Action" + - name: Obtain token + id: obtainMergeToken + uses: actions/create-github-app-token@v3 + with: + private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }} + client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }} + permission-contents: write # write to beta branch (due to merge) + permission-pull-requests: write # merge pull request + - id: automerge + if: ${{ steps.semver.outputs.bump != 'major' }} + name: automerge + uses: "pascalgn/automerge-action@v0.16.4" + env: + GITHUB_TOKEN: ${{ steps.obtainMergeToken.outputs.token }} + MERGE_METHOD: merge # we prefer merge commits for merging to beta + MERGE_COMMIT_MESSAGE: "chore(release): release ${{ steps.semver.outputs.next }}-beta" + MERGE_DELETE_BRANCH: false # never set to true! + PULL_REQUEST: "${{ steps.create-pull-request.outputs.pr_number }}" + MERGE_RETRIES: 20 # 20 retries * MERGE_RETRY_SLEEP until step fails + MERGE_RETRY_SLEEP: 10000 # 10 seconds * MERGE_RETRIES until step fails + MERGE_REQUIRED_APPROVALS: 0 # do not require approvals + + - name: Merged Discord notification + if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }} + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + uses: Ilshidur/action-discord@0.4.0 + with: + args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta" + - name: Major Bump Discord notification + if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }} + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + uses: Ilshidur/action-discord@0.4.0 + with: + args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta" + - name: Discord Fail Notification + if: failure() && github.event.inputs.send-notifications + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} + uses: Ilshidur/action-discord@0.4.0 + with: + args: "The beta release workflow [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) has failed" diff --git a/.github/workflows/deployment-docker-image.yml b/.github/workflows/deployment-docker-image.yml index d55fa857d..d1ec9042e 100644 --- a/.github/workflows/deployment-docker-image.yml +++ b/.github/workflows/deployment-docker-image.yml @@ -191,7 +191,7 @@ jobs: name: Extract amd64 asset from docker image needs: [release, build-amd64] runs-on: ubuntu-latest - timeout-minutes: 2 + timeout-minutes: 5 steps: - name: Extract amd64 if: needs.release.outputs.skipped == 'false' @@ -207,7 +207,7 @@ jobs: name: Extract arm64 asset from docker image needs: [release, build-arm64] runs-on: ubuntu-24.04-arm - timeout-minutes: 2 + timeout-minutes: 5 steps: - name: Extract arm64 if: needs.release.outputs.skipped == 'false' diff --git a/.gitignore b/.gitignore index 43488df7a..0e991cc6c 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,4 @@ en.d.json.ts # personal agent config AGENTS.md -.opencode \ No newline at end of file +.opencode diff --git a/CODEOWNERS b/CODEOWNERS index ed482fd41..b798cfada 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,11 @@ # Exempt Renovate‑managed files (no owners) package.json +**/package.json package-lock.json +**/package-lock.json pnpm-lock.yaml +**/pnpm-lock.yaml +pnpm-workspace.yaml Dockerfile docker-compose.yml diff --git a/apps/nextjs/eslint.config.js b/apps/nextjs/eslint.config.js index c131bab90..3d593e34e 100644 --- a/apps/nextjs/eslint.config.js +++ b/apps/nextjs/eslint.config.js @@ -10,4 +10,21 @@ export default [ ...baseConfig, ...reactConfig, ...nextjsConfig, + { + files: ["public/sw.js"], + languageOptions: { + globals: { + self: "readonly", + clients: "readonly", + skipWaiting: "readonly", + importScripts: "readonly", + caches: "readonly", + fetch: "readonly", + }, + }, + rules: { + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + }, + }, ]; diff --git a/apps/nextjs/next.config.ts b/apps/nextjs/next.config.ts index a3dabe151..4a0450165 100644 --- a/apps/nextjs/next.config.ts +++ b/apps/nextjs/next.config.ts @@ -42,9 +42,18 @@ const nextConfig: NextConfig = { }, ], }, + // skipcq: JS-0116 // eslint-disable-next-line @typescript-eslint/require-await,no-restricted-syntax async headers() { return [ + { + source: "/sw.js", + headers: [ + { key: "Content-Type", value: "application/javascript; charset=utf-8" }, + { key: "Cache-Control", value: "no-cache, no-store, must-revalidate" }, + { key: "Content-Security-Policy", value: "default-src 'self'; script-src 'self'" }, + ], + }, { source: "/(.*)", // Apply CSP to all routes headers: [ diff --git a/apps/nextjs/public/sw.js b/apps/nextjs/public/sw.js new file mode 100644 index 000000000..dbb12ebbb --- /dev/null +++ b/apps/nextjs/public/sw.js @@ -0,0 +1,7 @@ +self.addEventListener("install", () => { + self.skipWaiting(); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil(self.clients.claim()); +}); diff --git a/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx b/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx index e24c08b98..d81b828f3 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx @@ -8,13 +8,12 @@ import dayjs from "dayjs"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; import { parseCookies, setClientCookie } from "@homarr/common"; -import type { ColorScheme } from "@homarr/definitions"; import { colorSchemeCookieKey } from "@homarr/definitions"; export const CustomMantineProvider = ({ children, defaultColorScheme, -}: PropsWithChildren<{ defaultColorScheme: ColorScheme }>) => { +}: PropsWithChildren<{ defaultColorScheme: MantineColorScheme }>) => { const manager = useColorSchemeManager(); return ( @@ -35,7 +34,7 @@ export const CustomMantineProvider = ({ export function useColorSchemeManager(): MantineColorSchemeManager { const { data: session } = useSession(); - const updateCookieValue = (value: Exclude) => { + const updateCookieValue = (value: MantineColorScheme) => { setClientCookie(colorSchemeCookieKey, value, { expires: dayjs().add(1, "year").toDate(), path: "/" }); }; @@ -60,7 +59,6 @@ export function useColorSchemeManager(): MantineColorSchemeManager { }, set: (value) => { - if (value === "auto") return; try { if (session) { mutateColorScheme({ colorScheme: value }); diff --git a/apps/nextjs/src/app/[locale]/layout.tsx b/apps/nextjs/src/app/[locale]/layout.tsx index 708699bf8..5449e1252 100644 --- a/apps/nextjs/src/app/[locale]/layout.tsx +++ b/apps/nextjs/src/app/[locale]/layout.tsx @@ -4,6 +4,7 @@ import { Inter } from "next/font/google"; import "@homarr/notifications/styles.css"; import "@homarr/spotlight/styles.css"; import "@homarr/ui/styles.css"; +import "~/styles/color-scheme.scss"; import "~/styles/scroll-area.scss"; import { notFound } from "next/navigation"; @@ -25,6 +26,7 @@ import { isLocaleRTL, isLocaleSupported } from "@homarr/translation"; import { Analytics } from "~/components/layout/analytics"; import { CrowdinLiveTranslation } from "~/components/layout/crowdin-live-translation"; import { SearchEngineOptimization } from "~/components/layout/search-engine-optimization"; +import { ServiceWorkerRegistration } from "~/components/layout/service-worker-registration"; import { getCurrentColorSchemeAsync } from "~/theme/color-scheme"; import { DayJsLoader } from "./_client-providers/dayjs-loader"; import { JotaiProvider } from "./_client-providers/jotai"; @@ -129,7 +131,7 @@ export default async function Layout(props: { dir={direction} data-mantine-color-scheme={colorScheme} style={{ - backgroundColor: colorScheme === "dark" ? "#242424" : "#fff", + backgroundColor: colorScheme === "dark" ? "#242424" : colorScheme === "auto" ? undefined : "#fff", }} suppressHydrationWarning > @@ -141,6 +143,7 @@ export default async function Layout(props: { + {props.children} diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx index 2ca25ac4d..900caf0e4 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx @@ -1,7 +1,7 @@ "use client"; import { Group, Text } from "@mantine/core"; -import { IconMoon, IconSun } from "@tabler/icons-react"; +import { IconDeviceDesktop, IconMoon, IconSun } from "@tabler/icons-react"; import type { ColorScheme } from "@homarr/definitions"; import { colorSchemes } from "@homarr/definitions"; @@ -34,6 +34,7 @@ export const AppearanceSettingsForm = ({ defaultValues }: { defaultValues: Serve }; const appearanceIcons = { + auto: IconDeviceDesktop, light: IconSun, dark: IconMoon, }; 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 537c85e7c..de7e781a1 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 @@ -58,6 +58,18 @@ const createColumns = ( return ; }, }, + { + accessorKey: "host", + header: t("docker.field.host.label"), + size: 140, + Cell({ row }) { + return ( + + {row.original.host} + + ); + }, + }, { accessorKey: "image", header: t("docker.field.containerImage.label"), diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx index 5bf23a5fb..afae60dc1 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_change-search-preferences.tsx @@ -26,6 +26,7 @@ export const ChangeSearchPreferencesForm = ({ user, searchEnginesData }: ChangeS form.setInitialValues({ defaultSearchEngineId: variables.defaultSearchEngineId, openInNewTab: variables.openInNewTab, + ddgBangsEnabled: variables.ddgBangsEnabled, }); showSuccessNotification({ message: t("user.action.changeSearchPreferences.notification.success.message"), @@ -41,6 +42,7 @@ export const ChangeSearchPreferencesForm = ({ user, searchEnginesData }: ChangeS initialValues: { defaultSearchEngineId: user.defaultSearchEngineId, openInNewTab: user.openSearchInNewTab, + ddgBangsEnabled: user.ddgBangs, }, }); @@ -64,6 +66,10 @@ export const ChangeSearchPreferencesForm = ({ user, searchEnginesData }: ChangeS label={t("user.field.openSearchInNewTab.label")} {...form.getInputProps("openInNewTab", { type: "checkbox" })} /> +