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