Compare commits

..
Author SHA1 Message Date
ajnart 96bb33299e style: auto-fix lint and format 2026-07-06 04:32:49 +00:00
ajnart 39a1a3695e fix: address coderabbit review on #6214
- Remove invalid null return from session callback (must return session object)
- Prevent admin from disabling their own account (guard with isSelf check)
- Hide disable UI when viewing own profile
- Add user existence check to changeDisabled endpoint
- Add OpenAPI metadata to changeDisabled mutation
2026-07-06 06:31:42 +02:00
ajnart fbac0c8752 refactor: remove ternary operators for better readability
Replace ternary operators with if-statements and helper functions in:
- DisableUserButton component
- user list disabled status column
- EditUserPage disable item configuration
2026-07-06 02:59:02 +02:00
ajnart 26c83d8853 style: auto-fix lint and format 2026-07-06 00:57:10 +00:00
ajnart 4ff2c9a7fb feat(users): add ability to disable users without deleting them
- Add disabled boolean field to user schema (PostgreSQL, MySQL, SQLite)
- Block disabled users from logging in via any auth provider (credentials, LDAP, OIDC)
- Check disabled status in session callback to prevent session replay
- Add changeDisabled API endpoint (admin-only)
- Include disabled field in getById and getAll user endpoints
- Add disable/enable user toggle in admin UI with status badge in user list
2026-07-06 02:55:39 +02:00
53 changed files with 989 additions and 874 deletions

No files matched your search

