Compare commits

..
33 Commits
Author SHA1 Message Date
Releases Homarr 5463402f5c chore(release): 1.48.0 [skip ci] 2025-12-26 19:17:04 +00:00
homarr-releases[bot] 5baa183718 chore(release): automatic release v1.48.0 2025-12-26 19:16:01 +00:00
homarr-renovate[bot] 93bf9ab84b fix(deps): update trpc monorepo to ^11.8.1 (#4750)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-26 12:50:49 +00:00
homarr-update-contributors[bot] 4ff98e3803 chore: update contributors (#4753)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2025-12-26 12:37:33 +00:00
homarr-renovate[bot] d8db0169a1 fix(deps): update turbo monorepo to ^2.7.1 (#4751)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-25 23:28:39 +00:00
homarr-renovate[bot] 96c2c0d604 fix(deps): update dependency ldapts to v8.0.30 (#4749)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-25 17:26:42 +00:00
Meier Lukas 5a57115ca0 feat(http): add proxy support (#4721) 2025-12-25 11:32:14 +01:00
homarr-renovate[bot] 707a4dad83 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.54.0 (#4748)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-25 10:31:00 +00:00
homarr-renovate[bot] 5dd7400684 fix(deps): update dependency ldapts to v8.0.29 (#4747)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-25 09:31:47 +00:00
homarr-renovate[bot] 6b774f47d0 fix(deps): update dependency sass to ^1.97.1 (#4746)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-24 22:28:25 +00:00
homarr-renovate[bot] adbe707c73 fix(deps): update tiptap monorepo to v3.14.0 (#4744)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-24 21:29:30 +00:00
homarr-renovate[bot] 75c545344a fix(deps): update dependency ldapts to v8.0.28 (#4743)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-24 21:27:03 +00:00
homarr-renovate[bot] 7c399f4073 chore(deps): update pnpm to v10.26.1 (#4742)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-24 20:31:05 +00:00
homarr-renovate[bot] 5d659a3ebb fix(deps): update turbo monorepo to ^2.7.0 (#4745)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-24 19:26:16 +00:00
homarr-renovate[bot] 99f2842a5a fix(deps): update nextjs monorepo to v16.1.0 (#4739)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-23 18:40:54 +00:00
homarr-renovate[bot] f34bdce644 fix(deps): update dependency mysql2 to v3.16.0 (#4731)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-23 16:23:11 +00:00
homarr-renovate[bot] 8891ac4239 fix(deps): update dependency ldapts to v8.0.27 (#4737)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-23 04:45:56 +00:00
homarr-crowdin[bot] 602c37574e chore(lang): updated translations from crowdin
Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
2025-12-22 00:58:49 +00:00
homarr-renovate[bot] f4d782abef fix(deps): update dependency next-intl to v4.6.1 (#4730)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-21 17:25:42 +00:00
homarr-renovate[bot] a20b479a42 chore(deps): update vitest monorepo to ^4.0.16 (#4728)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-21 15:27:45 +00:00
homarr-renovate[bot] 68dc6844fd fix(deps): update dependency zod to ^4.2.1 (#4722)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-21 09:37:08 +00:00
homarr-renovate[bot] 88baad8143 fix(deps): update dependency sass to ^1.97.0 (#4726)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-21 09:34:37 +00:00
homarr-renovate[bot] ef12b2b5c2 fix(deps): update dependency ldapts to v8.0.25 (#4724)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-21 06:40:05 +00:00
homarr-renovate[bot] 551a7ea469 chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.53.5 (#4723)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-21 06:39:51 +00:00
Manuel fbb5298cb0 feat: enable dns caching by default (#4706) 2025-12-20 21:07:57 +01:00
homarr-renovate[bot] 2dc8feae7e fix(deps): update dependency typescript-eslint to ^8.50.0 (#4720)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 17:25:20 +00:00
homarr-renovate[bot] 6e39524f9d fix(deps): update dependency zod to ^4.2.0 (#4712)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 13:37:00 +00:00
homarr-renovate[bot] b699f28177 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.3.0 (#4713)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 13:35:32 +00:00
homarr-renovate[bot] de39c7e85b chore(deps): update pnpm to v10.26.0 (#4715)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 12:48:32 +00:00
homarr-renovate[bot] e9c33cdd5a chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.53.4 (#4714)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 12:47:56 +00:00
homarr-renovate[bot] 648f7e2c77 fix(deps): update dependency ldapts to v8.0.24 (#4711)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 07:28:01 +00:00
homarr-renovate[bot] 41f40149b3 chore(deps): update dependency hono@&lt;4.6.5 to >=4.11.1 (#4708)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 07:27:41 +00:00
homarr-renovate[bot] 689523a194 fix(deps): update dependency @t3-oss/env-nextjs to ^0.13.10 (#4710)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 05:28:31 +00:00
54 changed files with 1405 additions and 1129 deletions

No files matched your search

+29
View File
@@ -1,3 +1,32 @@
## [1.48.0](https://github.com/homarr-labs/homarr/compare/v1.47.0...v1.48.0) (2025-12-26)
### Features
* enable dns caching by default ([#4706](https://github.com/homarr-labs/homarr/issues/4706)) ([fbb5298](https://github.com/homarr-labs/homarr/commit/fbb5298cb05b12eb2f19c121a1bc103caeb3e576))
* **http:** add proxy support ([#4721](https://github.com/homarr-labs/homarr/issues/4721)) ([5a57115](https://github.com/homarr-labs/homarr/commit/5a57115ca073aea1c75a000fda6a23648c5488e4))
### Bug Fixes
* **deps:** update dependency @t3-oss/env-nextjs to ^0.13.10 ([#4710](https://github.com/homarr-labs/homarr/issues/4710)) ([689523a](https://github.com/homarr-labs/homarr/commit/689523a194ab48bdc072b1cdf65f07ade6cbc584))
* **deps:** update dependency ldapts to v8.0.24 ([#4711](https://github.com/homarr-labs/homarr/issues/4711)) ([648f7e2](https://github.com/homarr-labs/homarr/commit/648f7e2c7743af81f4bced36b7e1e80c21e851e5))
* **deps:** update dependency ldapts to v8.0.25 ([#4724](https://github.com/homarr-labs/homarr/issues/4724)) ([ef12b2b](https://github.com/homarr-labs/homarr/commit/ef12b2b5c2e1131138937fb6da61ce4c167fe0eb))
* **deps:** update dependency ldapts to v8.0.27 ([#4737](https://github.com/homarr-labs/homarr/issues/4737)) ([8891ac4](https://github.com/homarr-labs/homarr/commit/8891ac42399e47fda6efc657b8ff2fe58aebca9e))
* **deps:** update dependency ldapts to v8.0.28 ([#4743](https://github.com/homarr-labs/homarr/issues/4743)) ([75c5453](https://github.com/homarr-labs/homarr/commit/75c545344aae39550917331458c01a894b6a942f))
* **deps:** update dependency ldapts to v8.0.29 ([#4747](https://github.com/homarr-labs/homarr/issues/4747)) ([5dd7400](https://github.com/homarr-labs/homarr/commit/5dd74006843ae72f63e631b14f0fd50c911d610b))
* **deps:** update dependency ldapts to v8.0.30 ([#4749](https://github.com/homarr-labs/homarr/issues/4749)) ([96c2c0d](https://github.com/homarr-labs/homarr/commit/96c2c0d604737c64722fa21bf79965c806bf4da3))
* **deps:** update dependency mysql2 to v3.16.0 ([#4731](https://github.com/homarr-labs/homarr/issues/4731)) ([f34bdce](https://github.com/homarr-labs/homarr/commit/f34bdce6446d4e62b54b96f63e91cfdddd51e901))
* **deps:** update dependency next-intl to v4.6.1 ([#4730](https://github.com/homarr-labs/homarr/issues/4730)) ([f4d782a](https://github.com/homarr-labs/homarr/commit/f4d782abefb78e085ca3fc66034c0ca54fb21120))
* **deps:** update dependency sass to ^1.97.0 ([#4726](https://github.com/homarr-labs/homarr/issues/4726)) ([88baad8](https://github.com/homarr-labs/homarr/commit/88baad8143f4ca830e50644d6bd1d1980ef1da82))
* **deps:** update dependency sass to ^1.97.1 ([#4746](https://github.com/homarr-labs/homarr/issues/4746)) ([6b774f4](https://github.com/homarr-labs/homarr/commit/6b774f47d0a8bf731e7e1837a9d47e0dc53a1ff0))
* **deps:** update dependency typescript-eslint to ^8.50.0 ([#4720](https://github.com/homarr-labs/homarr/issues/4720)) ([2dc8fea](https://github.com/homarr-labs/homarr/commit/2dc8feae7ea6174a1d826d8f0151ddc2c04c6aa6))
* **deps:** update dependency zod to ^4.2.0 ([#4712](https://github.com/homarr-labs/homarr/issues/4712)) ([6e39524](https://github.com/homarr-labs/homarr/commit/6e39524f9d28d201c13738212d238726e1685832))
* **deps:** update dependency zod to ^4.2.1 ([#4722](https://github.com/homarr-labs/homarr/issues/4722)) ([68dc684](https://github.com/homarr-labs/homarr/commit/68dc6844fdee53aca55b44dd7d19bf9d3a35c057))
* **deps:** update nextjs monorepo to v16.1.0 ([#4739](https://github.com/homarr-labs/homarr/issues/4739)) ([99f2842](https://github.com/homarr-labs/homarr/commit/99f2842a5a102056c76ddc68cb5083791f324c3c))
* **deps:** update tiptap monorepo to v3.14.0 ([#4744](https://github.com/homarr-labs/homarr/issues/4744)) ([adbe707](https://github.com/homarr-labs/homarr/commit/adbe707c73f462771c3d21cbbcc21e8245eea6f3))
* **deps:** update trpc monorepo to ^11.8.1 ([#4750](https://github.com/homarr-labs/homarr/issues/4750)) ([93bf9ab](https://github.com/homarr-labs/homarr/commit/93bf9ab84b5387eefb8cc0b03057a4ccf0b13aa9))
* **deps:** update turbo monorepo to ^2.7.0 ([#4745](https://github.com/homarr-labs/homarr/issues/4745)) ([5d659a3](https://github.com/homarr-labs/homarr/commit/5d659a3ebbf79266158cb57236de90e7883e7a4e))
* **deps:** update turbo monorepo to ^2.7.1 ([#4751](https://github.com/homarr-labs/homarr/issues/4751)) ([d8db016](https://github.com/homarr-labs/homarr/commit/d8db0169a142c2ec302806fe946cb17431ccae6b))
## [1.47.0](https://github.com/homarr-labs/homarr/compare/v1.46.0...v1.47.0) (2025-12-19)
### Features
+1
View File
@@ -45,6 +45,7 @@ COPY --from=builder /app/apps/nextjs/package.json .
COPY --from=builder /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs
COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
COPY --from=builder /app/packages/db/migrations ./db/migrations
+7 -7
View File
@@ -59,10 +59,10 @@
"@tanstack/react-query": "^5.90.12",
"@tanstack/react-query-devtools": "^5.91.1",
"@tanstack/react-query-next-experimental": "^5.91.0",
"@trpc/client": "^11.8.0",
"@trpc/next": "^11.8.0",
"@trpc/react-query": "^11.8.0",
"@trpc/server": "^11.8.0",
"@trpc/client": "^11.8.1",
"@trpc/next": "^11.8.1",
"@trpc/react-query": "^11.8.1",
"@trpc/server": "^11.8.1",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0",
@@ -76,18 +76,18 @@
"isomorphic-dompurify": "^2.34.0",
"jotai": "^2.16.0",
"mantine-react-table": "2.0.0-beta.9",
"next": "16.0.10",
"next": "16.1.0",
"postcss-preset-mantine": "^1.18.0",
"prismjs": "^1.30.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-error-boundary": "^6.0.0",
"react-simple-code-editor": "^0.14.1",
"sass": "^1.96.0",
"sass": "^1.97.1",
"superjson": "2.2.6",
"swagger-ui-react": "^5.31.0",
"use-deep-compare-effect": "^1.8.1",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -4,10 +4,10 @@
"dependencies": {
"better-sqlite3": "^12.5.0"
},
"packageManager": "pnpm@10.25.0",
"packageManager": "pnpm@10.26.1",
"engines": {
"node": ">=24.12.0",
"pnpm": ">=10.25.0"
"pnpm": ">=10.26.1"
},
"pnpm": {
"onlyBuiltDependencies": [
+12 -11
View File
@@ -1,6 +1,6 @@
{
"name": "homarr",
"version": "1.47.0",
"version": "1.48.0",
"private": true,
"scripts": {
"build": "cross-env CI=true turbo build",
@@ -43,10 +43,10 @@
"@semantic-release/npm": "^13.1.3",
"@semantic-release/release-notes-generator": "^14.1.0",
"@testcontainers/redis": "^11.10.0",
"@turbo/gen": "^2.6.3",
"@turbo/gen": "^2.7.1",
"@vitejs/plugin-react": "^5.1.2",
"@vitest/coverage-v8": "^4.0.15",
"@vitest/ui": "^4.0.15",
"@vitest/coverage-v8": "^4.0.16",
"@vitest/ui": "^4.0.16",
"conventional-changelog-conventionalcommits": "^9.1.0",
"cross-env": "^10.1.0",
"jsdom": "^27.3.0",
@@ -54,20 +54,21 @@
"prettier": "^3.7.4",
"semantic-release": "^25.0.2",
"testcontainers": "^11.10.0",
"turbo": "^2.6.3",
"turbo": "^2.7.1",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.15"
"vitest": "^4.0.16"
},
"packageManager": "pnpm@10.25.0",
"packageManager": "pnpm@10.26.1",
"engines": {
"node": ">=24.12.0",
"pnpm": ">=10.25.0"
"pnpm": ">=10.26.1"
},
"pnpm": {
"onlyBuiltDependencies": [
"@tree-sitter-grammars/tree-sitter-yaml",
"bcrypt",
"better-sqlite3",
"cpu-features",
"esbuild",
"sharp",
@@ -83,18 +84,18 @@
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
"esbuild@<=0.24.2": ">=0.27.1",
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
"hono@<4.6.5": ">=4.11.0",
"hono@<4.6.5": ">=4.11.1",
"linkifyjs@<4.3.2": ">=4.3.2",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
"prismjs@<1.30.0": ">=1.30.0",
"proxmox-api>undici": "7.16.0",
"react-is": "^19.2.3",
"rollup@>=4.0.0 <4.22.4": ">=4.53.3",
"rollup@>=4.0.0 <4.22.4": ">=4.54.0",
"sha.js@<=2.4.11": ">=2.4.12",
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
"tmp@<=0.2.3": ">=0.2.5",
"vite@>=5.0.0 <=5.4.18": ">=7.2.7"
"vite@>=5.0.0 <=5.4.18": ">=7.3.0"
},
"patchedDependencies": {
"@types/node-unifi": "patches/@types__node-unifi.patch",
+6 -6
View File
@@ -42,17 +42,17 @@
"@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.4.0",
"@tanstack/react-query": "^5.90.12",
"@trpc/client": "^11.8.0",
"@trpc/react-query": "^11.8.0",
"@trpc/server": "^11.8.0",
"@trpc/tanstack-react-query": "^11.8.0",
"@trpc/client": "^11.8.1",
"@trpc/react-query": "^11.8.1",
"@trpc/server": "^11.8.1",
"@trpc/tanstack-react-query": "^11.8.1",
"lodash.clonedeep": "^4.5.0",
"next": "16.0.10",
"next": "16.1.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"superjson": "2.2.6",
"trpc-to-openapi": "^3.1.0",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+8 -10
View File
@@ -5,7 +5,7 @@ import { createId } from "@homarr/common";
import { asc, eq, inArray, like } from "@homarr/db";
import { apps } from "@homarr/db/schema";
import { selectAppSchema } from "@homarr/db/validationSchemas";
import { getIconForNameAsync } from "@homarr/icons";
import { getIconForName } from "@homarr/icons";
import { appCreateManySchema, appEditSchema, appManageSchema } from "@homarr/validation/app";
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
@@ -141,15 +141,13 @@ export const appRouter = createTRPCRouter({
.output(z.void())
.mutation(async ({ ctx, input }) => {
await ctx.db.insert(apps).values(
await Promise.all(
input.map(async (app) => ({
id: createId(),
name: app.name,
description: app.description,
iconUrl: app.iconUrl ?? (await getIconForNameAsync(ctx.db, app.name))?.url ?? defaultIcon,
href: app.href,
})),
),
input.map((app) => ({
id: createId(),
name: app.name,
description: app.description,
iconUrl: app.iconUrl ?? getIconForName(ctx.db, app.name).sync()?.url ?? defaultIcon,
href: app.href,
})),
);
}),
update: permissionRequiredProcedure
+287
View File
@@ -477,6 +477,47 @@ export const boardRouter = createTRPCRouter({
}
});
},
handleSync(db) {
db.transaction((transaction) => {
transaction
.insert(boards)
.values({
...boardProps,
id: newBoardId,
name: input.name,
creatorId: ctx.session.user.id,
})
.run();
if (layoutsToInsert.length > 0) {
transaction.insert(layouts).values(layoutsToInsert).run();
}
if (sectionsToInsert.length > 0) {
transaction.insert(sections).values(sectionsToInsert).run();
}
if (sectionLayoutsToInsert.length > 0) {
transaction.insert(sectionLayouts).values(sectionLayoutsToInsert).run();
}
if (sectionCollapseStatesToInsert.length > 0) {
transaction.insert(sectionCollapseStates).values(sectionCollapseStatesToInsert).run();
}
if (itemsToInsert.length > 0) {
transaction.insert(items).values(itemsToInsert).run();
}
if (itemLayoutsToInsert.length > 0) {
transaction.insert(itemLayouts).values(itemLayoutsToInsert).run();
}
if (itemIntegrationsToInsert.length > 0) {
transaction.insert(integrationItems).values(itemIntegrationsToInsert).run();
}
});
},
});
}),
renameBoard: protectedProcedure.input(boardRenameSchema).mutation(async ({ ctx, input }) => {
@@ -913,6 +954,216 @@ export const boardRouter = createTRPCRouter({
}
});
},
handleSync(db) {
db.transaction((transaction) => {
const addedSections = filterAddedItems(input.sections, dbBoard.sections);
if (addedSections.length > 0) {
transaction
.insert(sections)
.values(
addedSections.map((section) => ({
id: section.id,
kind: section.kind,
yOffset: section.kind !== "dynamic" ? section.yOffset : null,
xOffset: section.kind === "dynamic" ? null : 0,
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
name: "name" in section ? section.name : null,
boardId: dbBoard.id,
})),
)
.run();
if (addedSections.some((section) => section.kind === "dynamic")) {
transaction
.insert(sectionLayouts)
.values(
addedSections
.filter((section) => section.kind === "dynamic")
.flatMap((section) =>
section.layouts.map(
(sectionLayout): InferInsertModel<typeof sectionLayouts> => ({
layoutId: sectionLayout.layoutId,
sectionId: section.id,
parentSectionId: sectionLayout.parentSectionId,
height: sectionLayout.height,
width: sectionLayout.width,
xOffset: sectionLayout.xOffset,
yOffset: sectionLayout.yOffset,
}),
),
),
)
.run();
}
}
const addedItems = filterAddedItems(input.items, dbBoard.items);
if (addedItems.length > 0) {
transaction
.insert(items)
.values(
addedItems.map((item) => ({
id: item.id,
kind: item.kind,
options: superjson.stringify(item.options),
advancedOptions: superjson.stringify(item.advancedOptions),
boardId: dbBoard.id,
})),
)
.run();
transaction
.insert(itemLayouts)
.values(
addedItems.flatMap((item) =>
item.layouts.map(
(layoutSection): InferInsertModel<typeof itemLayouts> => ({
layoutId: layoutSection.layoutId,
sectionId: layoutSection.sectionId,
itemId: item.id,
height: layoutSection.height,
width: layoutSection.width,
xOffset: layoutSection.xOffset,
yOffset: layoutSection.yOffset,
}),
),
),
)
.run();
}
const inputIntegrationRelations = input.items.flatMap(({ integrationIds, id: itemId }) =>
integrationIds.map((integrationId) => ({
integrationId,
itemId,
})),
);
const dbIntegrationRelations = dbBoard.items.flatMap(({ integrationIds, id: itemId }) =>
integrationIds.map((integrationId) => ({
integrationId,
itemId,
})),
);
const addedIntegrationRelations = inputIntegrationRelations.filter(
(inputRelation) =>
!dbIntegrationRelations.some(
(dbRelation) =>
dbRelation.itemId === inputRelation.itemId &&
dbRelation.integrationId === inputRelation.integrationId,
),
);
if (addedIntegrationRelations.length > 0) {
transaction
.insert(integrationItems)
.values(
addedIntegrationRelations.map((relation) => ({
itemId: relation.itemId,
integrationId: relation.integrationId,
})),
)
.run();
}
const updatedItems = filterUpdatedItems(input.items, dbBoard.items);
for (const item of updatedItems) {
transaction
.update(items)
.set({
kind: item.kind,
options: superjson.stringify(item.options),
advancedOptions: superjson.stringify(item.advancedOptions),
})
.where(eq(items.id, item.id))
.run();
for (const itemSectionLayout of item.layouts) {
transaction
.update(itemLayouts)
.set({
height: itemSectionLayout.height,
width: itemSectionLayout.width,
xOffset: itemSectionLayout.xOffset,
yOffset: itemSectionLayout.yOffset,
sectionId: itemSectionLayout.sectionId,
})
.where(and(eq(itemLayouts.itemId, item.id), eq(itemLayouts.layoutId, itemSectionLayout.layoutId)))
.run();
}
}
const updatedSections = filterUpdatedItems(input.sections, dbBoard.sections);
for (const section of updatedSections) {
const prev = dbBoard.sections.find((dbSection) => dbSection.id === section.id);
transaction
.update(sections)
.set({
yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null,
xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null,
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
name: prev?.kind === "category" && "name" in section ? section.name : null,
})
.where(eq(sections.id, section.id))
.run();
if (section.kind !== "dynamic") continue;
for (const sectionLayout of section.layouts) {
transaction
.update(sectionLayouts)
.set({
height: sectionLayout.height,
width: sectionLayout.width,
xOffset: sectionLayout.xOffset,
yOffset: sectionLayout.yOffset,
parentSectionId: sectionLayout.parentSectionId,
})
.where(
and(eq(sectionLayouts.sectionId, section.id), eq(sectionLayouts.layoutId, sectionLayout.layoutId)),
)
.run();
}
}
const removedIntegrationRelations = dbIntegrationRelations.filter(
(dbRelation) =>
!inputIntegrationRelations.some(
(inputRelation) =>
dbRelation.itemId === inputRelation.itemId &&
dbRelation.integrationId === inputRelation.integrationId,
),
);
for (const relation of removedIntegrationRelations) {
transaction
.delete(integrationItems)
.where(
and(
eq(integrationItems.itemId, relation.itemId),
eq(integrationItems.integrationId, relation.integrationId),
),
)
.run();
}
const removedItems = filterRemovedItems(input.items, dbBoard.items);
const itemIds = removedItems.map((item) => item.id);
if (itemIds.length > 0) {
transaction.delete(items).where(inArray(items.id, itemIds)).run();
}
const removedSections = filterRemovedItems(input.sections, dbBoard.sections);
const sectionIds = removedSections.map((section) => section.id);
if (sectionIds.length > 0) {
transaction.delete(sections).where(inArray(sections.id, sectionIds)).run();
}
});
},
});
}),
getBoardPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
@@ -1005,6 +1256,24 @@ export const boardRouter = createTRPCRouter({
);
});
},
handleSync(db) {
db.transaction((transaction) => {
transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)).run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(boardUserPermissions)
.values(
input.permissions.map((permission) => ({
userId: permission.principalId,
permission: permission.permission,
boardId: input.entityId,
})),
)
.run();
});
},
});
}),
saveGroupBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
@@ -1028,6 +1297,24 @@ export const boardRouter = createTRPCRouter({
);
});
},
handleSync(db) {
db.transaction((transaction) => {
transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)).run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(boardGroupPermissions)
.values(
input.permissions.map((permission) => ({
groupId: permission.principalId,
permission: permission.permission,
boardId: input.entityId,
})),
)
.run();
});
},
});
}),
importOldmarrConfig: permissionRequiredProcedure
@@ -91,7 +91,7 @@ export const certificateRouter = createTRPCRouter({
hostname: input.hostname,
thumbprint: input.thumbprint,
});
await ctx.db
const dbResult = await ctx.db
.delete(trustedCertificateHostnames)
.where(
and(
@@ -103,6 +103,7 @@ export const certificateRouter = createTRPCRouter({
logger.info("Removed trusted hostname", {
hostname: input.hostname,
thumbprint: input.thumbprint,
count: dbResult.changes,
});
}),
removeCertificate: permissionRequiredProcedure
+7
View File
@@ -260,6 +260,13 @@ export const groupRouter = createTRPCRouter({
}
});
},
handleSync: (db) => {
db.transaction((trx) => {
for (const { id, position } of positions) {
trx.update(groups).set({ position }).where(eq(groups.id, id)).run();
}
});
},
});
}),
savePermissions: permissionRequiredProcedure
@@ -541,6 +541,27 @@ export const integrationRouter = createTRPCRouter({
);
});
},
handleSync(db) {
db.transaction((transaction) => {
transaction
.delete(integrationUserPermissions)
.where(eq(integrationUserPermissions.integrationId, input.entityId))
.run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(integrationUserPermissions)
.values(
input.permissions.map((permission) => ({
userId: permission.principalId,
permission: permission.permission,
integrationId: input.entityId,
})),
)
.run();
});
},
});
}),
saveGroupIntegrationPermissions: protectedProcedure
@@ -566,6 +587,27 @@ export const integrationRouter = createTRPCRouter({
);
});
},
handleSync(db) {
db.transaction((transaction) => {
transaction
.delete(integrationGroupPermissions)
.where(eq(integrationGroupPermissions.integrationId, input.entityId))
.run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(integrationGroupPermissions)
.values(
input.permissions.map((permission) => ({
groupId: permission.principalId,
permission: permission.permission,
integrationId: input.entityId,
})),
)
.run();
});
},
});
}),
searchInIntegration: protectedProcedure
+8 -2
View File
@@ -1,6 +1,7 @@
import { z } from "zod/v4";
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { createTRPCRouter, publicProcedure } from "../trpc";
@@ -36,7 +37,12 @@ export const locationRouter = createTRPCRouter({
.input(locationSearchCityInput)
.output(locationSearchCityOutput)
.query(async ({ input }) => {
const res = await fetchWithTimeoutAsync(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
const res = await withTimeoutAsync(async (signal) => {
return await fetchWithTrustedCertificatesAsync(
`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`,
{ signal },
);
});
return (await res.json()) as z.infer<typeof locationSearchCityOutput>;
}),
});
+3 -3
View File
@@ -32,12 +32,12 @@
"@homarr/validation": "workspace:^0.1.0",
"bcrypt": "^6.0.0",
"cookies": "^0.9.1",
"ldapts": "8.0.23",
"next": "16.0.10",
"ldapts": "8.0.30",
"next": "16.1.0",
"next-auth": "5.0.0-beta.30",
"react": "19.2.3",
"react-dom": "19.2.3",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -29,12 +29,12 @@
"@homarr/core": "workspace:^0.1.0",
"@paralleldrive/cuid2": "^3.1.0",
"dayjs": "^1.11.19",
"next": "16.0.10",
"next": "16.1.0",
"octokit": "^5.0.5",
"react": "19.2.3",
"react-dom": "19.2.3",
"undici": "7.16.0",
"zod": "^4.1.13",
"zod": "^4.2.1",
"zod-validation-error": "^5.0.0"
},
"devDependencies": {
+5 -4
View File
@@ -37,21 +37,22 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@libsql/client": "^0.15.15",
"@t3-oss/env-nextjs": "^0.13.8",
"@t3-oss/env-nextjs": "^0.13.10",
"better-sqlite3": "^12.5.0",
"dns-caching": "^0.2.9",
"drizzle-orm": "^0.45.1",
"ioredis": "5.8.2",
"mysql2": "3.15.3",
"mysql2": "3.16.0",
"pg": "^8.16.3",
"superjson": "2.2.6",
"winston": "3.19.0",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0",
"eslint": "^9.39.2",
"typescript": "^5.9.3"
@@ -1,10 +1,10 @@
import { createClient } from "@libsql/client";
import { drizzle as drizzleSqlite } from "drizzle-orm/libsql";
import Database from "better-sqlite3";
import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
import { dbEnv } from "../env";
import type { SharedDrizzleConfig } from "./shared";
export const createSqliteDb = <TSchema extends Record<string, unknown>>(config: SharedDrizzleConfig<TSchema>) => {
const client = createClient({ url: `file:${dbEnv.URL}` });
return drizzleSqlite<TSchema>(client, config);
const connection = new Database(dbEnv.URL);
return drizzleSqlite<TSchema>(connection, config);
};
+1 -1
View File
@@ -2,7 +2,7 @@ import { createBooleanSchema, createEnv } from "../env";
export const dnsEnv = createEnv({
server: {
ENABLE_DNS_CACHING: createBooleanSchema(false),
ENABLE_DNS_CACHING: createBooleanSchema(true),
},
experimental__runtimeEnv: process.env,
});
@@ -1,5 +1,5 @@
import type { Dispatcher } from "undici";
import { Agent } from "undici";
import { EnvHttpProxyAgent } from "undici";
import type { ILogger } from "@homarr/core/infrastructure/logs";
import { createLogger } from "@homarr/core/infrastructure/logs";
@@ -7,16 +7,15 @@ import { createLogger } from "@homarr/core/infrastructure/logs";
// The below import statement initializes dns-caching
import "@homarr/core/infrastructure/dns/init";
interface HttpAgentOptions extends Agent.Options {
interface HttpAgentOptions extends EnvHttpProxyAgent.Options {
logger?: ILogger;
}
export class UndiciHttpAgent extends Agent {
export class UndiciHttpAgent extends EnvHttpProxyAgent {
private logger: ILogger;
constructor(props?: HttpAgentOptions) {
super(props);
this.logger = props?.logger ?? createLogger({ module: "httpAgent" });
}
@@ -42,12 +42,13 @@ export const createCertificateAgentAsync = async (override?: {
};
export const createHttpsAgentAsync = async (override?: Pick<AgentOptions, "ca" | "checkServerIdentity">) => {
return new HttpsAgent(
override ?? {
ca: await getAllTrustedCertificatesAsync(),
checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()),
},
);
return new HttpsAgent({
ca: await getAllTrustedCertificatesAsync(),
checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()),
// Override the ca and checkServerIdentity if provided
...override,
proxyEnv: process.env,
});
};
export const createAxiosCertificateInstanceAsync = async (
@@ -7,7 +7,7 @@ import { TestLogger } from "../logs";
vi.mock("undici", () => {
return {
Agent: class Agent {
EnvHttpProxyAgent: class EnvHttpProxyAgent {
dispatch(_options: Dispatcher.DispatchOptions, _handler: Dispatcher.DispatchHandler): boolean {
return true;
}
+4 -4
View File
@@ -29,12 +29,12 @@
"@homarr/core": "workspace:^0.1.0",
"@homarr/cron-jobs": "workspace:^0.1.0",
"@tanstack/react-query": "^5.90.12",
"@trpc/client": "^11.8.0",
"@trpc/server": "^11.8.0",
"@trpc/tanstack-react-query": "^11.8.0",
"@trpc/client": "^11.8.1",
"@trpc/server": "^11.8.1",
"@trpc/tanstack-react-query": "^11.8.1",
"node-cron": "^4.2.1",
"react": "19.2.3",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -121,6 +121,46 @@ export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, {
);
}
countDeleted += deadIcons.length;
});
},
handleSync() {
db.transaction((transaction) => {
if (newIconRepositories.length >= 1) {
transaction.insert(iconRepositories).values(newIconRepositories).run();
}
if (newIcons.length >= 1) {
// We only insert 5000 icons at a time to avoid SQLite limitations
for (const chunck of splitToNChunks(newIcons, Math.ceil(newIcons.length / 5000))) {
transaction.insert(icons).values(chunck).run();
}
}
if (deadIcons.length >= 1) {
transaction
.delete(icons)
.where(
inArray(
// Combine iconRepositoryId and checksum to allow same icons on different repositories
sql`concat(${icons.iconRepositoryId}, '.', ${icons.checksum})`,
deadIcons.map((icon) => `${icon.iconRepositoryId}.${icon.checksum}`),
),
)
.run();
}
if (deadIconRepositories.length >= 1) {
transaction
.delete(iconRepositories)
.where(
inArray(
iconRepositories.id,
deadIconRepositories.map((iconRepository) => iconRepository.id),
),
)
.run();
}
countDeleted += deadIcons.length;
});
},
+15 -3
View File
@@ -31,8 +31,20 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
return {
...context,
insertAll: (db: HomarrDatabase) => {
db.transaction((transaction) => {
for (const [key, values] of objectEntries(context)) {
if (values.length >= 1) {
transaction
.insert(schema[key])
.values(values as never)
.run();
}
}
});
},
// We allow any database that supports async passed here but then fallback to mysql to prevent typescript errors
insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql | HomarrDatabase) => {
insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql) => {
const innerDb = db as HomarrDatabaseMysql;
await innerDb.transaction(async (transaction) => {
for (const [key, values] of objectEntries(context)) {
@@ -49,7 +61,7 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
export const createDbInsertCollectionWithoutTransaction = <TTableKey extends TableKey>(
tablesInInsertOrder: TTableKey[],
) => {
const { insertAllAsync, ...collection } = createDbInsertCollectionForTransaction(tablesInInsertOrder);
const { insertAll, insertAllAsync, ...collection } = createDbInsertCollectionForTransaction(tablesInInsertOrder);
return {
...collection,
@@ -63,7 +75,7 @@ export const createDbInsertCollectionWithoutTransaction = <TTableKey extends Tab
default:
// For better-sqlite3, we need to use the synchronous insertAll method
// default assumes better-sqlite3. It's original implementation.
await insertAllAsync(db);
insertAll(db);
break;
}
},
+2 -2
View File
@@ -1,4 +1,4 @@
import type { LibSQLDatabase } from "drizzle-orm/libsql";
import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import type { MySql2Database } from "drizzle-orm/mysql2";
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
@@ -6,6 +6,6 @@ import type * as mysqlSchema from "./schema/mysql";
import type * as pgSchema from "./schema/postgresql";
import type * as sqliteSchema from "./schema/sqlite";
export type HomarrDatabase = LibSQLDatabase<typeof sqliteSchema>;
export type HomarrDatabase = BetterSQLite3Database<typeof sqliteSchema>;
export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>;
export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>;
+3 -1
View File
@@ -51,11 +51,12 @@
"@paralleldrive/cuid2": "^3.1.0",
"@testcontainers/mysql": "^11.10.0",
"@testcontainers/postgresql": "^11.10.0",
"better-sqlite3": "^12.5.0",
"dotenv": "^17.2.3",
"drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1",
"drizzle-zod": "^0.8.3",
"mysql2": "3.15.3",
"mysql2": "3.16.0",
"pg": "^8.16.3",
"superjson": "2.2.6"
},
@@ -63,6 +64,7 @@
"@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0",
"dotenv-cli": "^11.0.0",
"esbuild": "^0.27.1",
+5 -6
View File
@@ -1,16 +1,15 @@
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";
import { migrate } from "drizzle-orm/libsql/migrator";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import * as sqliteSchema from "../schema/sqlite";
export const createDb = (debug?: boolean) => {
const sqlite = createClient({ url: ":memory:" });
const sqlite = new Database(":memory:");
const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: DB_CASING });
// TODO: Make async and awaitable
void migrate(db, {
migrate(db, {
migrationsFolder: "./packages/db/migrations/sqlite",
});
+8 -1
View File
@@ -1,9 +1,11 @@
import { isMysql, isPostgresql } from "./collection";
import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver";
import type { MySqlSchema } from "./schema";
import * as schema from "./schema";
interface HandleTransactionInput {
handleAsync: (db: HomarrDatabaseMysql, schema: MySqlSchema) => Promise<void>;
handleSync: (db: HomarrDatabase) => void;
}
/**
@@ -12,5 +14,10 @@ interface HandleTransactionInput {
* But it can also generally be used when dealing with different database drivers.
*/
export const handleDiffrentDbDriverOperationsAsync = async (db: HomarrDatabase, input: HandleTransactionInput) => {
await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema);
if (isMysql() || isPostgresql()) {
// Schema type is always the correct one based on env variables
await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema);
} else {
input.handleSync(db);
}
};
+1 -1
View File
@@ -25,7 +25,7 @@
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"fast-xml-parser": "^5.3.3",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -28,7 +28,7 @@
"@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.3.10",
"mantine-form-zod-resolver": "^1.3.0",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.10",
"react": "19.2.3",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -2,8 +2,8 @@ import type { Database } from "@homarr/db";
import { like } from "@homarr/db";
import { icons } from "@homarr/db/schema";
export const getIconForNameAsync = async (db: Database, name: string) => {
return await db.query.icons.findFirst({
export const getIconForName = (db: Database, name: string) => {
return db.query.icons.findFirst({
where: like(icons.name, `%${name}%`),
});
};
@@ -1,6 +1,7 @@
import { parse } from "path";
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import type { IconRepositoryLicense } from "../types/icon-repository-license";
import type { RepositoryIconGroup } from "../types/repository-icon-group";
@@ -19,11 +20,12 @@ export class GitHubIconRepository extends IconRepository {
}
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
if (!this.repositoryIndexingUrl || !this.repositoryBlobUrlTemplate) {
const url = this.repositoryIndexingUrl;
if (!url || !this.repositoryBlobUrlTemplate) {
throw new Error("Repository URLs are required for this repository");
}
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
const response = await withTimeoutAsync(async (signal) => fetchWithTrustedCertificatesAsync(url, { signal }));
const listOfFiles = (await response.json()) as GitHubApiResponse;
return {
@@ -1,6 +1,7 @@
import { parse } from "path";
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import type { IconRepositoryLicense } from "../types/icon-repository-license";
import type { RepositoryIconGroup } from "../types/repository-icon-group";
@@ -19,7 +20,9 @@ export class JsdelivrIconRepository extends IconRepository {
}
protected async getAllIconsInternalAsync(): Promise<RepositoryIconGroup> {
const response = await fetchWithTimeoutAsync(this.repositoryIndexingUrl);
const response = await withTimeoutAsync(async (signal) =>
fetchWithTrustedCertificatesAsync(this.repositoryIndexingUrl, { signal }),
);
const listOfFiles = (await response.json()) as JsdelivrApiResponse;
return {
+1 -1
View File
@@ -48,7 +48,7 @@
"tsdav": "^2.1.6",
"undici": "7.16.0",
"xml2js": "^0.6.2",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -36,10 +36,10 @@
"@mantine/core": "^8.3.10",
"@tabler/icons-react": "^3.35.0",
"dayjs": "^1.11.19",
"next": "16.0.10",
"next": "16.1.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+2 -2
View File
@@ -40,11 +40,11 @@
"@mantine/core": "^8.3.10",
"@mantine/hooks": "^8.3.10",
"adm-zip": "0.5.16",
"next": "16.0.10",
"next": "16.1.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"superjson": "2.2.6",
"zod": "^4.1.13",
"zod": "^4.2.1",
"zod-form-data": "^3.0.1"
},
"devDependencies": {
@@ -45,6 +45,13 @@ export const importInitialOldmarrAsync = async (
await integrationInsertCollection.insertAllAsync(transaction);
});
},
handleSync(db) {
db.transaction((transaction) => {
boardInsertCollection.insertAll(transaction);
userInsertCollection.insertAll(transaction);
integrationInsertCollection.insertAll(transaction);
});
},
});
logger.info("Import successful", { duration: stopwatch.getElapsedInHumanWords() });
@@ -35,5 +35,8 @@ export const importSingleOldmarrConfigAsync = async (
async handleAsync(db) {
await boardInsertCollection.insertAllAsync(db);
},
handleSync(db) {
boardInsertCollection.insertAll(db);
},
});
};
+1 -1
View File
@@ -23,7 +23,7 @@
"prettier": "@homarr/prettier-config",
"dependencies": {
"@homarr/common": "workspace:^0.1.0",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -34,7 +34,7 @@
"octokit": "^5.0.5",
"superjson": "2.2.6",
"undici": "7.16.0",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
@@ -1,7 +1,8 @@
import dayjs from "dayjs";
import { z } from "zod/v4";
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
@@ -11,7 +12,9 @@ export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHand
async requestAsync(input: { domain: string; isBedrockServer: boolean }) {
const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`;
const response = await fetchWithTimeoutAsync(`https://api.mcsrvstat.us${path}`);
const response = await withTimeoutAsync(async (signal) =>
fetchWithTrustedCertificatesAsync(`https://api.mcsrvstat.us${path}`, { signal }),
);
return responseSchema.parse(await response.json());
},
cacheDuration: dayjs.duration(5, "minutes"),
+8 -4
View File
@@ -1,7 +1,8 @@
import dayjs from "dayjs";
import { z } from "zod/v4";
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
@@ -9,9 +10,12 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
queryKey: "fetchStockPriceResult",
widgetKind: "stockPrice",
async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) {
const response = await fetchWithTimeoutAsync(
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
);
const response = await withTimeoutAsync(async (signal) => {
return await fetchWithTrustedCertificatesAsync(
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
{ signal },
);
});
const data = dataSchema.parse(await response.json());
if ("error" in data) {
@@ -3,7 +3,7 @@ import { Octokit } from "octokit";
import { compareSemVer, isValidSemVer } from "semver-parser";
import { env } from "@homarr/common/env";
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { createChannelWithLatestAndEvents } from "@homarr/redis";
import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler";
@@ -23,7 +23,7 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({
const octokit = new Octokit({
request: {
fetch: fetchWithTimeoutAsync,
fetch: fetchWithTrustedCertificatesAsync,
},
});
const releases = await octokit.rest.repos.listReleases({
+8 -4
View File
@@ -1,7 +1,8 @@
import dayjs from "dayjs";
import { z } from "zod";
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
@@ -9,9 +10,12 @@ export const weatherRequestHandler = createCachedWidgetRequestHandler({
queryKey: "weatherAtLocation",
widgetKind: "weather",
async requestAsync(input: { latitude: number; longitude: number }) {
const res = await fetchWithTimeoutAsync(
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max&current_weather=true&timezone=auto`,
);
const res = await withTimeoutAsync(async (signal) => {
return await fetchWithTrustedCertificatesAsync(
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max&current_weather=true&timezone=auto`,
{ signal },
);
});
const json: unknown = await res.json();
const weather = await atLocationOutput.parseAsync(json);
return {
+1 -1
View File
@@ -27,7 +27,7 @@
"@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0",
"@mantine/dates": "^8.3.10",
"next": "16.0.10",
"next": "16.1.0",
"react": "19.2.3",
"react-dom": "19.2.3"
},
+1 -1
View File
@@ -38,7 +38,7 @@
"@mantine/spotlight": "^8.3.10",
"@tabler/icons-react": "^3.35.0",
"jotai": "^2.16.0",
"next": "16.0.10",
"next": "16.1.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"use-deep-compare-effect": "^1.8.1"
+2 -2
View File
@@ -32,8 +32,8 @@
"dayjs": "^1.11.19",
"deepmerge": "4.3.1",
"mantine-react-table": "2.0.0-beta.9",
"next": "16.0.10",
"next-intl": "4.6.0",
"next": "16.1.0",
"next-intl": "4.6.1",
"react": "19.2.3",
"react-dom": "19.2.3"
},
+13 -13
View File
@@ -312,7 +312,7 @@
},
"mobileBoard": {
"label": "Mobiles Board",
"description": "Nur Boards welche für die Gruppe zugänglich sind, können ausgewählt werden"
"description": "Nur Boards, welche für die Gruppe zugänglich sind, können ausgewählt werden"
}
},
"permission": {
@@ -355,7 +355,7 @@
},
"view-all": {
"label": "Alle Boards anzeigen",
"description": "Mitgliedern das Ansehen von allen Boards erlauben"
"description": "Mitgliedern das Ansehen von Boards erlauben"
},
"modify-all": {
"label": "Alle Boards ändern",
@@ -743,7 +743,7 @@
},
"authorization": {
"title": "Autorisierungsfehler",
"description": "Die Anfrage wurde nicht autorisiert. Bitte überprüfen Sie, ob die Anmeldedaten korrekt sind und Sie diese mit mit den entsprechenden Berechtigungen konfiguriert haben."
"description": "Die Anfrage wurde nicht autorisiert. Bitte überprüfen Sie, ob die Anmeldedaten korrekt sind und Sie diese mit den entsprechenden Berechtigungen konfiguriert haben."
},
"statusCode": {
"title": "Antwort-Fehler",
@@ -964,7 +964,7 @@
},
"personalAccessToken": {
"label": "Persönlicher Zugangs-Token",
"newLabel": "Neuer Persönliches Zugangs-Token"
"newLabel": "Neuer persönlicher Zugangs-Token"
},
"topic": {
"label": "Thema",
@@ -1237,7 +1237,7 @@
},
"openAllInNewTabs": {
"title": "Alle in tabs öffnen",
"text": "Einige Browser können aus Sicherheitsgründen das Öffnen von mehreren Tabs aufeinmal blockieren. Homarr konnte nicht alle Fenster öffnen, weil Ihr Browser diese Aktion blockiert hat. Bitte erlauben Sie \"Pop-up Fenster öffnen\" und versuchen Sie es erneut."
"text": "Einige Browser können aus Sicherheitsgründen das gleichzeitige Öffnen von mehreren Tabs blockieren. Homarr konnte nicht alle Fenster öffnen, weil Ihr Browser diese Aktion blockiert hat. Bitte erlauben Sie \"Pop-up Fenster öffnen\" und versuchen Sie es erneut."
}
}
},
@@ -1949,7 +1949,7 @@
"name": "Name",
"state": "Status",
"cpuUsage": "CPU-Auslastung",
"memoryUsage": "Speicherverbrauch"
"memoryUsage": "Speichernutzung"
}
},
"descendingDefaultSort": {
@@ -2639,7 +2639,7 @@
"notification": {
"success": {
"title": "Änderungen erfolgreich angewendet",
"message": "Das Board wurde erfolgreich dupliziert"
"message": "Das Board wurde erfolgreich gespeichert"
},
"error": {
"title": "Änderungen konnten nicht angewendet werden",
@@ -2676,7 +2676,7 @@
},
"onlyImportApps": {
"label": "Nur Apps importieren",
"description": "Fügt nur die Apps hinzu, das Board muss manuell neu erstellt werden"
"description": "Fügt nur die Apps hinzu. Das Board selbst muss manuell neu erstellt werden"
}
},
"name": {
@@ -2926,7 +2926,7 @@
"error": {
"noBoard": {
"title": "Willkommen bei Homarr",
"description": "Ein schlankens, modernes Dashboard, das alle Ihre Apps und Dienste bereit stellt.",
"description": "Ein schlankes, modernes Dashboard, das alle Ihre Apps und Dienste bereitstellt.",
"link": "Erstellen Sie Ihr erstes Board",
"notice": "Um diese Seite verschwinden zu lassen, erstellen Sie ein Board und setzen es als Home Board"
},
@@ -3366,7 +3366,7 @@
"label": "Index Manager"
},
"healthMonitoring": {
"label": "Überwachung des Gesundheit"
"label": "Überwachung des Zustandes"
},
"dnsHole": {
"label": "DNS Hole Daten"
@@ -3512,7 +3512,7 @@
},
"about": {
"version": "Version {version}",
"text": "Homarr ist ein gemeinschaftsorientiertes Open-Source Projekt, das von Freiwilligen betreut wird. Dank dieser Menschen ist Homarr seit 2021 ein wachsendes Projekt. Unser Team, sitzhaft mit ihren Migliedern in vielen verschiedenen Ländern, arbeitet in ihrer Freizeit ohne Vergütung.",
"text": "Homarr ist ein gemeinschaftsorientiertes Open-Source-Projekt, das von Freiwilligen betreut wird. Dank dieser Menschen ist Homarr seit 2021 ein wachsendes Projekt. Unser Team, dessen Mitglieder in vielen verschiedenen Ländern ansässig sind, arbeitet in seiner Freizeit und ohne Vergütung.",
"accordion": {
"contributors": {
"title": "Mitwirkende",
@@ -4395,7 +4395,7 @@
"configControl": "Konfiguration",
"searchEngineType": {
"generic": "Allgemein",
"fromIntegration": "Aus den Integration"
"fromIntegration": "Aus der Integration"
}
},
"delete": {
@@ -4498,7 +4498,7 @@
"notification": {
"success": {
"title": "Zertifikat entfernt",
"message": "Das Zertifikat wurde erfolgreich entfern"
"message": "Das Zertifikat wurde erfolgreich entfernt"
},
"error": {
"title": "Zertifikat nicht entfernt",
+1 -1
View File
@@ -34,7 +34,7 @@
"@mantine/hooks": "^8.3.10",
"@tabler/icons-react": "^3.35.0",
"mantine-react-table": "2.0.0-beta.9",
"next": "16.0.10",
"next": "16.1.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"svgson": "^5.3.1"
+1 -1
View File
@@ -24,7 +24,7 @@
"dependencies": {
"@homarr/definitions": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0",
"zod": "^4.1.13",
"zod": "^4.2.1",
"zod-form-data": "^3.0.1"
},
"devDependencies": {
+18 -18
View File
@@ -52,33 +52,33 @@
"@mantine/core": "^8.3.10",
"@mantine/hooks": "^8.3.10",
"@tabler/icons-react": "^3.35.0",
"@tiptap/extension-color": "3.13.0",
"@tiptap/extension-highlight": "3.13.0",
"@tiptap/extension-image": "3.13.0",
"@tiptap/extension-link": "^3.13.0",
"@tiptap/extension-placeholder": "^3.13.0",
"@tiptap/extension-table": "3.13.0",
"@tiptap/extension-table-cell": "3.13.0",
"@tiptap/extension-table-header": "3.13.0",
"@tiptap/extension-table-row": "3.13.0",
"@tiptap/extension-task-item": "3.13.0",
"@tiptap/extension-task-list": "3.13.0",
"@tiptap/extension-text-align": "3.13.0",
"@tiptap/extension-text-style": "3.13.0",
"@tiptap/extension-underline": "3.13.0",
"@tiptap/react": "^3.13.0",
"@tiptap/starter-kit": "^3.13.0",
"@tiptap/extension-color": "3.14.0",
"@tiptap/extension-highlight": "3.14.0",
"@tiptap/extension-image": "3.14.0",
"@tiptap/extension-link": "^3.14.0",
"@tiptap/extension-placeholder": "^3.14.0",
"@tiptap/extension-table": "3.14.0",
"@tiptap/extension-table-cell": "3.14.0",
"@tiptap/extension-table-header": "3.14.0",
"@tiptap/extension-table-row": "3.14.0",
"@tiptap/extension-task-item": "3.14.0",
"@tiptap/extension-task-list": "3.14.0",
"@tiptap/extension-text-align": "3.14.0",
"@tiptap/extension-text-style": "3.14.0",
"@tiptap/extension-underline": "3.14.0",
"@tiptap/react": "^3.14.0",
"@tiptap/starter-kit": "^3.14.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.19",
"mantine-form-zod-resolver": "^1.3.0",
"mantine-react-table": "2.0.0-beta.9",
"next": "16.0.10",
"next": "16.1.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-markdown": "^10.1.0",
"recharts": "^2.15.4",
"video.js": "^8.23.4",
"zod": "^4.1.13"
"zod": "^4.2.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",
+795 -978
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1 +1 @@
[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"Skick","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12619811/medium/0331f1de413a645b4516bc4e037f31bb.jpg"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/249b02e3368752cb175446082015737c_default.png"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowLine truncated
[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/249b02e3368752cb175446082015737c_default.png"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"httpsLine truncated
+3 -3
View File
@@ -17,14 +17,14 @@
},
"prettier": "@homarr/prettier-config",
"dependencies": {
"@next/eslint-plugin-next": "16.0.10",
"@next/eslint-plugin-next": "16.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-config-turbo": "^2.6.3",
"eslint-config-turbo": "^2.7.1",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^6.1.1",
"typescript-eslint": "^8.49.0"
"typescript-eslint": "^8.50.0"
},
"devDependencies": {
"@homarr/prettier-config": "workspace:^0.1.0",