Compare commits

...
Author SHA1 Message Date
Thomas Camlong eef06dd5a3 Revert "chore(release): 1.66.0 [skip ci]"
This reverts commit 84e044f427.
2026-06-18 13:17:30 +02:00
Releases Homarr 84e044f427 chore(release): 1.66.0 [skip ci] 2026-06-18 11:14:43 +00:00
homarr-releases[bot] 609ab9976a chore(release): automatic release v1.66.0 2026-06-18 11:13:29 +00:00
Thomas Camlong 28f570aa79 Revert "chore(release): 1.66.0 [skip ci]"
This reverts commit 5b4f03da35.
2026-06-18 13:07:44 +02:00
Releases Homarr 5b4f03da35 chore(release): 1.66.0 [skip ci] 2026-06-18 10:55:09 +00:00
Matt Van Horn 6960ac15e9 fix(integrations): make Emby Studios field optional (#5997) 2026-06-18 12:45:59 +02:00
Thomas f1ee76c99c fix(db): crash on MySQL — replace onConflictDoNothing with dialect-neutral pre-filter (#5995) 2026-06-18 12:45:02 +02:00
homarr-renovate[bot] 9a9ee5a9d7 chore(deps): update tiptap monorepo to v3.26.1 (#5976)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 09:24:34 +00:00
homarr-renovate[bot] 7debe34e24 chore(deps): update dependency jotai to ^2.20.1 (#5974)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-18 04:14:37 +00:00
homarr-renovate[bot] 0d67c5a451 chore(deps): update scalar monorepo to ^0.9.46 (#5975)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-17 23:36:17 +00:00
homarr-renovate[bot] 2db0fe15fb chore(deps): update testcontainers-node monorepo to ^12.0.2 (#5987)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-06-17 21:20:11 +00:00
homarr-releases[bot] 4b85afbb87 chore: update bug report template (#5990)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-06-17 20:10:20 +00:00
homarr-crowdin[bot]andThomas 536f82af04 chore(lang): updated translations from crowdin
* chore(lang): update translations from crowdin

* chore(lang): update translations from crowdin

---------

Co-authored-by: Crowdin Homarr <190541745+homarr-crowdin[bot]@users.noreply.github.com>
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-06-17 20:00:36 +00:00
Thomas 445e191490 fix: persist Beszel server selection and improve picker visibility (#5985) 2026-06-17 17:06:23 +02:00
44 changed files with 1026 additions and 767 deletions

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ body:
options: options:
# The below comment is used to insert a new version with on-release.yml # The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION# #NEXT_VERSION#
- 1.66.0
- 1.65.0 - 1.65.0
- 1.64.0 - 1.64.0
- 1.62.0 - 1.62.0
+37 -1
View File
@@ -1,7 +1,14 @@
import { TRPCError } from "@trpc/server";
import SuperJSON from "superjson";
import { z } from "zod/v4";
import { eq } from "@homarr/db";
import { getServerSettingsAsync } from "@homarr/db/queries"; import { getServerSettingsAsync } from "@homarr/db/queries";
import { boards, items } from "@homarr/db/schema";
import type { WidgetOptionsSettings } from "../../../../widgets/src"; import type { WidgetOptionsSettings } from "../../../../widgets/src";
import { createTRPCRouter, publicProcedure } from "../../trpc"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../../trpc";
import { throwIfActionForbiddenAsync } from "../board/board-access";
export const optionsRouter = createTRPCRouter({ export const optionsRouter = createTRPCRouter({
getWidgetOptionSettings: publicProcedure.query(async ({ ctx }): Promise<WidgetOptionsSettings> => { getWidgetOptionSettings: publicProcedure.query(async ({ ctx }): Promise<WidgetOptionsSettings> => {
@@ -16,4 +23,33 @@ export const optionsRouter = createTRPCRouter({
}, },
}; };
}), }),
saveItemOptions: protectedProcedure
.input(
z.object({
itemId: z.string(),
boardId: z.string(),
newOptions: z.record(z.string(), z.unknown()),
}),
)
.mutation(async ({ ctx, input }) => {
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.boardId), "modify");
const item = await ctx.db.query.items.findFirst({
where: eq(items.id, input.itemId),
});
if (item?.boardId !== input.boardId) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Specified item was not found",
});
}
const options = SuperJSON.parse<Record<string, unknown>>(item.options);
Object.assign(options, input.newOptions);
await ctx.db
.update(items)
.set({ options: SuperJSON.stringify(options) })
.where(eq(items.id, input.itemId));
}),
}); });
+10 -14
View File
@@ -647,9 +647,13 @@ const seedDemoUserAsync = async (db: Database) => {
const seedDefaultCustomWidgetsAsync = async (db: Database) => { const seedDefaultCustomWidgetsAsync = async (db: Database) => {
const seedIds = CUSTOM_WIDGET_SEEDS.map((s) => s.id); const seedIds = CUSTOM_WIDGET_SEEDS.map((s) => s.id);
const beforeCount = await db.$count(customWidgetDefinitions, inArray(customWidgetDefinitions.id, seedIds)); const existing = await db.query.customWidgetDefinitions.findMany({
columns: { id: true },
where: inArray(customWidgetDefinitions.id, seedIds),
});
const existingIds = new Set(existing.map((row) => row.id));
const seedValues = CUSTOM_WIDGET_SEEDS.map((seed) => { const seedValues = CUSTOM_WIDGET_SEEDS.filter((seed) => !existingIds.has(seed.id)).map((seed) => {
const parsed = customWidgetImportSchema.parse(seed.data); const parsed = customWidgetImportSchema.parse(seed.data);
return { return {
id: seed.id, id: seed.id,
@@ -668,22 +672,14 @@ const seedDefaultCustomWidgetsAsync = async (db: Database) => {
}; };
}); });
if (seedValues.length === 0) return; if (seedValues.length === 0) {
await db
.insert(customWidgetDefinitions)
.values(seedValues)
.onConflictDoNothing({ target: customWidgetDefinitions.id });
const afterCount = await db.$count(customWidgetDefinitions, inArray(customWidgetDefinitions.id, seedIds));
const createdCount = afterCount - beforeCount;
if (createdCount === 0) {
console.log("Skipping seeding of default custom widgets as they already exist"); console.log("Skipping seeding of default custom widgets as they already exist");
return; return;
} }
console.log(`Created ${createdCount} default custom widgets through seeding process`); await db.insert(customWidgetDefinitions).values(seedValues);
console.log(`Created ${seedValues.length} default custom widgets through seeding process`);
}; };
const seedBoardWidgetsAsync = async (db: Database) => { const seedBoardWidgetsAsync = async (db: Database) => {
@@ -98,7 +98,6 @@ export type HomarrDocumentationPath =
| "/docs/tags/puid" | "/docs/tags/puid"
| "/docs/tags/redis" | "/docs/tags/redis"
| "/docs/tags/responsive" | "/docs/tags/responsive"
| "/docs/tags/restore"
| "/docs/tags/roles" | "/docs/tags/roles"
| "/docs/tags/search-engines" | "/docs/tags/search-engines"
| "/docs/tags/search" | "/docs/tags/search"
@@ -180,6 +179,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/github" | "/docs/integrations/github"
| "/docs/integrations/gitlab" | "/docs/integrations/gitlab"
| "/docs/integrations/glances" | "/docs/integrations/glances"
| "/docs/integrations/gotify"
| "/docs/integrations/home-assistant" | "/docs/integrations/home-assistant"
| "/docs/integrations/ical" | "/docs/integrations/ical"
| "/docs/integrations/immich" | "/docs/integrations/immich"
@@ -233,7 +233,6 @@ export type HomarrDocumentationPath =
| "/docs/management/search-engines" | "/docs/management/search-engines"
| "/docs/management/settings" | "/docs/management/settings"
| "/docs/management/tasks" | "/docs/management/tasks"
| "/docs/management/tools/backup"
| "/docs/management/users" | "/docs/management/users"
| "/docs/widgets/anchor-note" | "/docs/widgets/anchor-note"
| "/docs/widgets/app" | "/docs/widgets/app"
@@ -38,7 +38,7 @@ const itemSchema = z.object({
ServerId: z.string(), ServerId: z.string(),
Name: z.string(), Name: z.string(),
Taglines: z.array(z.string()), Taglines: z.array(z.string()),
Studios: z.array(z.object({ Name: z.string() })), Studios: z.array(z.object({ Name: z.string() })).optional(),
Overview: z.string().optional(), Overview: z.string().optional(),
PremiereDate: z PremiereDate: z
.string() .string()
@@ -172,7 +172,7 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
poster: super.externalUrl(`/Items/${item.Id}/Images/Primary?maxHeight=492&maxWidth=328&quality=90`).toString(), poster: super.externalUrl(`/Items/${item.Id}/Images/Primary?maxHeight=492&maxWidth=328&quality=90`).toString(),
backdrop: super.externalUrl(`/Items/${item.Id}/Images/Backdrop/0?maxWidth=960&quality=70`).toString(), backdrop: super.externalUrl(`/Items/${item.Id}/Images/Backdrop/0?maxWidth=960&quality=70`).toString(),
}, },
producer: item.Studios.at(0)?.Name, producer: item.Studios?.at(0)?.Name,
rating: item.CommunityRating?.toFixed(1), rating: item.CommunityRating?.toFixed(1),
tags: item.Genres, tags: item.Genres,
href: super.externalUrl(`/web/index.html#!/item?id=${item.Id}&serverId=${item.ServerId}`).toString(), href: super.externalUrl(`/web/index.html#!/item?id=${item.Id}&serverId=${item.ServerId}`).toString(),
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole 摘要", "name": "DNS Hole 摘要",
"description": "显示 DNS Hole 的摘要", "description": "显示 DNS Hole 的摘要",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "crwdns5635:0crwdne5635:0",
"description": "crwdns5637:0crwdne5637:0",
"option": {
"showBroadcastMessage": {
"label": "crwdns5639:0crwdne5639:0"
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "crwdns768:0crwdne768:0", "name": "crwdns768:0crwdne768:0",
"description": "crwdns770:0crwdne770:0", "description": "crwdns770:0crwdne770:0",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "Zobrazí shrnutí vaší DNS Hole", "description": "Zobrazí shrnutí vaší DNS Hole",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole Oversigt", "name": "DNS Hole Oversigt",
"description": "Viser oversigten over dit DNS-hul", "description": "Viser oversigten over dit DNS-hul",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole Zusammenfassung", "name": "DNS Hole Zusammenfassung",
"description": "Zeigt die Zusammenfassung deines DNS Holes an", "description": "Zeigt die Zusammenfassung deines DNS Holes an",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole Zusammenfassung", "name": "DNS Hole Zusammenfassung",
"description": "Zeigt die Zusammenfassung deines DNS Holes an", "description": "Zeigt die Zusammenfassung deines DNS Holes an",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+48 -39
View File
@@ -8,9 +8,9 @@
"action": { "action": {
"scratch": "Start from scratch", "scratch": "Start from scratch",
"importOldmarr": "Import from a Homarr version before v1.0", "importOldmarr": "Import from a Homarr version before v1.0",
"restoreSqlite": "", "restoreSqlite": "Restore from SQLite backup",
"restoreSqliteDescription": "", "restoreSqliteDescription": "Upload a full SQLite database backup from a previous Homarr instance. This will restore all boards, users, apps, integrations, and settings.",
"backToOptions": "" "backToOptions": "← Back to setup options"
} }
}, },
"import": { "import": {
@@ -115,24 +115,24 @@
} }
}, },
"urlTemplate": { "urlTemplate": {
"label": "", "label": "Base URL",
"placeholder": "", "placeholder": "homelab.local or 192.168.1.1",
"hint": "", "hint": "Enter your base domain or IP to auto-fill integration URLs",
"warning": "", "warning": "Setting your base URL now will auto-fill all integration URLs, saving you time during setup",
"preview": "", "preview": "e.g. {example}",
"mode": { "mode": {
"subdomain": "", "subdomain": "Subdomain",
"hostPort": "" "hostPort": "Host:Port"
} }
}, },
"docker": { "docker": {
"detected": "", "detected": "Docker detected {count} services",
"detectedBreakdown": "", "detectedBreakdown": "{integrations} integrations · {apps} apps",
"summary": "", "summary": "Homarr detected {apps} apps, {integrations} of which have integrations built-in",
"appBadge": "", "appBadge": "App",
"appSectionLabel": "", "appSectionLabel": "Apps to add to dashboard ({count} selected)",
"scanning": "", "scanning": "Scanning Docker...",
"scanningDescription": "" "scanningDescription": "Looking for running containers on your system"
} }
}, },
"finish": { "finish": {
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole Summary", "name": "DNS Hole Summary",
"description": "Displays the summary of your DNS Hole", "description": "Displays the summary of your DNS Hole",
@@ -5704,10 +5713,10 @@
"option": { "option": {
"google": { "google": {
"name": "", "name": "",
"description": "" "description": "Search the web with Google"
}, },
"bing": { "bing": {
"name": "", "name": "Bing",
"description": "Search the web with Bing" "description": "Search the web with Bing"
}, },
"duckduckgo": { "duckduckgo": {
@@ -6089,46 +6098,46 @@
"description": "These are all the apps you've added — links to your self-hosted services. Each app has an icon, optional health check, and a status badge on the dashboard." "description": "These are all the apps you've added — links to your self-hosted services. Each app has an icon, optional health check, and a status badge on the dashboard."
}, },
"appsCreate": { "appsCreate": {
"title": "", "title": "Add an App",
"description": "" "description": "Register a new service here. Give it a name, URL, and icon — it will appear as a tile on your boards with live status monitoring."
}, },
"integrationsList": { "integrationsList": {
"title": "", "title": "Your Integrations",
"description": "" "description": "These are the services connected to Homarr — Sonarr, Radarr, Plex, Jellyfin, and 50+ more. All credentials are encrypted at rest."
}, },
"integrationsCreate": { "integrationsCreate": {
"title": "", "title": "Add an Integration",
"description": "" "description": "Connect a new service here. Pick the type, enter the URL and API key — Homarr validates the connection before saving."
}, },
"usersList": { "usersList": {
"title": "", "title": "Your Users",
"description": "" "description": "See everyone who has access. Organise users into groups and control who can view or edit each board."
}, },
"usersCreate": { "usersCreate": {
"title": "", "title": "Create a User",
"description": "" "description": "Add a new user account here. You can also use invite links for easy onboarding. That's the tour — you're all set!"
} }
}, },
"board": { "board": {
"welcome": { "welcome": {
"title": "", "title": "This is your board",
"description": "" "description": "A drag-and-drop dashboard where you arrange apps and widgets. Press Ctrl+K anytime to search for apps, boards, and actions."
}, },
"editMode": { "editMode": {
"title": "", "title": "Edit Mode",
"description": "" "description": "Click here to enter edit mode. You can then add widgets, apps, and sections — drag to reposition, resize with the corner handle. Click again to save your changes."
}, },
"settings": { "settings": {
"title": "", "title": "Board Settings",
"description": "" "description": "Change this board's name, columns, background, colours, and who can access it. Each board can be public or private."
}, },
"switcher": { "switcher": {
"title": "", "title": "Switch Boards",
"description": "" "description": "Jump between boards. Create separate dashboards for different purposes — media, networking, per-user, or anything you like."
}, },
"userMenu": { "userMenu": {
"title": "", "title": "Your Profile",
"description": "" "description": "Change your colour scheme, language, home board, and preferences. You can also jump to the management area from here."
} }
} }
} }
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "Resumen del agujero DNS", "name": "Resumen del agujero DNS",
"description": "Muestra el resumen de tu agujero DNS", "description": "Muestra el resumen de tu agujero DNS",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "Résumé du puit DNS", "name": "Résumé du puit DNS",
"description": "Affiche le résumé de votre puit DNS", "description": "Affiche le résumé de votre puit DNS",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "סיכום חור אבטחה DNS", "name": "סיכום חור אבטחה DNS",
"description": "מציג את הסיכום חור אבטחת DNS שלך", "description": "מציג את הסיכום חור אבטחת DNS שלך",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Blokkoló Összefoglaló", "name": "DNS Blokkoló Összefoglaló",
"description": "Megjeleníti a DNS Blokkoló összefoglalóját", "description": "Megjeleníti a DNS Blokkoló összefoglalóját",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "Riepilogo del filtraggio DNS", "name": "Riepilogo del filtraggio DNS",
"description": "Mostra il riepilogo del tuo filtro DNS", "description": "Mostra il riepilogo del tuo filtro DNS",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole 概要", "name": "DNS Hole 概要",
"description": "DNS Hole の概要を表示します", "description": "DNS Hole の概要を表示します",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS-hole overzicht", "name": "DNS-hole overzicht",
"description": "Toont het overzicht van je DNS-hole", "description": "Toont het overzicht van je DNS-hole",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole Sammendrag", "name": "DNS Hole Sammendrag",
"description": "Viser sammendrag av DNS Hole", "description": "Viser sammendrag av DNS Hole",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "Podsumowanie Hole DNS", "name": "Podsumowanie Hole DNS",
"description": "Wyświetla podsumowanie Twojego hosta DNS", "description": "Wyświetla podsumowanie Twojego hosta DNS",
+201 -192
View File
@@ -8,9 +8,9 @@
"action": { "action": {
"scratch": "Começar do zero", "scratch": "Começar do zero",
"importOldmarr": "Importe de uma instância do Homarr anterior a 1.0", "importOldmarr": "Importe de uma instância do Homarr anterior a 1.0",
"restoreSqlite": "", "restoreSqlite": "Restaurar do backup SQLite",
"restoreSqliteDescription": "", "restoreSqliteDescription": "Fazer o upload de um backup completo do banco de dados SQLite de uma instância anterior da Homarr. Isso restaurará todos os painéis, usuários, apps, integrações e configurações.",
"backToOptions": "" "backToOptions": "← Retorna às opções de instalação"
} }
}, },
"import": { "import": {
@@ -94,45 +94,45 @@
"subtitle": "Definir as configurações do servidor." "subtitle": "Definir as configurações do servidor."
}, },
"integrations": { "integrations": {
"title": "", "title": "Integrações",
"subtitle": "", "subtitle": "Selecione os serviços que você usa.",
"description": "", "description": "Escolha as integrações que deseja conectar. Vamos configurar seu painel com os widgets correspondentes automaticamente.",
"action": { "action": {
"continue": "", "continue": "Continuar",
"skip": "" "skip": "Pular por enquanto"
}, },
"configure": { "configure": {
"progress": "", "progress": "{current} de {total}",
"skip": "", "skip": "Pular esta integração",
"finishing": "", "finishing": "Configurando seu painel...",
"error": { "error": {
"title": "", "title": "Falha na configuração",
"message": "" "message": "Algo deu errado ao configurar seu painel. Por favor, tente novamente."
}, },
"helpers": { "helpers": {
"tooltip": "", "tooltip": "Auxiliares de URL",
"description": "" "description": "Esses auxiliares tentam adivinhar a URL dos seus serviços. Se não funcionar, digite o endereço manualmente."
} }
}, },
"urlTemplate": { "urlTemplate": {
"label": "", "label": "URL base",
"placeholder": "", "placeholder": "homelab.local ou 192.168.1.1",
"hint": "", "hint": "Digite seu domínio base ou IP para preencher automaticamente as URLs das integrações",
"warning": "", "warning": "Definir sua URL base agora preencherá automaticamente todas as URLs das integrações, economizando tempo durante a configuração",
"preview": "", "preview": "ex.: {example}",
"mode": { "mode": {
"subdomain": "", "subdomain": "Subdomínio",
"hostPort": "" "hostPort": "Host:Porta"
} }
}, },
"docker": { "docker": {
"detected": "", "detected": "Docker detectou {count} serviços",
"detectedBreakdown": "", "detectedBreakdown": "{integrations} integrações · {apps} apps",
"summary": "", "summary": "O Homarr detectou {apps} apps, dos quais {integrations} possuem integrações nativas",
"appBadge": "", "appBadge": "App",
"appSectionLabel": "", "appSectionLabel": "Apps para adicionar ao painel ({count} selecionados)",
"scanning": "", "scanning": "Verificando Docker...",
"scanningDescription": "" "scanningDescription": "Procurando contêineres em execução no seu sistema"
} }
}, },
"finish": { "finish": {
@@ -177,7 +177,7 @@
}, },
"password": { "password": {
"label": "Senha", "label": "Senha",
"suggestionsTitle": "", "suggestionsTitle": "Sugestões de senha",
"suggestion": { "suggestion": {
"length": "Inclua pelo menos 8 caracteres", "length": "Inclua pelo menos 8 caracteres",
"lowercase": "Inclui letras minúsculas", "lowercase": "Inclui letras minúsculas",
@@ -687,94 +687,94 @@
"customWidget": { "customWidget": {
"page": { "page": {
"list": { "list": {
"title": "", "title": "Widgets Personalizados",
"noResults": "", "noResults": "Nenhum widget personalizado ainda. Crie o seu primeiro!",
"loading": "" "loading": "Carregando..."
}, },
"create": { "create": {
"title": "", "title": "Criar widget personalizado",
"subtitle": "" "subtitle": "Defina uma URL de API, autenticação e como exibir os dados de resposta."
}, },
"edit": { "edit": {
"title": "" "title": "Editar widget personalizado"
} }
}, },
"action": { "action": {
"create": "", "create": "Criar",
"import": "", "import": "Importar",
"save": "", "save": "Salvar",
"delete": "", "delete": "Excluir widget personalizado",
"deleteConfirm": "", "deleteConfirm": "Tem certeza de que deseja excluir \"{name}\"? Esta ação não pode ser desfeita.",
"export": "", "export": "Exportar",
"addMapping": "", "addMapping": "Adicionar Mapeamento",
"addColumn": "", "addColumn": "Adicionar Coluna",
"edit": "", "edit": "Editar",
"removeMapping": "", "removeMapping": "Remover Mapeamento",
"removeColumn": "", "removeColumn": "Remover coluna",
"addItem": "", "addItem": "Adicionar Item",
"removeItem": "", "removeItem": "Remover item",
"addBar": "", "addBar": "Adicionar barra",
"duplicate": "", "duplicate": "Duplicar",
"copyAiPrompt": "", "copyAiPrompt": "",
"enable": "", "enable": "Habilitado",
"disable": "", "disable": "Desabilitado",
"menu": "" "menu": "Ações"
}, },
"field": { "field": {
"name": "", "name": "Nome",
"description": "", "description": "Descrição",
"iconUrl": "", "iconUrl": "URL do ícone",
"url": "", "url": "URL",
"authType": "", "authType": "Tipo de Autenticação",
"headerName": "", "headerName": "",
"method": "", "method": "Método",
"requestBody": "", "requestBody": "Corpo da requisição",
"displayType": "", "displayType": "",
"jsonPath": "", "jsonPath": "Caminho JSON",
"jsonPathHint": "", "jsonPathHint": "Usar sintaxe JSONPath para extrair valores, por exemplo, $.data.temperature ou $.items[0].name",
"label": "", "label": "",
"unit": "", "unit": "",
"mappings": "", "mappings": "Mapeamentos",
"tablePath": "", "tablePath": "Caminho Array JSON",
"tablePathHint": "", "tablePathHint": "Caminho para o array na resposta, por exemplo, $.data.items",
"columns": "", "columns": "Colunas",
"header": "", "header": "Header (cabeçalho)",
"items": "", "items": "Itens",
"gridColumns": "", "gridColumns": "Colunas da grade",
"cardStyle": "", "cardStyle": "Estilo de cartão",
"bars": "", "bars": "Barras",
"valuePath": "", "valuePath": "Caminho do valor JSON",
"maxPath": "", "maxPath": "Caminho do Max Valor JSON",
"showPercentage": "", "showPercentage": "Mostrar porcentagem",
"barSize": "", "barSize": "Tamanho da barra",
"goodValues": "", "goodValues": "Valores bons (separados por vírgula)",
"dotSize": "", "dotSize": "",
"valueSize": "", "valueSize": "Tamanho do valor",
"labelPosition": "", "labelPosition": "",
"kvLayout": "", "kvLayout": "Layout",
"striped": "", "striped": "Linhas Listradas",
"compact": "", "compact": "Modo Compacto",
"maxHeight": "", "maxHeight": "Altura máxima (px)",
"color": "", "color": "Cor",
"buttonLabel": "", "buttonLabel": "",
"buttonColor": "", "buttonColor": "Cor do botão",
"confirmText": "", "confirmText": "Texto de confirmação",
"confirmTextHint": "", "confirmTextHint": "Se definido, um diálogo de confirmação irá aparecer antes de executar a solicitação",
"successMessage": "", "successMessage": "Mensagem de Sucesso",
"successMessageHint": "", "successMessageHint": "Mensagem mostrada após execução com sucesso",
"enabled": "", "enabled": "Habilitado",
"template": { "template": {
"label": "", "label": "Template JSX",
"description": "" "description": ""
} }
}, },
"placeholder": { "placeholder": {
"exampleLabel": "", "exampleLabel": "Temperatura da CPU",
"exampleLabelShort": "", "exampleLabelShort": "",
"exampleHeader": "", "exampleHeader": "Nome",
"exampleUnit": "", "exampleUnit": "",
"iconUrl": "", "iconUrl": "https://...",
"url": "", "url": "https://api.exemplo.com/br/api/v1/status",
"headerName": "", "headerName": "",
"jsonPath": "", "jsonPath": "",
"tablePath": "", "tablePath": "",
@@ -790,35 +790,35 @@
"subtle": "" "subtle": ""
}, },
"layoutOption": { "layoutOption": {
"list": "", "list": "Lista",
"grid": "" "grid": "Grade"
}, },
"sizeOption": { "sizeOption": {
"sm": "", "sm": "Pequeno",
"md": "", "md": "Médio",
"lg": "", "lg": "Grande",
"xl": "" "xl": "Extra grande"
}, },
"labelPositionOption": { "labelPositionOption": {
"above": "", "above": "Acima",
"below": "" "below": "Abaixo"
}, },
"fieldset": { "fieldset": {
"general": "", "general": "Geral",
"connection": "", "connection": "Conexão com API",
"display": "" "display": "Configurações de tela"
}, },
"displayTypeDescription": { "displayTypeDescription": {
"singleValue": "", "singleValue": "",
"keyValue": "", "keyValue": "",
"table": "", "table": "",
"statGrid": "", "statGrid": "Estatísticas coloridas em uma grade — como Pi-Hole ou Widgets Rápidos",
"progressBars": "", "progressBars": "Barras de progresso mostrando uso ou conclusão — para disco, memória, quotas",
"statusIndicator": "", "statusIndicator": "Pontos de status coloridos mostrando estados online/offline ou de saúde",
"countGrid": "", "countGrid": "",
"raw": "", "raw": "Saída JSON impressa para depuração",
"actionButton": "", "actionButton": "Um botão que aciona uma solicitação quando clicado",
"customJsx": "" "customJsx": "Layout JSX personalizado usando componentes Mantine na lista branca com ligação completa de dados de API"
}, },
"method": { "method": {
"GET": "", "GET": "",
@@ -848,42 +848,42 @@
}, },
"secret": { "secret": {
"apiKey": "", "apiKey": "",
"username": "", "username": "Usuário",
"password": "", "password": "Senha",
"savedPlaceholder": "" "savedPlaceholder": ""
}, },
"preview": { "preview": {
"title": "", "title": "Pré-visualizar",
"test": "", "test": "",
"hint": "", "hint": "Clique em Testar para visualizar a resposta da API com sua configuração atual.",
"rawResponse": "" "rawResponse": "Resposta JSON Bruta"
}, },
"notification": { "notification": {
"created": "", "created": "Widget \"{name}\" criado com sucesso.",
"updated": "", "updated": "Widget \"{name}atualizado com sucesso.",
"deleted": "", "deleted": "Widget \"{name}excluído.",
"imported": "", "imported": "Widget importado com sucesso.",
"aiPromptCopied": "", "aiPromptCopied": "",
"aiPromptDescription": "", "aiPromptDescription": "Copie um prompt pré-construído para colar qualquer chatbot de AI (ChatGPT, Claude, Gemini...). Ele inclui o esquema completo de widget e a sua resposta de API para que a AI possa gerar uma configuração JSON pronta para importar para você.",
"aiPromptCopy": "", "aiPromptCopy": "Copiar para área de transferência",
"aiPromptNoResponse": "", "aiPromptNoResponse": "Dica: Execute um teste primeiro para incluir a sua API de resposta no prompt para melhores resultados.",
"aiPromptCopyError": "", "aiPromptCopyError": "Falha ao copiar prompt para área de transferência. O seu navegador pode não suportar este recurso.",
"createError": "", "createError": "Falha ao criar widget. Verifique se o endpoint API está acessível.",
"updateError": "", "updateError": "Falha ao atualizar o widget. Suas alterações não foram salvas.",
"importError": "", "importError": "Falha ao importar o widget. Certifique-se de que o arquivo seja um widget personalizado válido exportado em JSON.",
"exportError": "", "exportError": "Falha ao exportar o widget.",
"duplicated": "", "duplicated": "Widget \"{name}duplicado.",
"duplicateError": "", "duplicateError": "Falha ao duplicar o widget.",
"toggleError": "", "toggleError": "Falha ao alternar o status do widget.",
"deleteError": "", "deleteError": "Falha ao excluir o widget.",
"previewError": "", "previewError": "Falha ao buscar a pré-visualização. Verifique a URL e tente novamente.",
"executed": "", "executed": "Ação executada com sucesso.",
"executeError": "" "executeError": "A ação falhou: {error}"
}, },
"table": { "table": {
"name": "", "name": "Nome",
"url": "", "url": "URL",
"display": "" "display": "Exibição"
}, },
"widget": { "widget": {
"disabled": "" "disabled": ""
@@ -983,46 +983,46 @@
"placeholder": "URL de aplicativo personalizada" "placeholder": "URL de aplicativo personalizada"
}, },
"apiKeySettings": { "apiKeySettings": {
"label": "" "label": "Obter chave de API"
} }
}, },
"action": { "action": {
"create": "Nova integração" "create": "Nova integração"
}, },
"grid": { "grid": {
"widgetCount": "", "widgetCount": "{count} widgets",
"noWidgets": "", "noWidgets": "Sem widgets",
"detected": "" "detected": "Detectado via Docker"
}, },
"category": { "category": {
"dnsHole": "", "dnsHole": "DNS",
"mediaService": "", "mediaService": "Mídia",
"calendar": "", "calendar": "Calendário",
"mediaSearch": "", "mediaSearch": "Busca",
"mediaRelease": "", "mediaRelease": "Lançamentos",
"mediaRequest": "", "mediaRequest": "Solicitações",
"downloadClient": "", "downloadClient": "Downloads",
"usenet": "", "usenet": "Usenet",
"torrent": "", "torrent": "Torrent",
"miscellaneous": "", "miscellaneous": "Diversos",
"smartHomeServer": "", "smartHomeServer": "Casa Inteligente",
"indexerManager": "", "indexerManager": "Indexador",
"healthMonitoring": "", "healthMonitoring": "",
"search": "", "search": "Busca",
"mediaTranscoding": "", "mediaTranscoding": "Transcodificação",
"networkController": "", "networkController": "Rede",
"releasesProvider": "", "releasesProvider": "Lançamentos",
"notifications": "", "notifications": "Notificações",
"firewall": "Firewall", "firewall": "Firewall",
"timetable": "", "timetable": "",
"photoService": "", "photoService": "Fotos",
"documents": "", "documents": "Documentos",
"notes": "", "notes": "Anotações",
"mediaMonitoring": "", "mediaMonitoring": "Monitoramento",
"speedtest": "", "speedtest": "Teste de Velocidade",
"analytics": "", "analytics": "Análises",
"mediaLibrary": "", "mediaLibrary": "Biblioteca de mídia",
"uptimeMonitoring": "" "uptimeMonitoring": "Monitoramento do Uptime"
}, },
"testConnection": { "testConnection": {
"action": { "action": {
@@ -1297,7 +1297,7 @@
"newLabel": "Nova chave privada" "newLabel": "Nova chave privada"
}, },
"slug": { "slug": {
"label": "", "label": "Slug página de status",
"newLabel": "" "newLabel": ""
} }
} }
@@ -1329,7 +1329,7 @@
}, },
"paperlessNgx": { "paperlessNgx": {
"name": "", "name": "",
"description": "" "description": "Sistema de gerenciamento de documentos para arquivamento e organização de documentos digitalizados"
} }
}, },
"media": { "media": {
@@ -1384,7 +1384,7 @@
"add": "Adicionar", "add": "Adicionar",
"apply": "Aplicar", "apply": "Aplicar",
"backToOverview": "Voltar à visão geral", "backToOverview": "Voltar à visão geral",
"skip": "", "skip": "Pular",
"change": "Alterar", "change": "Alterar",
"create": "Criar", "create": "Criar",
"createAnother": "Criar e recomeçar", "createAnother": "Criar e recomeçar",
@@ -1621,11 +1621,11 @@
"edit": { "edit": {
"title": "Editar item", "title": "Editar item",
"tab": { "tab": {
"widget": "", "widget": "Editar widget",
"app": "" "app": "Editar aplicativo"
}, },
"app": { "app": {
"propagationNotice": "" "propagationNotice": "As alterações deste aplicativo serão aplicadas em todos os lugares em que for usado em todos os painéis."
}, },
"advancedOptions": { "advancedOptions": {
"label": "Opções avançadas", "label": "Opções avançadas",
@@ -1731,7 +1731,7 @@
"label": "Abrir em nova guia" "label": "Abrir em nova guia"
}, },
"withBorder": { "withBorder": {
"label": "" "label": "Mostrar bordas"
}, },
"items": { "items": {
"label": "Marcadores", "label": "Marcadores",
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "Monitore o status do ArchiveTeam Warrior, projetos e trabalhos de arquivamento ativos.",
"option": {
"showBroadcastMessage": {
"label": "Mostrar mensagens de transmissão"
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "Resumo do DNS Hole", "name": "Resumo do DNS Hole",
"description": "Exibe o resumo do seu DNS Hole", "description": "Exibe o resumo do seu DNS Hole",
@@ -2390,31 +2399,31 @@
} }
}, },
"ups": { "ups": {
"name": "", "name": "UPS",
"description": "", "description": "Monitore o status de seus dispositivos UPS através de um servidor NUT",
"option": { "option": {
"showBattery": { "showBattery": {
"label": "" "label": "Exibir bateria"
}, },
"showLoad": { "showLoad": {
"label": "" "label": "Mostrar carga"
}, },
"showVoltage": { "showVoltage": {
"label": "" "label": "Mostrar tensão"
} }
}, },
"status": { "status": {
"online": "", "online": "Online",
"onBattery": "", "onBattery": "Na bateria",
"lowBattery": "", "lowBattery": "Bateria fraca",
"charging": "", "charging": "Carregando",
"unknown": "" "unknown": "Desconhecido"
}, },
"field": { "field": {
"runtime": "", "runtime": "Tempo de execução",
"load": "", "load": "Carga",
"input": "", "input": "Entrada",
"output": "" "output": "Saída"
}, },
"error": { "error": {
"internalServerError": "" "internalServerError": ""
@@ -2669,8 +2678,8 @@
} }
}, },
"paperlessNgx": { "paperlessNgx": {
"name": "", "name": "Paperless-ngx",
"description": "", "description": "Visão geral do seu arquivo de documentos Paperless-ngx",
"documentsTotal": "", "documentsTotal": "",
"documentsInbox": "", "documentsInbox": "",
"correspondents": "", "correspondents": "",
@@ -4133,7 +4142,7 @@
"items": { "items": {
"documentation": "Documentação", "documentation": "Documentação",
"submitIssue": "Enviar um problema", "submitIssue": "Enviar um problema",
"discord": "Discórdia da comunidade", "discord": "Discord da comunidade",
"sourceCode": "Código-fonte" "sourceCode": "Código-fonte"
} }
}, },
@@ -5741,7 +5750,7 @@
"label": "Reportar um problema" "label": "Reportar um problema"
}, },
"discord": { "discord": {
"label": "Discórdia da comunidade" "label": "Discord da comunidade"
} }
} }
} }
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "Сводка DNS-фильтра", "name": "Сводка DNS-фильтра",
"description": "Отображает сводную информацию о вашем DNS-фильтре", "description": "Отображает сводную информацию о вашем DNS-фильтре",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "Zhrnutie dier DNS", "name": "Zhrnutie dier DNS",
"description": "Zobrazí súhrn vašej diery DNS", "description": "Zobrazí súhrn vašej diery DNS",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Çözümleyici Özeti", "name": "DNS Çözümleyici Özeti",
"description": "DNS Çözümleyici özetini görüntüler", "description": "DNS Çözümleyici özetini görüntüler",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "", "name": "",
"description": "", "description": "",
+9
View File
@@ -1739,6 +1739,15 @@
} }
} }
}, },
"archiveTeamWarrior": {
"name": "",
"description": "",
"option": {
"showBroadcastMessage": {
"label": ""
}
}
},
"dnsHoleSummary": { "dnsHoleSummary": {
"name": "DNS Hole 概況", "name": "DNS Hole 概況",
"description": "顯示 DNS Hole 的概況", "description": "顯示 DNS Hole 的概況",
@@ -1,8 +1,6 @@
.beszelStatsSystemToggle { .beszelStatsSystemToggle {
opacity: 0; position: absolute;
transition: opacity 150ms ease; top: 6px;
} left: 6px;
z-index: 1;
.beszelStatsWrapper:hover .beszelStatsSystemToggle {
opacity: 1;
} }
@@ -1,10 +1,13 @@
"use client"; "use client";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import { ActionIcon, Button, Center, Menu, ScrollArea, Select, SimpleGrid, Stack, Text } from "@mantine/core"; import { Button, Center, Menu, ScrollArea, Select, SimpleGrid, Stack, Text } from "@mantine/core";
import { IconPlugConnectedX, IconServer, IconQuestionMark } from "@tabler/icons-react"; import { IconPlugConnectedX, IconServer, IconQuestionMark } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client"; import { clientApi } from "@homarr/api/client";
import { useSession } from "@homarr/auth/client";
import { constructBoardPermissions } from "@homarr/auth/shared";
import { useOptionalBoard } from "@homarr/boards/context";
import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types";
import { useScopedI18n } from "@homarr/translation/client"; import { useScopedI18n } from "@homarr/translation/client";
@@ -49,9 +52,15 @@ export default function BeszelSystemStatsWidget({
integrationIds, integrationIds,
isEditMode, isEditMode,
width, width,
boardId,
itemId,
setOptions, setOptions,
}: WidgetComponentProps<"beszelSystemStats">) { }: WidgetComponentProps<"beszelSystemStats">) {
const t = useScopedI18n("widget.beszelSystemStats"); const t = useScopedI18n("widget.beszelSystemStats");
const board = useOptionalBoard();
const { data: session } = useSession();
const hasChangeAccess = board ? constructBoardPermissions(board, session).hasChangeAccess : false;
const { mutate: saveItemOptions } = clientApi.widget.options.saveItemOptions.useMutation();
const { data: systemsResult = [], isPending: systemsPending } = clientApi.widget.beszel.getSystems.useQuery( const { data: systemsResult = [], isPending: systemsPending } = clientApi.widget.beszel.getSystems.useQuery(
{ integrationIds }, { integrationIds },
{ staleTime: 30 * 1000 }, { staleTime: 30 * 1000 },
@@ -68,8 +77,13 @@ export default function BeszelSystemStatsWidget({
const systemReady = !systemsPending && (selectedSystem === "" || systemExists); const systemReady = !systemsPending && (selectedSystem === "" || systemExists);
const handleSelectSystem = useCallback( const handleSelectSystem = useCallback(
(value: string) => setOptions({ newOptions: { systemId: value } }), (value: string) => {
[setOptions], setOptions({ newOptions: { systemId: value } });
if (hasChangeAccess && boardId && itemId) {
saveItemOptions({ boardId, itemId, newOptions: { systemId: value } });
}
},
[setOptions, hasChangeAccess, boardId, itemId, saveItemOptions],
); );
const showDocker = options.showDockerCpu || options.showDockerMemory || options.showDockerNetwork; const showDocker = options.showDockerCpu || options.showDockerMemory || options.showDockerNetwork;
@@ -208,40 +222,27 @@ export default function BeszelSystemStatsWidget({
} }
return ( return (
<ScrollArea <ScrollArea h="100%" style={{ pointerEvents: (isEditMode && "none") || undefined }}>
h="100%"
className={classes.beszelStatsWrapper}
style={{ pointerEvents: (isEditMode && "none") || undefined }}
>
<Stack gap="md" p="sm"> <Stack gap="md" p="sm">
{!isEditMode && systems.length > 1 && ( {!isEditMode && systems.length > 1 && (
<Menu <Menu position="bottom-start" withArrow shadow="md" withinPortal>
trigger="click-hover"
openDelay={100}
closeDelay={300}
position="bottom-start"
withArrow
shadow="md"
withinPortal
>
<Menu.Target> <Menu.Target>
<ActionIcon <Button
variant="subtle" variant="default"
size="sm" size="compact-xs"
leftSection={<IconServer size={14} />}
className={classes.beszelStatsSystemToggle} className={classes.beszelStatsSystemToggle}
style={{ position: "absolute", top: 6, left: 6, zIndex: 1 }}
title={selectedLabel}
> >
<IconServer size={14} /> {selectedLabel}
</ActionIcon> </Button>
</Menu.Target> </Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
{systems.map((s) => ( {systems.map((s) => (
<Menu.Item <Menu.Item
key={s.value} key={s.value}
fz="xs" fz="xs"
fw={(s.value === selectedSystem && 600) || 400} fw={s.value === selectedSystem ? 600 : 400}
c={(s.value !== selectedSystem && "dimmed") || undefined} c={s.value !== selectedSystem ? "dimmed" : undefined}
onClick={() => handleSelectSystem(s.value)} onClick={() => handleSelectSystem(s.value)}
> >
{s.label} {s.label}
+376 -463
View File
File diff suppressed because it is too large. Load diff
+22 -22
View File
@@ -49,25 +49,25 @@ catalog:
"@tanstack/react-query": ^5.101.0 "@tanstack/react-query": ^5.101.0
"@tanstack/react-query-devtools": ^5.101.0 "@tanstack/react-query-devtools": ^5.101.0
"@tanstack/react-query-next-experimental": ^5.101.0 "@tanstack/react-query-next-experimental": ^5.101.0
"@testcontainers/mysql": ^12.0.1 "@testcontainers/mysql": ^12.0.2
"@testcontainers/postgresql": ^12.0.1 "@testcontainers/postgresql": ^12.0.2
"@testcontainers/redis": ^12.0.1 "@testcontainers/redis": ^12.0.2
"@tiptap/extension-color": 3.26.0 "@tiptap/extension-color": 3.26.1
"@tiptap/extension-highlight": 3.26.0 "@tiptap/extension-highlight": 3.26.1
"@tiptap/extension-image": 3.26.0 "@tiptap/extension-image": 3.26.1
"@tiptap/extension-link": ^3.26.0 "@tiptap/extension-link": ^3.26.1
"@tiptap/extension-placeholder": ^3.26.0 "@tiptap/extension-placeholder": ^3.26.1
"@tiptap/extension-table": 3.26.0 "@tiptap/extension-table": 3.26.1
"@tiptap/extension-table-cell": 3.26.0 "@tiptap/extension-table-cell": 3.26.1
"@tiptap/extension-table-header": 3.26.0 "@tiptap/extension-table-header": 3.26.1
"@tiptap/extension-table-row": 3.26.0 "@tiptap/extension-table-row": 3.26.1
"@tiptap/extension-task-item": 3.26.0 "@tiptap/extension-task-item": 3.26.1
"@tiptap/extension-task-list": 3.26.0 "@tiptap/extension-task-list": 3.26.1
"@tiptap/extension-text-align": 3.26.0 "@tiptap/extension-text-align": 3.26.1
"@tiptap/extension-text-style": 3.26.0 "@tiptap/extension-text-style": 3.26.1
"@tiptap/extension-underline": 3.26.0 "@tiptap/extension-underline": 3.26.1
"@tiptap/react": ^3.26.0 "@tiptap/react": ^3.26.1
"@tiptap/starter-kit": ^3.26.0 "@tiptap/starter-kit": ^3.26.1
"@trpc/client": ^11.17.0 "@trpc/client": ^11.17.0
"@trpc/next": ^11.17.0 "@trpc/next": ^11.17.0
"@trpc/react-query": ^11.17.0 "@trpc/react-query": ^11.17.0
@@ -88,7 +88,7 @@ catalog:
"@types/prismjs": ^1.26.6 "@types/prismjs": ^1.26.6
"@types/react": 19.2.17 "@types/react": 19.2.17
"@types/react-dom": 19.2.3 "@types/react-dom": 19.2.3
"@scalar/api-reference-react": ^0.9.45 "@scalar/api-reference-react": ^0.9.46
"@types/video.js": ^7.3.58 "@types/video.js": ^7.3.58
"@types/ws": ^8.18.1 "@types/ws": ^8.18.1
"@types/xml2js": ^0.4.14 "@types/xml2js": ^0.4.14
@@ -131,7 +131,7 @@ catalog:
ical.js: ^2.2.1 ical.js: ^2.2.1
ioredis: 5.11.1 ioredis: 5.11.1
isomorphic-dompurify: ^3.16.0 isomorphic-dompurify: ^3.16.0
jotai: ^2.20.0 jotai: ^2.20.1
jsdom: ^29.1.1 jsdom: ^29.1.1
json5: ^2.2.3 json5: ^2.2.3
jsonpath-plus: ^10.4.0 jsonpath-plus: ^10.4.0
@@ -168,7 +168,7 @@ catalog:
semver-parser: ^4.2.1 semver-parser: ^4.2.1
superjson: 2.2.6 superjson: 2.2.6
svgson: ^5.3.1 svgson: ^5.3.1
testcontainers: ^12.0.1 testcontainers: ^12.0.2
timezones-ical-library: ^2.2.0 timezones-ical-library: ^2.2.0
trpc-to-mcp: ^1.3.2 trpc-to-mcp: ^1.3.2
trpc-to-openapi: ^3.3.0 trpc-to-openapi: ^3.3.0