-1
View File
@@ -33,7 +33,6 @@ body:
options: options:
# The below comment is used to insert a new version with on-release.yml # The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION# #NEXT_VERSION#
- 1.69.2
- 1.69.0 - 1.69.0
- 1.68.0 - 1.68.0
- 1.67.0 - 1.67.0
+1 -1
View File
@@ -11,7 +11,7 @@ outputs:
runs: runs:
using: "composite" using: "composite"
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
persist-credentials: false persist-credentials: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441 if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
uses: actions/create-github-app-token@v3 uses: actions/create-github-app-token@v3
+7 -7
View File
@@ -31,7 +31,7 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
@@ -60,7 +60,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -92,7 +92,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -106,7 +106,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Setup - name: Setup
uses: ./tooling/github/setup uses: ./tooling/github/setup
@@ -124,7 +124,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Setup - name: Setup
uses: ./tooling/github/setup uses: ./tooling/github/setup
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -153,7 +153,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup - name: Setup
@@ -15,7 +15,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Obtain token - name: Obtain token
id: obtainToken id: obtainToken
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Upload Crowdin translations - name: Upload Crowdin translations
uses: crowdin/github-action@v2 uses: crowdin/github-action@v2
@@ -26,7 +26,7 @@ jobs:
with: with:
args: "Beta release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" args: "Beta release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v7 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get Next Version - name: Get Next Version
@@ -57,7 +57,7 @@ jobs:
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }} client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
permission-contents: write # required to commit package.json & changelog changes, merge them to dev / next and publish the release permission-contents: write # required to commit package.json & changelog changes, merge them to dev / next and publish the release
- uses: actions/checkout@v7 - uses: actions/checkout@v6
if: env.SKIP_RELEASE == 'false' if: env.SKIP_RELEASE == 'false'
with: with:
persist-credentials: false persist-credentials: false
@@ -119,7 +119,7 @@ jobs:
outputs: outputs:
digest: ${{ steps.build.outputs.digest }} digest: ${{ steps.build.outputs.digest }}
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
ref: ${{ needs.release.outputs.git_ref }} ref: ${{ needs.release.outputs.git_ref }}
@@ -158,7 +158,7 @@ jobs:
outputs: outputs:
digest: ${{ steps.build.outputs.digest }} digest: ${{ steps.build.outputs.digest }}
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
ref: ${{ needs.release.outputs.git_ref }} ref: ${{ needs.release.outputs.git_ref }}
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Setup - name: Setup
uses: ./tooling/github/setup uses: ./tooling/github/setup
- name: Build docs - name: Build docs
@@ -29,7 +29,7 @@ jobs:
with: with:
args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v7 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Get Next Version - name: Get Next Version
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Setup - name: Setup
uses: ./tooling/github/setup uses: ./tooling/github/setup
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
ref: dev ref: dev
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
with: with:
ref: dev ref: dev
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Validate prebuilt dependencies for amd64 - name: Validate prebuilt dependencies for amd64
id: validate-amd64 id: validate-amd64
uses: ./.github/actions/prebuilt-debian uses: ./.github/actions/prebuilt-debian
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
timeout-minutes: 5 timeout-minutes: 5
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Validate prebuilt dependencies for arm64 - name: Validate prebuilt dependencies for arm64
id: validate-arm64 id: validate-arm64
uses: ./.github/actions/prebuilt-debian uses: ./.github/actions/prebuilt-debian
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 2 timeout-minutes: 2
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- run: | - run: |
npx --yes --package renovate -- \ npx --yes --package renovate -- \
renovate-config-validator --strict .github/renovate.json5 renovate-config-validator --strict .github/renovate.json5
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
permission-contents: write # required to commit to branch permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code - name: Checkout code
uses: actions/checkout@v7 uses: actions/checkout@v6
with: with:
token: ${{ steps.obtainToken.outputs.token }} token: ${{ steps.obtainToken.outputs.token }}
ref: dev ref: dev
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
permission-pull-requests: write # required to create pr & enable automerge permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v7 uses: actions/checkout@v6
env: env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }} GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
@@ -28,7 +28,7 @@ jobs:
permission-contents: write # required to commit to branch permission-contents: write # required to commit to branch
permission-pull-requests: write # required to create pr & enable automerge permission-pull-requests: write # required to create pr & enable automerge
- name: Checkout code - name: Checkout code
uses: actions/checkout@v7 uses: actions/checkout@v6
env: env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }} GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
- name: Setup - name: Setup
-6
View File
@@ -1,9 +1,3 @@
## [1.69.2](https://github.com/homarr-labs/homarr/compare/v1.69.1...v1.69.2) (2026-07-06)
### Bug Fixes
* **layout:** raise AppShellHeader z-index above Indicator dots ([687edb9](https://github.com/homarr-labs/homarr/commit/687edb9226307cb83986097abb24fe7f672e1778)), closes [#6194](https://github.com/homarr-labs/homarr/issues/6194)
## [1.69.1](https://github.com/homarr-labs/homarr/compare/v1.69.0...v1.69.1) (2026-07-04) ## [1.69.1](https://github.com/homarr-labs/homarr/compare/v1.69.0...v1.69.1) (2026-07-04)
### Bug Fixes ### Bug Fixes
+1 -1
View File
@@ -58,7 +58,7 @@ import { immichServerStatsWidget } from '../../widgets/immich-server-stats';
Additional permissions may be required depending on your Immich version. If you encounter authorization errors, grant any missing permissions requested by the server. Additional permissions may be required depending on your Immich version. If you encounter authorization errors, grant any missing permissions requested by the server.
</span> </span>
), ),Ï
'Create the integration in Homarr and copy the API key to there' 'Create the integration in Homarr and copy the API key to there'
] ]
}]} /> }]} />
+1 -1
View File
@@ -43,7 +43,7 @@
"random-word-slugs": "^0.1.7", "random-word-slugs": "^0.1.7",
"react": "catalog:", "react": "catalog:",
"react-dom": "catalog:", "react-dom": "catalog:",
"swiper": "^14.0.1", "swiper": "^12.2.0",
"tailwindcss": "^4.3.2" "tailwindcss": "^4.3.2"
}, },
"devDependencies": { "devDependencies": {
+2 -1
View File
@@ -1,7 +1,8 @@
@import url("https://api.fontshare.com/v2/css?f[]=work-sans@2,1&display=swap");
@import "tailwindcss/theme" layer(theme); @import "tailwindcss/theme" layer(theme);
@import "tailwindcss/utilities" layer(utilities); @import "tailwindcss/utilities" layer(utilities);
@import url(https://api.fontshare.com/v2/css?f[]=work-sans@2,1&display=swap);
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *)); @custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
@theme { @theme {
+4 -5
View File
@@ -1,6 +1,9 @@
{ {
"extends": "@docusaurus/tsconfig",
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "ignoreDeprecations": "6.0",
"baseUrl": ".",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
@@ -9,14 +12,10 @@
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"paths": {
"@site/*": ["./*"]
},
}, },
"include": [ "include": [
"**/*.ts", "**/*.ts",
+14 -12
View File
@@ -5,9 +5,21 @@ import "@homarr/common/env";
import "@homarr/core/infrastructure/logs/env"; import "@homarr/core/infrastructure/logs/env";
import "@homarr/docker/env"; import "@homarr/docker/env";
import { readFileSync } from "fs";
import type { NextConfig } from "next"; import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin"; import createNextIntlPlugin from "next-intl/plugin";
const homarrVersion = (() => {
for (const candidate of ["../../package.json", "./package.json"]) {
try {
const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
if (pkg.name === "homarr") return pkg.version as string;
} catch {}
}
return "unknown";
})();
// Package path does not work... so we need to use relative path // Package path does not work... so we need to use relative path
const withNextIntl = createNextIntlPlugin({ const withNextIntl = createNextIntlPlugin({
requestConfig: "../../packages/translation/src/request.ts", requestConfig: "../../packages/translation/src/request.ts",
@@ -15,7 +27,7 @@ const withNextIntl = createNextIntlPlugin({
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
env: { env: {
HOMARR_VERSION: process.env.HOMARR_VERSION ?? "unknown", HOMARR_VERSION: homarrVersion,
}, },
output: "standalone", output: "standalone",
reactStrictMode: true, reactStrictMode: true,
@@ -31,19 +43,9 @@ const nextConfig: NextConfig = {
experimental: { experimental: {
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"], optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
turbopackFileSystemCacheForDev: true, turbopackFileSystemCacheForDev: true,
preloadEntriesOnStart: false,
webpackMemoryOptimizations: true, webpackMemoryOptimizations: true,
}, },
turbopack: {
// ponytail: known Turbopack NFT warning from path.join(process.cwd(), …) in
// src/app/api/backup/{route,shared}.ts. No working placement in 16.2.x
// (see vercel/next.js#95125). Suppress until upstream fix lands.
ignoreIssue: [
{
path: "**/*",
title: "Encountered unexpected file in NFT list",
},
],
},
transpilePackages: ["@homarr/ui", "@homarr/notifications", "@homarr/modals", "@homarr/spotlight", "@homarr/widgets"], transpilePackages: ["@homarr/ui", "@homarr/notifications", "@homarr/modals", "@homarr/spotlight", "@homarr/widgets"],
images: { images: {
localPatterns: [ localPatterns: [
+1 -1
View File
@@ -4,7 +4,7 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "HOMARR_VERSION=$(node -p \"require('../../package.json').version\") pnpm with-env next build", "build": "pnpm with-env next build",
"clean": "git clean -xdf .next .turbo node_modules", "clean": "git clean -xdf .next .turbo node_modules",
"dev": "pnpm with-env next dev", "dev": "pnpm with-env next dev",
"format": "oxfmt .", "format": "oxfmt .",
@@ -0,0 +1,90 @@
"use client";
import { useTransition } from "react";
import { Button, Tooltip } from "@mantine/core";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
import { useI18n } from "@homarr/translation/client";
interface DisableUserButtonProps {
user: RouterOutputs["user"]["getById"];
isSelf?: boolean;
}
export const DisableUserButton = ({ user, isSelf = false }: DisableUserButtonProps) => {
const t = useI18n();
const [isPending, startTransition] = useTransition();
const changeDisabledMutation = clientApi.user.changeDisabled.useMutation();
const isDisabled = user.disabled;
const getSuccessMessage = () => {
if (isDisabled) {
return {
title: t("user.action.enable.success", { defaultValue: "User enabled" }),
message: t("user.action.enable.successDescription", { defaultValue: "User has been enabled" }),
};
}
return {
title: t("user.action.disable.success", { defaultValue: "User disabled" }),
message: t("user.action.disable.successDescription", { defaultValue: "User has been disabled" }),
};
};
const handleToggle = () => {
if (isSelf) return;
startTransition(async () => {
try {
await changeDisabledMutation.mutateAsync({
userId: user.id,
disabled: !isDisabled,
});
showSuccessNotification(getSuccessMessage());
} catch {
showErrorNotification({
title: t("common.notification.update.error"),
message: t("common.notification.update.error"),
});
}
});
};
const getTooltipLabel = () => {
if (isDisabled) {
return t("user.action.enable.description", { defaultValue: "Enable this user" });
}
return t("user.action.disable.description", { defaultValue: "Disable this user" });
};
const getButtonLabel = () => {
if (isDisabled) {
return t("user.action.enable.label", { defaultValue: "Enable user" });
}
return t("user.action.disable.label", { defaultValue: "Disable user" });
};
const getButtonColor = () => (isDisabled ? "green" : "yellow");
return (
<Tooltip
label={
isSelf
? t("user.action.disableSelf.disabled", { defaultValue: "Cannot disable your own account" })
: getTooltipLabel()
}
position="top"
>
<Button
onClick={handleToggle}
loading={isPending || changeDisabledMutation.isPending}
color={getButtonColor()}
variant="light"
disabled={isSelf}
>
{getButtonLabel()}
</Button>
</Tooltip>
);
};
@@ -11,6 +11,7 @@ import { catchTrpcNotFound } from "~/errors/trpc-catch-error";
import { createMetaTitle } from "~/metadata"; import { createMetaTitle } from "~/metadata";
import { canAccessUserEditPage } from "../access"; import { canAccessUserEditPage } from "../access";
import { DeleteUserButton } from "./_components/_delete-user-button"; import { DeleteUserButton } from "./_components/_delete-user-button";
import { DisableUserButton } from "./_components/_disable-user-button";
import { UserGeneralSettingsForm } from "./_components/_general-settings-form"; import { UserGeneralSettingsForm } from "./_components/_general-settings-form";
import { UserProfileAvatarForm } from "./_components/_profile-avatar-form"; import { UserProfileAvatarForm } from "./_components/_profile-avatar-form";
import { ResetTours } from "./_components/_reset-tours"; import { ResetTours } from "./_components/_reset-tours";
@@ -61,6 +62,19 @@ export default async function EditUserPage(props: Props) {
const searchEngines = await api.searchEngine.getSelectable(); const searchEngines = await api.searchEngine.getSelectable();
const isSelf = session?.user.id === user.id; const isSelf = session?.user.id === user.id;
const isCredentialsUser = user.provider === "credentials"; const isCredentialsUser = user.provider === "credentials";
const isDisabled = user.disabled;
const getDisableItemLabel = () => {
if (isDisabled) return t("user.action.enable.label");
return t("user.action.disable.label");
};
const getDisableItemDescription = () => {
if (isDisabled) {
return t("management.page.user.action.enable.description", { defaultValue: "Re-enable access for this user" });
}
return t("management.page.user.action.disable.description", { defaultValue: "Prevent this user from logging in" });
};
return ( return (
<Stack> <Stack>
@@ -96,6 +110,13 @@ export default async function EditUserPage(props: Props) {
)} )}
<DangerZoneRoot> <DangerZoneRoot>
{!isSelf && (
<DangerZoneItem
label={getDisableItemLabel()}
description={getDisableItemDescription()}
action={<DisableUserButton user={user} isSelf={isSelf} />}
/>
)}
<DangerZoneItem <DangerZoneItem
label={t("user.action.delete.label")} label={t("user.action.delete.label")}
description={t("user.action.delete.description")} description={t("user.action.delete.description")}
@@ -1,7 +1,7 @@
"use client"; "use client";
import { useMemo } from "react"; import { useMemo } from "react";
import { Anchor, Group, Text, Tooltip } from "@mantine/core"; import { Anchor, Badge, Group, Text, Tooltip } from "@mantine/core";
import { IconCheck } from "@tabler/icons-react"; import { IconCheck } from "@tabler/icons-react";
import type { MRT_ColumnDef } from "mantine-react-table"; import type { MRT_ColumnDef } from "mantine-react-table";
import { MantineReactTable } from "mantine-react-table"; import { MantineReactTable } from "mantine-react-table";
@@ -52,6 +52,15 @@ export const UserListComponent = ({ initialUserList }: UserListComponentProps) =
</Group> </Group>
), ),
}, },
{
accessorKey: "disabled",
header: t("user.field.status.label", { defaultValue: "Status" }),
size: 100,
Cell: ({ row }) => {
if (!row.original.disabled) return null;
return <Badge color="red">{t("user.field.status.disabled", { defaultValue: "Disabled" })}</Badge>;
},
},
], ],
[t], [t],
); );
@@ -37,8 +37,8 @@ export const WidgetPreviewPageContent = ({ kind, integrationData }: WidgetPrevie
const currentDefinition = useMemo(() => widgetImports[kind].definition, [kind]); const currentDefinition = useMemo(() => widgetImports[kind].definition, [kind]);
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);
const [dimensions, setDimensions] = useState<Dimensions>({ const [dimensions, setDimensions] = useState<Dimensions>({
width: 960, width: 128,
height: 640, height: 128,
}); });
const [state, setState] = useState<{ const [state, setState] = useState<{
options: Record<string, unknown>; options: Record<string, unknown>;
+8 -10
View File
@@ -1,13 +1,11 @@
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
export const findMigrationsFolder = () => { const migrationsFolderCandidates = [
const cwd = process.cwd(); path.join(process.cwd(), "db/migrations/sqlite"),
const migrationsFolderCandidates = [ path.join(process.cwd(), "packages/db/migrations/sqlite"),
path.join(cwd, "db/migrations/sqlite"), path.resolve(process.cwd(), "../../db/migrations/sqlite"),
path.join(cwd, "packages/db/migrations/sqlite"), "/app/db/migrations/sqlite",
path.resolve(cwd, "../../db/migrations/sqlite"), ];
"/app/db/migrations/sqlite",
]; export const findMigrationsFolder = () => migrationsFolderCandidates.find((candidate) => fs.existsSync(candidate));
return migrationsFolderCandidates.find((candidate) => fs.existsSync(candidate));
};
+1 -1
View File
@@ -18,7 +18,7 @@ interface Props {
export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => { export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => {
return ( return (
<AppShellHeader maw="100vw" zIndex={201} style={{ overflowX: "hidden" }}> <AppShellHeader maw="100vw" style={{ overflowX: "hidden" }}>
<Group h="100%" gap="xl" px="md" justify="apart" wrap="nowrap"> <Group h="100%" gap="xl" px="md" justify="apart" wrap="nowrap">
<Group h="100%" align="center" style={{ flex: 1 }} wrap="nowrap"> <Group h="100%" align="center" style={{ flex: 1 }} wrap="nowrap">
{hasNavigation && <ClientBurger />} {hasNavigation && <ClientBurger />}
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "homarr-prebuilt-debian", "name": "homarr-prebuilt-debian",
"private": true, "private": true,
"dependencies": { "dependencies": {
"better-sqlite3": "^12.11.1" "better-sqlite3": "^12.10.0"
}, },
"engines": { "engines": {
"node": ">=24.18.0", "node": ">=24.18.0",
+5 -5
View File
@@ -9,16 +9,16 @@ importers:
.: .:
dependencies: dependencies:
better-sqlite3: better-sqlite3:
specifier: ^12.11.1 specifier: ^12.10.0
version: 12.11.1 version: 12.10.0
packages: packages:
base64-js@1.5.1: base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
better-sqlite3@12.11.1: better-sqlite3@12.10.0:
resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==} resolution: {integrity: sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==}
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x}
bindings@1.5.0: bindings@1.5.0:
@@ -148,7 +148,7 @@ snapshots:
base64-js@1.5.1: {} base64-js@1.5.1: {}
better-sqlite3@12.11.1: better-sqlite3@12.10.0:
dependencies: dependencies:
bindings: 1.5.0 bindings: 1.5.0
prebuild-install: 7.1.3 prebuild-install: 7.1.3
+1 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "homarr", "name": "homarr",
"version": "1.69.2", "version": "1.69.1",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "cross-env CI=true turbo build", "build": "cross-env CI=true turbo build",
@@ -43,7 +43,6 @@
"@semantic-release/release-notes-generator": "catalog:", "@semantic-release/release-notes-generator": "catalog:",
"@testcontainers/redis": "catalog:", "@testcontainers/redis": "catalog:",
"@turbo/gen": "catalog:", "@turbo/gen": "catalog:",
"@vitejs/plugin-react": "catalog:", "@vitejs/plugin-react": "catalog:",
"@vitest/coverage-v8": "catalog:", "@vitest/coverage-v8": "catalog:",
"@vitest/ui": "catalog:", "@vitest/ui": "catalog:",
+43
View File
@@ -232,6 +232,7 @@ export const userRouter = createTRPCRouter({
email: true, email: true,
emailVerified: true, emailVerified: true,
image: true, image: true,
disabled: true,
}), }),
), ),
) )
@@ -252,6 +253,7 @@ export const userRouter = createTRPCRouter({
email: true, email: true,
emailVerified: true, emailVerified: true,
image: true, image: true,
disabled: true,
}, },
}); });
}), }),
@@ -341,6 +343,7 @@ export const userRouter = createTRPCRouter({
emailVerified: true, emailVerified: true,
image: true, image: true,
provider: true, provider: true,
disabled: true,
homeBoardId: true, homeBoardId: true,
mobileHomeBoardId: true, mobileHomeBoardId: true,
firstDayOfWeek: true, firstDayOfWeek: true,
@@ -377,6 +380,7 @@ export const userRouter = createTRPCRouter({
emailVerified: true, emailVerified: true,
image: true, image: true,
provider: true, provider: true,
disabled: true,
homeBoardId: true, homeBoardId: true,
mobileHomeBoardId: true, mobileHomeBoardId: true,
firstDayOfWeek: true, firstDayOfWeek: true,
@@ -777,6 +781,45 @@ export const userRouter = createTRPCRouter({
completedBoardTour: user?.completedBoardTour ?? false, completedBoardTour: user?.completedBoardTour ?? false,
}; };
}), }),
changeDisabled: permissionRequiredProcedure
.requiresPermission("admin")
.input(
z.object({
userId: z.string(),
disabled: z.boolean(),
}),
)
.output(z.void())
.meta({
openapi: {
method: "PATCH",
path: "/api/users/disabled",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ input, ctx }) => {
const dbUser = await ctx.db.query.users.findFirst({
columns: {
id: true,
},
where: eq(users.id, input.userId),
});
if (!dbUser) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
await ctx.db
.update(users)
.set({
disabled: input.disabled,
})
.where(eq(users.id, input.userId));
}),
}); });
const createUserAsync = async (db: Database, input: Omit<z.infer<typeof userBaseCreateSchema>, "groupIds">) => { const createUserAsync = async (db: Database, input: Omit<z.infer<typeof userBaseCreateSchema>, "groupIds">) => {
@@ -23,6 +23,11 @@ export const authorizeWithBasicCredentialsAsync = async (
return null; return null;
} }
if (user.disabled) {
logger.warn("User is disabled", { userName: user.name });
return null;
}
logger.info("User is trying to log in. Checking password...", { userName: user.name }); logger.info("User is trying to log in. Checking password...", { userName: user.name });
const isValidPassword = await comparePasswordsAsync(credentials.password, user.password); const isValidPassword = await comparePasswordsAsync(credentials.password, user.password);
@@ -107,6 +107,7 @@ export const authorizeWithLdapCredentialsAsync = async (
email: true, email: true,
emailVerified: true, emailVerified: true,
provider: true, provider: true,
disabled: true,
}, },
where: and(eq(users.email, mailResult.data), eq(users.provider, "ldap")), where: and(eq(users.email, mailResult.data), eq(users.provider, "ldap")),
}); });
@@ -130,6 +131,11 @@ export const authorizeWithLdapCredentialsAsync = async (
logger.info("User created successfully", { userName: credentials.name }); logger.info("User created successfully", { userName: credentials.name });
} }
if (user.disabled) {
logger.warn("User is disabled", { userName: credentials.name });
throw new CredentialsSignin("User is disabled");
}
return { return {
id: user.id, id: user.id,
name: credentials.name, name: credentials.name,
+1 -1
View File
@@ -6,5 +6,5 @@ export function parseCookies(cookieString: string) {
} }
export function setClientCookie(name: string, value: string, options: SerializeOptions = {}) { export function setClientCookie(name: string, value: string, options: SerializeOptions = {}) {
document.cookie = stringifySetCookie({ name, value, ...options }); document.cookie = stringifySetCookie(name, value, options);
} }
@@ -25,7 +25,7 @@ export class AxiosHttpErrorHandler extends HttpErrorHandler {
return new RequestError(requestErrorInput, { return new RequestError(requestErrorInput, {
cause: error, cause: error,
}) as AnyRequestError; });
} }
handleResponseError(error: unknown): ResponseError | undefined { handleResponseError(error: unknown): ResponseError | undefined {
if (!(error instanceof AxiosError)) return undefined; if (!(error instanceof AxiosError)) return undefined;
@@ -20,7 +20,7 @@ export class FetchHttpErrorHandler extends HttpErrorHandler {
const result = matchErrorCode(error.cause.code); const result = matchErrorCode(error.cause.code);
if (!result) return undefined; if (!result) return undefined;
return new RequestError(result, { cause: error }) as AnyRequestError; return new RequestError(result, { cause: error });
} }
/** /**
@@ -59,7 +59,7 @@ export const matchErrorCode = (code: string): AnyRequestErrorInput | undefined =
type: key, type: key,
reason: found[0] as RequestErrorReason<typeof key>, reason: found[0] as RequestErrorReason<typeof key>,
code: code as RequestErrorCode, code: code as RequestErrorCode,
} as AnyRequestErrorInput; };
} }
return undefined; return undefined;
@@ -30,7 +30,7 @@ export class NodeFetchHttpErrorHandler extends HttpErrorHandler {
return new RequestError(requestErrorInput, { return new RequestError(requestErrorInput, {
cause: error, cause: error,
}) as AnyRequestError; });
} }
/** /**
@@ -0,0 +1 @@
ALTER TABLE `user` ADD `disabled` boolean DEFAULT false NOT NULL;
@@ -0,0 +1 @@
ALTER TABLE "user" ADD COLUMN "disabled" boolean DEFAULT false NOT NULL;
@@ -0,0 +1 @@
ALTER TABLE "user" ADD COLUMN "disabled" integer DEFAULT 0 NOT NULL;
+1
View File
@@ -72,6 +72,7 @@ export const users = mysqlTable("user", {
image: text(), image: text(),
password: text(), password: text(),
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(), provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
disabled: boolean().default(false).notNull(),
homeBoardId: varchar({ length: 64 }).references((): AnyMySqlColumn => boards.id, { homeBoardId: varchar({ length: 64 }).references((): AnyMySqlColumn => boards.id, {
onDelete: "set null", onDelete: "set null",
}), }),
+1
View File
@@ -71,6 +71,7 @@ export const users = pgTable("user", {
image: text(), image: text(),
password: text(), password: text(),
provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(), provider: varchar({ length: 64 }).$type<SupportedAuthProvider>().default("credentials").notNull(),
disabled: boolean().default(false).notNull(),
homeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, { homeBoardId: varchar({ length: 64 }).references((): AnyPgColumn => boards.id, {
onDelete: "set null", onDelete: "set null",
}), }),
+1
View File
@@ -54,6 +54,7 @@ export const users = sqliteTable("user", {
image: text(), image: text(),
password: text(), password: text(),
provider: text().$type<SupportedAuthProvider>().default("credentials").notNull(), provider: text().$type<SupportedAuthProvider>().default("credentials").notNull(),
disabled: int({ mode: "boolean" }).default(false).notNull(),
homeBoardId: text().references((): AnySQLiteColumn => boards.id, { homeBoardId: text().references((): AnySQLiteColumn => boards.id, {
onDelete: "set null", onDelete: "set null",
}), }),
@@ -72,14 +72,7 @@ export const AppForm = ({
} }
form.initialize(toFormValues(initialValues)); form.initialize(toFormValues(initialValues));
// `form` is a new object every render (Mantine useForm returns a fresh literal), }, [initialValuesKey, form, initialValues]);
// so it must not be a dep here — it would re-run every render and, since
// form.initialize() always calls clearErrors() (new {} object) internally,
// trigger an infinite update loop. initialValuesKey (a primitive string)
// already captures the only case this effect exists for: re-initializing
// when initialValues actually changes (e.g. navigating between edit pages).
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialValuesKey]);
// Debounce the name value with 200ms delay // Debounce the name value with 200ms delay
const [debouncedName] = useDebouncedValue(form.values.name, 200); const [debouncedName] = useDebouncedValue(form.values.name, 200);
+33 -33
View File
@@ -6,9 +6,9 @@
"subtitle": "", "subtitle": "",
"description": "", "description": "",
"action": { "action": {
"scratch": "Sākt no jauna", "scratch": "",
"importOldmarr": "", "importOldmarr": "",
"restoreSqlite": "SQLite rezerves kopija", "restoreSqlite": "",
"restoreSqliteDescription": "", "restoreSqliteDescription": "",
"backToOptions": "" "backToOptions": ""
} }
@@ -26,19 +26,19 @@
} }
}, },
"importSettings": { "importSettings": {
"title": "Importēt iestatījumus", "title": "",
"description": "" "description": ""
}, },
"boardSelection": { "boardSelection": {
"title": "", "title": "",
"description": "", "description": "",
"action": { "action": {
"selectAll": "Atzīmēt visu", "selectAll": "",
"unselectAll": "" "unselectAll": ""
} }
}, },
"summary": { "summary": {
"title": "Importēšanas kopsavilkums", "title": "",
"description": "", "description": "",
"action": { "action": {
"import": "" "import": ""
@@ -46,7 +46,7 @@
"entities": { "entities": {
"apps": "Lietotnes", "apps": "Lietotnes",
"boards": "Dēļi", "boards": "Dēļi",
"integrations": "Integrācijas", "integrations": "",
"credentialUsers": "" "credentialUsers": ""
} }
}, },
@@ -67,15 +67,15 @@
} }
}, },
"user": { "user": {
"title": "Administratora lietotājs", "title": "",
"subtitle": "", "subtitle": "",
"notification": { "notification": {
"success": { "success": {
"title": "Lietotājs izveidots", "title": "",
"message": "" "message": ""
}, },
"error": { "error": {
"title": "Lietotāja izveide neizdevās" "title": ""
} }
} }
}, },
@@ -94,19 +94,19 @@
"subtitle": "" "subtitle": ""
}, },
"integrations": { "integrations": {
"title": "Integrācijas", "title": "",
"subtitle": "", "subtitle": "",
"description": "", "description": "",
"action": { "action": {
"continue": "Turpināt", "continue": "",
"skip": "Pagaidām izlaist šo soli" "skip": ""
}, },
"configure": { "configure": {
"progress": "{current} no {total}", "progress": "",
"skip": "Izlaist šo integrāciju", "skip": "",
"finishing": "", "finishing": "",
"error": { "error": {
"title": "Neizdevās iestatīt!", "title": "",
"message": "" "message": ""
}, },
"helpers": { "helpers": {
@@ -115,7 +115,7 @@
} }
}, },
"urlTemplate": { "urlTemplate": {
"label": "Bāzes URL", "label": "",
"placeholder": "", "placeholder": "",
"hint": "", "hint": "",
"warning": "", "warning": "",
@@ -136,14 +136,14 @@
} }
}, },
"finish": { "finish": {
"title": "Pabeigt Iestatīšanu", "title": "",
"subtitle": "", "subtitle": "",
"description": "", "description": "",
"action": { "action": {
"goToBoard": "", "goToBoard": "",
"createBoard": "", "createBoard": "",
"inviteUser": "Ielūgt citus lietotājus", "inviteUser": "",
"docs": "Lasīt dokumentāciju" "docs": ""
} }
} }
}, },
@@ -158,7 +158,7 @@
"subtitle": "" "subtitle": ""
}, },
"invite": { "invite": {
"title": "Pievienoties Homarr", "title": "",
"subtitle": "", "subtitle": "",
"description": "" "description": ""
}, },
@@ -177,20 +177,20 @@
}, },
"password": { "password": {
"label": "Parole", "label": "Parole",
"suggestionsTitle": "Paroles ieteikumi", "suggestionsTitle": "",
"suggestion": { "suggestion": {
"length": "Vismaz 8 rakstzīmes", "length": "",
"lowercase": "Ietver mazo burtu", "lowercase": "Ietver mazo burtu",
"uppercase": "Ietver lielo burtu", "uppercase": "Ietver lielo burtu",
"number": "Ietver numuru", "number": "Ietver numuru",
"special": "Iekļauj speciālo simbolu" "special": ""
} }
}, },
"passwordConfirm": { "passwordConfirm": {
"label": "Apstipriniet paroli" "label": "Apstipriniet paroli"
}, },
"previousPassword": { "previousPassword": {
"label": "Iepriekšējā parole" "label": ""
}, },
"homeBoard": { "homeBoard": {
"label": "" "label": ""
@@ -218,10 +218,10 @@
"notification": { "notification": {
"success": { "success": {
"title": "", "title": "",
"message": "Jūs esat veiksmīgi pieteicies" "message": ""
}, },
"error": { "error": {
"title": "Pieteikšanās neizdevās", "title": "",
"message": "" "message": ""
} }
}, },
@@ -245,13 +245,13 @@
}, },
"create": "Izveidot lietotāju", "create": "Izveidot lietotāju",
"changePassword": { "changePassword": {
"label": "Mainīt paroli", "label": "",
"notification": { "notification": {
"success": { "success": {
"message": "Parole veiksmīgi nomainīta." "message": ""
}, },
"error": { "error": {
"message": "Nav iespējams nomainīt paroli" "message": ""
} }
} }
}, },
@@ -595,12 +595,12 @@
} }
}, },
"defaultGroup": { "defaultGroup": {
"name": "Noklusētā grupa", "name": "",
"description": "" "description": ""
} }
}, },
"app": { "app": {
"search": "Atrast aplikāciju", "search": "",
"page": { "page": {
"list": { "list": {
"title": "Lietotnes", "title": "Lietotnes",
@@ -689,7 +689,7 @@
"list": { "list": {
"title": "", "title": "",
"noResults": "", "noResults": "",
"loading": "Notiek ielāde..." "loading": ""
}, },
"create": { "create": {
"title": "", "title": "",
@@ -700,7 +700,7 @@
} }
}, },
"action": { "action": {
"create": "Izveidot", "create": "",
"import": "", "import": "",
"save": "", "save": "",
"delete": "", "delete": "",
+12 -12
View File
@@ -1599,7 +1599,7 @@
"menu": { "menu": {
"label": { "label": {
"settings": "Ustawienia", "settings": "Ustawienia",
"refresh": "Odśwież", "refresh": "",
"options": "Opcje", "options": "Opcje",
"actions": "Akcje" "actions": "Akcje"
} }
@@ -1904,7 +1904,7 @@
"noStation": "Nie wybrano stacji", "noStation": "Nie wybrano stacji",
"option": { "option": {
"baseUrl": { "baseUrl": {
"label": "Szukaj adresu URL" "label": ""
}, },
"station": { "station": {
"label": "Stacja" "label": "Stacja"
@@ -2791,8 +2791,8 @@
"description": "Wyświetl datę zdjęcia oraz bieżący indeks na dole pokazu slajdów" "description": "Wyświetl datę zdjęcia oraz bieżący indeks na dole pokazu slajdów"
}, },
"randomizePhotos": { "randomizePhotos": {
"label": "Losowe zdjęcia", "label": "",
"description": "Pokaż zdjęcia w losowej kolejności, zamiast zawsze rozpoczynać się od tej samej kolejności albumu." "description": ""
} }
}, },
"description": "Pokaz slajdów zdjęć z Twojego albumu w Immich", "description": "Pokaz slajdów zdjęć z Twojego albumu w Immich",
@@ -3158,7 +3158,7 @@
"label": "Dostawca" "label": "Dostawca"
}, },
"providerUrl": { "providerUrl": {
"label": "URL dostawcy" "label": ""
}, },
"identifier": { "identifier": {
"label": "Identyfikator", "label": "Identyfikator",
@@ -3209,11 +3209,11 @@
"tooltip": "Nie udało się rozpoznać dostawcy. Ustaw go ręcznie po zaimportowaniu obrazów." "tooltip": "Nie udało się rozpoznać dostawcy. Ustaw go ręcznie po zaimportowaniu obrazów."
}, },
"tokens": { "tokens": {
"label": "Tokeny dostawcy", "label": "",
"token": "Token", "token": "",
"configured": "Token skonfigurowany", "configured": "",
"notConfigured": "Brak ustawionych tokenów", "notConfigured": "",
"save": "Zapisz" "save": ""
} }
} }
}, },
@@ -3236,9 +3236,9 @@
"invalidIdentifier": "Nieprawidłowy identyfikator", "invalidIdentifier": "Nieprawidłowy identyfikator",
"noMatchingVersion": "Nie znaleziono pasującej wersji", "noMatchingVersion": "Nie znaleziono pasującej wersji",
"noReleasesFound": "Nie znaleziono wydań!", "noReleasesFound": "Nie znaleziono wydań!",
"noProviderSelected": "Nie wybrano dostawcy", "noProviderSelected": "",
"noProviderResponse": "Brak odpowiedzi od dostawcy", "noProviderResponse": "Brak odpowiedzi od dostawcy",
"unexpected": "Nieoczekiwany błąd dostawcy" "unexpected": ""
} }
} }
}, },
+1 -1
View File
@@ -3437,7 +3437,7 @@
"description": "" "description": ""
}, },
"maxNowPlayingItems": { "maxNowPlayingItems": {
"label": "Максимум елементів для відтворення", "label": "",
"description": "" "description": ""
}, },
"showLibraryCount": { "showLibraryCount": {
+658 -704
View File
File diff suppressed because it is too large. Load diff
+31 -35
View File
@@ -14,7 +14,7 @@ catalog:
"@dnd-kit/utilities": ^3.2.2 "@dnd-kit/utilities": ^3.2.2
"@drizzle-team/brocli": ^0.12.0 "@drizzle-team/brocli": ^0.12.0
"@extractus/feed-extractor": 7.2.1 "@extractus/feed-extractor": 7.2.1
"@gfazioli/mantine-onboarding-tour": ^4.0.6 '@gfazioli/mantine-onboarding-tour': ^4.0.6
"@gitbeaker/rest": ^43.8.0 "@gitbeaker/rest": ^43.8.0
"@homarr/gridstack": ^1.12.0 "@homarr/gridstack": ^1.12.0
"@homarr/node-unifi": ^2.6.0 "@homarr/node-unifi": ^2.6.0
@@ -40,16 +40,16 @@ catalog:
"@semantic-release/changelog": ^6.0.3 "@semantic-release/changelog": ^6.0.3
"@semantic-release/commit-analyzer": ^13.0.1 "@semantic-release/commit-analyzer": ^13.0.1
"@semantic-release/git": ^10.0.1 "@semantic-release/git": ^10.0.1
"@semantic-release/github": ^12.0.9 "@semantic-release/github": ^12.0.8
"@semantic-release/npm": ^13.1.5 "@semantic-release/npm": ^13.1.5
"@semantic-release/release-notes-generator": ^14.1.1 "@semantic-release/release-notes-generator": ^14.1.1
"@t3-oss/env-nextjs": ^0.13.11 "@t3-oss/env-nextjs": ^0.13.11
"@tabler/icons-react": ^3.44.0 "@tabler/icons-react": ^3.44.0
"@tanstack/query-async-storage-persister": ^5.101.2 "@tanstack/query-async-storage-persister": ^5.101.0
"@tanstack/react-query": ^5.101.2 "@tanstack/react-query": ^5.101.0
"@tanstack/react-query-devtools": ^5.101.2 "@tanstack/react-query-devtools": ^5.101.0
"@tanstack/react-query-next-experimental": ^5.101.2 "@tanstack/react-query-next-experimental": ^5.101.0
"@tanstack/react-query-persist-client": ^5.101.2 "@tanstack/react-query-persist-client": ^5.101.0
"@testcontainers/mysql": ^12.0.4 "@testcontainers/mysql": ^12.0.4
"@testcontainers/postgresql": ^12.0.4 "@testcontainers/postgresql": ^12.0.4
"@testcontainers/redis": ^12.0.4 "@testcontainers/redis": ^12.0.4
@@ -69,12 +69,12 @@ catalog:
"@tiptap/extension-underline": 3.27.1 "@tiptap/extension-underline": 3.27.1
"@tiptap/react": ^3.27.1 "@tiptap/react": ^3.27.1
"@tiptap/starter-kit": ^3.27.1 "@tiptap/starter-kit": ^3.27.1
"@trpc/client": ^11.18.0 "@trpc/client": ^11.17.0
"@trpc/next": ^11.18.0 "@trpc/next": ^11.17.0
"@trpc/react-query": ^11.18.0 "@trpc/react-query": ^11.17.0
"@trpc/server": ^11.18.0 "@trpc/server": ^11.17.0
"@trpc/tanstack-react-query": ^11.18.0 "@trpc/tanstack-react-query": ^11.17.0
"@turbo/gen": ^2.10.2 "@turbo/gen": ^2.10.0
"@types/adm-zip": 0.5.8 "@types/adm-zip": 0.5.8
"@types/bcrypt": 6.0.0 "@types/bcrypt": 6.0.0
"@types/better-sqlite3": 7.6.13 "@types/better-sqlite3": 7.6.13
@@ -89,29 +89,29 @@ catalog:
"@types/prismjs": ^1.26.6 "@types/prismjs": ^1.26.6
"@types/react": 19.2.17 "@types/react": 19.2.17
"@types/react-dom": 19.2.3 "@types/react-dom": 19.2.3
"@scalar/api-reference-react": ^0.9.50 "@scalar/api-reference-react": ^0.9.49
"@types/video.js": ^7.3.58 "@types/video.js": ^7.3.58
"@types/ws": ^8.18.1 "@types/ws": ^8.18.1
"@types/xml2js": ^0.4.14 "@types/xml2js": ^0.4.14
posthog-js: ^1.396.4 posthog-js: ^1.396.2
posthog-node: ^5.39.2 posthog-node: ^4.18.0
"@vitejs/plugin-react": ^6.0.3 "@vitejs/plugin-react": ^6.0.3
"@vitest/coverage-v8": ^4.1.9 "@vitest/coverage-v8": ^4.1.9
"@vitest/ui": ^4.1.9 "@vitest/ui": ^4.1.9
"@xterm/addon-canvas": ^0.7.0 "@xterm/addon-canvas": ^0.7.0
"@xterm/addon-fit": 0.11.0 "@xterm/addon-fit": 0.11.0
"@xterm/xterm": ^6.0.0 "@xterm/xterm": ^6.0.0
adm-zip: 0.5.18 adm-zip: 0.5.17
jszip: ^3.10.1 jszip: ^3.10.1
sql.js: ^1.14.1 sql.js: ^1.14.1
babel-plugin-react-compiler: ^1.0.0 babel-plugin-react-compiler: ^1.0.0
bcrypt: ^6.0.0 bcrypt: ^6.0.0
better-sqlite3: ^12.11.1 better-sqlite3: ^12.10.0
chroma-js: ^3.2.0 chroma-js: ^3.2.0
clsx: ^2.1.1 clsx: ^2.1.1
concurrently: ^10.0.3 concurrently: ^10.0.3
conventional-changelog-conventionalcommits: ^10.2.0 conventional-changelog-conventionalcommits: ^9.3.1
cookie: 2.0.1 cookie: 1.1.1
cookies: ^0.9.1 cookies: ^0.9.1
croner: 10.0.1 croner: 10.0.1
cross-env: ^10.1.0 cross-env: ^10.1.0
@@ -139,7 +139,7 @@ catalog:
jsonpath-plus: ^10.4.0 jsonpath-plus: ^10.4.0
ldapts: 8.1.8 ldapts: 8.1.8
lodash.clonedeep: ^4.5.0 lodash.clonedeep: ^4.5.0
lucide-react: ^1.23.0 lucide-react: ^1.22.0
"mantine-contextmenu": ^9.3.1 "mantine-contextmenu": ^9.3.1
mantine-datatable: ^9.3.1 mantine-datatable: ^9.3.1
mantine-react-table: npm:mantine-react-table-open@9.0.3 mantine-react-table: npm:mantine-react-table-open@9.0.3
@@ -148,11 +148,11 @@ catalog:
mysql2: 3.22.5 mysql2: 3.22.5
next: 16.2.9 next: 16.2.9
next-auth: 5.0.0-beta.31 next-auth: 5.0.0-beta.31
next-intl: 4.13.1 next-intl: 4.13.0
node-loader: ^2.1.0 node-loader: ^2.1.0
octokit: ^5.0.5 octokit: ^5.0.5
oxfmt: ^0.57.0 oxfmt: ^0.56.0
oxlint: ^1.72.0 oxlint: ^1.71.0
pg: ^8.22.0 pg: ^8.22.0
postcss-preset-mantine: ^1.18.0 postcss-preset-mantine: ^1.18.0
prismjs: ^1.30.0 prismjs: ^1.30.0
@@ -164,7 +164,7 @@ catalog:
react-markdown: ^10.1.0 react-markdown: ^10.1.0
react-quill-new: ^3.8.3 react-quill-new: ^3.8.3
react-simple-code-editor: ^0.14.1 react-simple-code-editor: ^0.14.1
recharts: ^3.9.1 recharts: ^3.9.0
sass: ^1.101.0 sass: ^1.101.0
semantic-release: ^25.0.5 semantic-release: ^25.0.5
semver-parser: ^4.2.1 semver-parser: ^4.2.1
@@ -174,11 +174,11 @@ catalog:
timezones-ical-library: ^2.2.1 timezones-ical-library: ^2.2.1
trpc-to-mcp: ^1.3.2 trpc-to-mcp: ^1.3.2
trpc-to-openapi: ^3.3.0 trpc-to-openapi: ^3.3.0
tsdav: ^2.3.0 tsdav: ^2.2.2
tsx: 4.20.4 tsx: 4.20.4
turbo: ^2.10.2 turbo: ^2.10.0
typescript: ^6.0.3 typescript: ^6.0.3
vite: ^8.1.2 vite: ^8.1.0
undici: 7.28.0 undici: 7.28.0
use-deep-compare-effect: ^1.8.1 use-deep-compare-effect: ^1.8.1
video.js: ^8.23.9 video.js: ^8.23.9
@@ -191,7 +191,6 @@ catalog:
zod: ^4.4.3 zod: ^4.4.3
zod-form-data: ^3.0.2 zod-form-data: ^3.0.2
zod-validation-error: ^5.0.0 zod-validation-error: ^5.0.0
catalogMode: strict catalogMode: strict
nodeLinker: hoisted nodeLinker: hoisted
strictPeerDependencies: false strictPeerDependencies: false
@@ -208,32 +207,29 @@ minimumReleaseAgeExclude:
- form-data@4.0.6 - form-data@4.0.6
# Renovate security update: undici@7.28.0 # Renovate security update: undici@7.28.0
- undici@7.28.0 - undici@7.28.0
# Renovate security update: esbuild@0.28.1
- esbuild@0.28.1
overrides: overrides:
"@babel/helpers@<7.26.10": ">=7.29.7" "@babel/helpers@<7.26.10": ">=7.29.7"
"@babel/runtime@<7.26.10": ">=7.29.7" "@babel/runtime@<7.26.10": ">=7.29.7"
"axios@>=1.0.0 <1.8.2": ">=1.18.1" "axios@>=1.0.0 <1.8.2": ">=1.18.1"
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.7" "brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.7"
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.7" "brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.7"
"esbuild@<=0.24.2": ">=0.28.1" "esbuild@<=0.24.2": ">=0.28.0"
"form-data@>=4.0.0 <4.0.4": ">=4.0.6" "form-data@>=4.0.0 <4.0.4": ">=4.0.6"
"hono@<4.6.5": ">=4.12.27" "hono@<4.6.5": ">=4.12.27"
"linkifyjs@<4.3.2": ">=4.3.3" "linkifyjs@<4.3.2": ">=4.3.3"
"nanoid@>=4.0.0 <5.0.9": ">=5.1.16" "nanoid@>=4.0.0 <5.0.9": ">=5.1.16"
"prismjs@<1.30.0": ">=1.30.0" "prismjs@<1.30.0": ">=1.30.0"
"proxmox-api>undici": "7.28.0" "proxmox-api>undici": "7.27.1"
"react-is": "^19.2.7" "react-is": "^19.2.7"
"rollup@>=4.0.0 <4.22.4": ">=4.62.2" "rollup@>=4.0.0 <4.22.4": ">=4.62.2"
"sha.js@<=2.4.11": ">=2.4.12" "sha.js@<=2.4.11": ">=2.4.12"
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.3" "tar-fs@>=3.0.0 <3.0.9": ">=3.1.3"
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.3" "tar-fs@>=2.0.0 <2.1.3": ">=3.1.3"
"tmp@<=0.2.3": ">=0.2.7" "tmp@<=0.2.3": ">=0.2.7"
"vite@>=5.0.0 <=5.4.18": ">=8.1.2" "vite@>=5.0.0 <=5.4.18": ">=8.1.0"
patchedDependencies: patchedDependencies:
"@types/node-unifi": "patches/@types__node-unifi.patch" "@types/node-unifi": "patches/@types__node-unifi.patch"
"trpc-to-openapi": "patches/trpc-to-openapi.patch" "trpc-to-openapi": "patches/trpc-to-openapi.patch"
allowUnusedPatches: true allowUnusedPatches: true
allowBuilds: allowBuilds:
"@tree-sitter-grammars/tree-sitter-yaml": true "@tree-sitter-grammars/tree-sitter-yaml": true