Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5463402f5c | ||
|
|
5baa183718 | ||
|
|
93bf9ab84b | ||
|
|
4ff98e3803 | ||
|
|
d8db0169a1 | ||
|
|
96c2c0d604 | ||
|
|
5a57115ca0 | ||
|
|
707a4dad83 | ||
|
|
5dd7400684 | ||
|
|
6b774f47d0 | ||
|
|
adbe707c73 | ||
|
|
75c545344a | ||
|
|
7c399f4073 | ||
|
|
5d659a3ebb | ||
|
|
99f2842a5a | ||
|
|
f34bdce644 | ||
|
|
8891ac4239 | ||
|
|
602c37574e | ||
|
|
f4d782abef | ||
|
|
a20b479a42 | ||
|
|
68dc6844fd | ||
|
|
88baad8143 | ||
|
|
ef12b2b5c2 | ||
|
|
551a7ea469 | ||
|
|
fbb5298cb0 | ||
|
|
2dc8feae7e | ||
|
|
6e39524f9d | ||
|
|
b699f28177 | ||
|
|
de39c7e85b | ||
|
|
e9c33cdd5a | ||
|
|
648f7e2c77 | ||
|
|
41f40149b3 | ||
|
|
689523a194 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
}),
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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>;
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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¤t_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¤t_weather=true&timezone=auto`,
|
||||
{ signal },
|
||||
);
|
||||
});
|
||||
const json: unknown = await res.json();
|
||||
const weather = await atLocationOutput.parseAsync(json);
|
||||
return {
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+795
-978
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user