Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e29ecf748a | ||
|
|
55fb227305 | ||
|
|
39e424563a | ||
|
|
e29c79a902 | ||
|
|
a19c9d88ca | ||
|
|
1fb97ee9c6 | ||
|
|
f69e545d64 | ||
|
|
52df56b19d | ||
|
|
0f66bfd6c1 | ||
|
|
842c5a0b4c | ||
|
|
ce9134dee2 | ||
|
|
9ce9be6b7e | ||
|
|
7ec22411a6 | ||
|
|
d3ae06e515 | ||
|
|
d09a4f8468 | ||
|
|
e1a245ff2d | ||
|
|
bf4396917e | ||
|
|
d059023b51 | ||
|
|
d9cbd5427d | ||
|
|
c56d9d6894 | ||
|
|
3ea49f0391 | ||
|
|
e2f8f5f081 | ||
|
|
f57037b568 | ||
|
|
84803aeefb | ||
|
|
5f47298f3d | ||
|
|
de858011fc | ||
|
|
12f8e2b83a | ||
|
|
78b6e520ac | ||
|
|
bcaa93f90b | ||
|
|
15e6612697 | ||
|
|
eef06dd5a3 | ||
|
|
84e044f427 | ||
|
|
609ab9976a | ||
|
|
28f570aa79 | ||
|
|
5b4f03da35 | ||
|
|
6960ac15e9 | ||
|
|
f1ee76c99c | ||
|
|
9a9ee5a9d7 | ||
|
|
7debe34e24 | ||
|
|
0d67c5a451 | ||
|
|
2db0fe15fb | ||
|
|
4b85afbb87 | ||
|
|
536f82af04 | ||
|
|
445e191490 |
No files matched your search
@@ -33,6 +33,9 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.67.0
|
||||
- 1.66.1
|
||||
- 1.66.0
|
||||
- 1.65.0
|
||||
- 1.64.0
|
||||
- 1.62.0
|
||||
|
||||
@@ -3,6 +3,7 @@ name: "[Deployment] Automatic Weekly Release"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 19 * * 5" # https://crontab.guru/#0_19_*_*_5
|
||||
timezone: Europe/Zurich
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
send-notifications:
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
## [1.67.0](https://github.com/homarr-labs/homarr/compare/v1.66.1...v1.67.0) (2026-06-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** migrate from Umami to PostHog server-side analytics ([#5983](https://github.com/homarr-labs/homarr/issues/5983)) ([d059023](https://github.com/homarr-labs/homarr/commit/d059023b51a323df027c1cf033454440bd0d7e57))
|
||||
* Calculate and display total CPU and memory usage ([#5911](https://github.com/homarr-labs/homarr/issues/5911)) ([d9cbd54](https://github.com/homarr-labs/homarr/commit/d9cbd5427d0e5be3e5dbecca8d6069dc8074d810))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** isolate session cookies from other Auth.js apps on same host ([#5996](https://github.com/homarr-labs/homarr/issues/5996)) ([e1a245f](https://github.com/homarr-labs/homarr/commit/e1a245ff2d8520d08957c4a7dbe01d039607a973))
|
||||
* **deps:** update tailwindcss monorepo to ^4.3.1 ([#5988](https://github.com/homarr-labs/homarr/issues/5988)) ([78b6e52](https://github.com/homarr-labs/homarr/commit/78b6e520ac095129398d3da8f2441df408e93e9f))
|
||||
* **downloads:** sort stalled torrents last when sorting by finish time ([#6011](https://github.com/homarr-labs/homarr/issues/6011)) ([c56d9d6](https://github.com/homarr-labs/homarr/commit/c56d9d6894c717b5c70ab75d6a100d4d1071c2d3)), closes [#5416](https://github.com/homarr-labs/homarr/issues/5416)
|
||||
|
||||
## [1.65.0](https://github.com/homarr-labs/homarr/compare/v1.64.0...v1.65.0) (2026-06-12)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -23,6 +23,7 @@ Homarr supports multiple authentication options, from internal userbase (credent
|
||||
| AUTH_PROVIDERS | Select Which provider to use between *credentials*, *ldap* and *oidc*. <br /> Multiple providers can be enabled with by separating them with `,`, (ex. `AUTH_PROVIDERS=credentials,oidc`, it is highly recommended to just enable one provider). | credentials |
|
||||
| AUTH_LOGOUT_REDIRECT_URL | URL to redirect to after clicking logging out. | --- |
|
||||
| AUTH_SESSION_EXPIRY_TIME | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") | "30d" |
|
||||
| AUTH_COOKIE_PREFIX | Prefix used for all authentication cookies (session, csrf, callback, pkce, state, nonce). Change this if you run another Auth.js/NextAuth app on the same hostname as Homarr (browsers scope cookies by host, not by port) to avoid cookie name collisions. Only letters, numbers, hyphens and underscores are allowed. | `homarr` |
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="credentials" label="Credentials Provider" default>
|
||||
|
||||
@@ -395,6 +395,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
|
||||
| env.AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG | string | `nil` | Extra arguments for user search filter (& based) |
|
||||
| env.AUTH_LDAP_USER_MAIL_ATTRIBUTE | string | `"mail"` | Attribute used for mail field |
|
||||
| env.AUTH_LOGOUT_REDIRECT_URL | string | `nil` | URL to redirect to after clicking logging out. |
|
||||
| env.AUTH_COOKIE_PREFIX | string | `"homarr"` | Prefix used for all authentication cookies. Change if you run another Auth.js/NextAuth app on the same hostname to avoid cookie name collisions. Only letters, numbers, hyphens and underscores. |
|
||||
| env.AUTH_OIDC_AUTO_LOGIN | string | `"false"` | Automatically redirect to OIDC login |
|
||||
| env.AUTH_OIDC_CLIENT_NAME | string | `"OIDC"` | Display name of provider (in login screen) |
|
||||
| env.AUTH_OIDC_GROUPS_ATTRIBUTE | string | `"groups"` | Attribute used for groups (roles) claim |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@scalar/api-reference-react": "catalog:",
|
||||
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"@tailwindcss/postcss": "^4.3.0",
|
||||
"@tailwindcss/postcss": "^4.3.1",
|
||||
"clsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"docusaurus-plugin-image-zoom": "^3.0.1",
|
||||
@@ -44,7 +44,7 @@
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"swiper": "^12.2.0",
|
||||
"tailwindcss": "^4.3.0"
|
||||
"tailwindcss": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.10.1",
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
"mcp-handler": "catalog:",
|
||||
"next": "catalog:",
|
||||
"postcss-preset-mantine": "catalog:",
|
||||
"posthog-js": "catalog:",
|
||||
"prismjs": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
|
||||
@@ -139,11 +139,11 @@ export default async function Layout(props: {
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head>
|
||||
<Analytics />
|
||||
<SearchEngineOptimization />
|
||||
<CrowdinLiveTranslation locale={locale} />
|
||||
</head>
|
||||
<body className={["font-sans", fontSans.variable].join(" ")}>
|
||||
<Analytics enabled={serverSettings.analytics.enableGeneral} />
|
||||
<StackedProvider>
|
||||
<Notifications pauseResetOnHover="notification" />
|
||||
<ServiceWorkerRegistration />
|
||||
|
||||
+2
@@ -131,6 +131,7 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
{(error.data.reason === "untrusted" && rootCertificate.isSelfSigned) ||
|
||||
error.data.reason === "hostnameMismatch" ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
fullWidth
|
||||
onClick={error.data.reason === "hostnameMismatch" ? handleTrustHostname : handleTrustSelfSigned}
|
||||
@@ -140,6 +141,7 @@ export const CertificateErrorDetails = ({ error, url }: CertificateErrorDetailsP
|
||||
) : null}
|
||||
{error.data.reason === "untrusted" && !rootCertificate.isSelfSigned ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
fullWidth
|
||||
onClick={() =>
|
||||
|
||||
@@ -17,9 +17,10 @@ interface AnalyticsSettingsProps {
|
||||
|
||||
export const AnalyticsSettings = ({ initialData }: AnalyticsSettingsProps) => {
|
||||
const t = useScopedI18n("management.page.settings.section.analytics");
|
||||
const { instanceId: _, ...formDefaults } = initialData;
|
||||
const form = useForm({
|
||||
initialValues: initialData,
|
||||
onValuesChange: (updatedValues, _) => {
|
||||
initialValues: formDefaults,
|
||||
onValuesChange: (updatedValues, _prev) => {
|
||||
if (!form.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import Script from "next/script";
|
||||
"use client";
|
||||
|
||||
import { UMAMI_WEBSITE_ID } from "@homarr/analytics";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const Analytics = async () => {
|
||||
// For static pages it will not find any analytics data so we do not include the script on them
|
||||
const analytics = await getServerSettingByKeyAsync(db, "analytics").catch(() => null);
|
||||
const POSTHOG_KEY = "phc_vYBmGWNbRshvfeC7EHfeSmUm2pD2Neg5nGqzJuGvS8Hs";
|
||||
const POSTHOG_HOST = "https://hog.homarr.dev";
|
||||
|
||||
if (analytics?.enableGeneral) {
|
||||
return <Script src="https://umami.homarr.dev/script.js" data-website-id={UMAMI_WEBSITE_ID} defer />;
|
||||
}
|
||||
export const Analytics = ({ enabled }: { enabled: boolean }) => {
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
|
||||
return <></>;
|
||||
void import("posthog-js").then(({ default: posthog }) => {
|
||||
posthog.init(POSTHOG_KEY, {
|
||||
api_host: POSTHOG_HOST,
|
||||
capture_pageview: false,
|
||||
capture_pageleave: false,
|
||||
autocapture: false,
|
||||
persistence: "memory",
|
||||
});
|
||||
});
|
||||
}, [enabled]);
|
||||
|
||||
return null;
|
||||
};
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.65.0",
|
||||
"version": "1.67.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -64,7 +64,7 @@
|
||||
"node": ">=24.16.0",
|
||||
"pnpm": ">=11.6.0"
|
||||
},
|
||||
"packageManager": "pnpm@11.6.0",
|
||||
"packageManager": "pnpm@11.7.0",
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "catalog:"
|
||||
}
|
||||
|
||||
@@ -26,9 +26,7 @@
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@umami/node": "catalog:",
|
||||
"superjson": "catalog:"
|
||||
"posthog-node": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { PostHog } from "posthog-node";
|
||||
|
||||
export const POSTHOG_API_KEY = "phc_vYBmGWNbRshvfeC7EHfeSmUm2pD2Neg5nGqzJuGvS8Hs";
|
||||
export const POSTHOG_HOST = "https://hog.homarr.dev";
|
||||
|
||||
let instance: PostHog | undefined;
|
||||
|
||||
export const getPostHogClient = (): PostHog => {
|
||||
instance ??= new PostHog(POSTHOG_API_KEY, {
|
||||
host: POSTHOG_HOST,
|
||||
flushAt: 10,
|
||||
flushInterval: 30_000,
|
||||
});
|
||||
return instance;
|
||||
};
|
||||
|
||||
export const trackEvent = (instanceId: string, event: string, properties?: Record<string, unknown>) => {
|
||||
getPostHogClient().capture({
|
||||
distinctId: instanceId,
|
||||
event,
|
||||
properties,
|
||||
});
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export const UMAMI_HOST_URL = "https://umami.homarr.dev";
|
||||
export const UMAMI_WEBSITE_ID = "ff7dc470-a84f-4779-b1ab-66a5bb16a94b";
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./constants";
|
||||
export * from "./send-server-analytics";
|
||||
export { trackEvent } from "./client";
|
||||
export { sendServerAnalyticsAsync } from "./send-server-analytics";
|
||||
@@ -1,17 +1,16 @@
|
||||
import type { UmamiEventData } from "@umami/node";
|
||||
import { Umami } from "@umami/node";
|
||||
|
||||
import { isProviderEnabled } from "@homarr/auth/server";
|
||||
import { Stopwatch } from "@homarr/common";
|
||||
import { createId, Stopwatch } from "@homarr/common";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { count, db } from "@homarr/db";
|
||||
import { isMysql, isPostgresql } from "@homarr/db/collection";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { getServerSettingByKeyAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import {
|
||||
accounts,
|
||||
apiKeys,
|
||||
apps,
|
||||
boards,
|
||||
cronJobConfigurations,
|
||||
customWidgetDefinitions,
|
||||
groups,
|
||||
iconRepositories,
|
||||
icons,
|
||||
@@ -24,86 +23,160 @@ import {
|
||||
searchEngines,
|
||||
sectionLayouts,
|
||||
sections,
|
||||
sessions,
|
||||
trustedCertificateHostnames,
|
||||
users,
|
||||
} from "@homarr/db/schema";
|
||||
import { env as dockerEnv } from "@homarr/docker/env";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
import { UMAMI_HOST_URL, UMAMI_WEBSITE_ID } from "./constants";
|
||||
import { getPostHogClient } from "./client";
|
||||
|
||||
const logger = createLogger({ module: "analytics" });
|
||||
|
||||
const getDatabaseType = (): "mysql" | "postgresql" | "sqlite" => {
|
||||
if (isMysql()) return "mysql";
|
||||
if (isPostgresql()) return "postgresql";
|
||||
return "sqlite";
|
||||
type AnalyticsResult = "sent" | "disabled" | "failed";
|
||||
|
||||
// ponytail: no DB-level lock; concurrent calls may both generate an ID, but the last write wins
|
||||
// and the ID stabilizes after the first successful run. Upgrade path: use DB upsert with WHERE instanceId IS NULL.
|
||||
const getOrCreateInstanceId = async (
|
||||
analyticsSettings: Awaited<ReturnType<typeof getServerSettingByKeyAsync<"analytics">>>,
|
||||
): Promise<string> => {
|
||||
if (analyticsSettings.instanceId) return analyticsSettings.instanceId;
|
||||
|
||||
const instanceId = createId();
|
||||
await updateServerSettingByKeyAsync(db, "analytics", { ...analyticsSettings, instanceId });
|
||||
|
||||
const verified = await getServerSettingByKeyAsync(db, "analytics");
|
||||
return verified.instanceId ?? instanceId;
|
||||
};
|
||||
|
||||
const getEnabledAuthProviders = (): string[] => {
|
||||
const providers: string[] = [];
|
||||
if (isProviderEnabled("credentials")) providers.push("credentials");
|
||||
if (isProviderEnabled("oidc")) providers.push("oidc");
|
||||
if (isProviderEnabled("ldap")) providers.push("ldap");
|
||||
return providers;
|
||||
};
|
||||
const sumGroupedCounts = (rows: { count: number }[]): number => rows.reduce((sum, row) => sum + row.count, 0);
|
||||
|
||||
export const sendServerAnalyticsAsync = async () => {
|
||||
export const sendServerAnalyticsAsync = async (): Promise<AnalyticsResult> => {
|
||||
const stopWatch = new Stopwatch();
|
||||
const analyticsSettings = await getServerSettingByKeyAsync(db, "analytics");
|
||||
|
||||
if (!analyticsSettings.enableGeneral) {
|
||||
logger.info("Analytics are disabled. No data will be sent. Enable analytics in the settings");
|
||||
return;
|
||||
return "disabled";
|
||||
}
|
||||
|
||||
const umamiInstance = new Umami();
|
||||
umamiInstance.init({
|
||||
hostUrl: UMAMI_HOST_URL,
|
||||
websiteId: UMAMI_WEBSITE_ID,
|
||||
});
|
||||
const client = getPostHogClient();
|
||||
|
||||
const enabledAuthProviders = getEnabledAuthProviders();
|
||||
try {
|
||||
const instanceId = await getOrCreateInstanceId(analyticsSettings);
|
||||
|
||||
const analyticsData: UmamiEventData = {
|
||||
homarrVersion: packageJson.version,
|
||||
databaseType: getDatabaseType(),
|
||||
dockerEnabled: dockerEnv.ENABLE_DOCKER ? 1 : 0,
|
||||
kubernetesEnabled: dockerEnv.ENABLE_KUBERNETES ? 1 : 0,
|
||||
authCredentials: enabledAuthProviders.includes("credentials") ? 1 : 0,
|
||||
authOidc: enabledAuthProviders.includes("oidc") ? 1 : 0,
|
||||
authLdap: enabledAuthProviders.includes("ldap") ? 1 : 0,
|
||||
countUsers: await db.$count(users),
|
||||
countBoards: await db.$count(boards),
|
||||
countGroups: await db.$count(groups),
|
||||
countApps: await db.$count(apps),
|
||||
countWidgets: await db.$count(items),
|
||||
countSections: await db.$count(sections),
|
||||
countIntegrations: await db.$count(integrations),
|
||||
countSearchEngines: await db.$count(searchEngines),
|
||||
countIconRepositories: await db.$count(iconRepositories),
|
||||
countIcons: await db.$count(icons),
|
||||
countApiKeys: await db.$count(apiKeys),
|
||||
countInvites: await db.$count(invites),
|
||||
countMedias: await db.$count(medias),
|
||||
countLayouts: await db.$count(layouts),
|
||||
countItemLayouts: await db.$count(itemLayouts),
|
||||
countSectionLayouts: await db.$count(sectionLayouts),
|
||||
countCronJobConfigs: await db.$count(cronJobConfigurations),
|
||||
};
|
||||
const [
|
||||
cultureSettings,
|
||||
countBoards,
|
||||
countGroups,
|
||||
countApps,
|
||||
countSections,
|
||||
countSearchEngines,
|
||||
countIconRepositories,
|
||||
countIcons,
|
||||
countLayouts,
|
||||
countItemLayouts,
|
||||
countSectionLayouts,
|
||||
countCronJobConfigs,
|
||||
countCustomWidgets,
|
||||
countTrustedCertificates,
|
||||
countUsers,
|
||||
countApiKeys,
|
||||
countInvites,
|
||||
countMedias,
|
||||
countSessions,
|
||||
countAccounts,
|
||||
integrationKinds,
|
||||
widgetKinds,
|
||||
] = await Promise.all([
|
||||
getServerSettingByKeyAsync(db, "culture"),
|
||||
db.$count(boards),
|
||||
db.$count(groups),
|
||||
db.$count(apps),
|
||||
db.$count(sections),
|
||||
db.$count(searchEngines),
|
||||
db.$count(iconRepositories),
|
||||
db.$count(icons),
|
||||
db.$count(layouts),
|
||||
db.$count(itemLayouts),
|
||||
db.$count(sectionLayouts),
|
||||
db.$count(cronJobConfigurations),
|
||||
db.$count(customWidgetDefinitions),
|
||||
db.$count(trustedCertificateHostnames),
|
||||
db.$count(users),
|
||||
db.$count(apiKeys),
|
||||
db.$count(invites),
|
||||
db.$count(medias),
|
||||
db.$count(sessions),
|
||||
db.$count(accounts),
|
||||
db
|
||||
.select({ kind: integrations.kind, count: count(integrations.id) })
|
||||
.from(integrations)
|
||||
.groupBy(integrations.kind),
|
||||
db
|
||||
.select({ kind: items.kind, count: count(items.id) })
|
||||
.from(items)
|
||||
.groupBy(items.kind),
|
||||
]);
|
||||
|
||||
const integrationKinds = await db
|
||||
.select({ kind: integrations.kind, count: count(integrations.id) })
|
||||
.from(integrations)
|
||||
.groupBy(integrations.kind);
|
||||
const enabledAuthProviders = (["credentials", "oidc", "ldap"] as const).filter(isProviderEnabled);
|
||||
|
||||
integrationKinds.forEach((integrationKind) => {
|
||||
analyticsData[`integration_${integrationKind.kind}`] = integrationKind.count;
|
||||
});
|
||||
const properties: Record<string, unknown> = {
|
||||
homarrVersion: packageJson.version,
|
||||
databaseType: isMysql() ? "mysql" : isPostgresql() ? "postgresql" : "sqlite",
|
||||
dockerEnabled: Boolean(dockerEnv.ENABLE_DOCKER),
|
||||
kubernetesEnabled: Boolean(dockerEnv.ENABLE_KUBERNETES),
|
||||
authProviders: enabledAuthProviders,
|
||||
|
||||
const response = await umamiInstance.track("server-analytics-v2", analyticsData);
|
||||
if (!response.ok) {
|
||||
logger.warn("Unable to send analytics to Umami instance");
|
||||
osPlatform: process.platform,
|
||||
osArch: process.arch,
|
||||
uptimeSeconds: Math.floor(process.uptime()),
|
||||
defaultLocale: cultureSettings.defaultLocale,
|
||||
|
||||
countBoards,
|
||||
countGroups,
|
||||
countApps,
|
||||
countSections,
|
||||
countSearchEngines,
|
||||
countIconRepositories,
|
||||
countIcons,
|
||||
countLayouts,
|
||||
countItemLayouts,
|
||||
countSectionLayouts,
|
||||
countCronJobConfigs,
|
||||
countCustomWidgets,
|
||||
countTrustedCertificates,
|
||||
|
||||
countUsers,
|
||||
countApiKeys,
|
||||
countInvites,
|
||||
countMedias,
|
||||
countSessions,
|
||||
countAccounts,
|
||||
|
||||
countIntegrations: sumGroupedCounts(integrationKinds),
|
||||
countWidgets: sumGroupedCounts(widgetKinds),
|
||||
};
|
||||
|
||||
for (const row of integrationKinds) {
|
||||
properties[`integration_${row.kind}`] = row.count;
|
||||
}
|
||||
for (const row of widgetKinds) {
|
||||
properties[`widget_${row.kind}`] = row.count;
|
||||
}
|
||||
|
||||
client.capture({
|
||||
distinctId: instanceId,
|
||||
event: "server-analytics",
|
||||
properties,
|
||||
});
|
||||
|
||||
await client.flush();
|
||||
logger.info(`Sent analytics to PostHog in ${stopWatch.getElapsedInHumanWords()}`);
|
||||
return "sent";
|
||||
} catch (error) {
|
||||
logger.warn("Failed to send analytics to PostHog", { error });
|
||||
return "failed";
|
||||
}
|
||||
|
||||
logger.info(`Sent analytics in ${stopWatch.getElapsedInHumanWords()}`);
|
||||
};
|
||||
@@ -24,6 +24,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/analytics": "workspace:^0.1.0",
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
|
||||
@@ -28,6 +28,7 @@ export const appRouter = createTRPCRouter({
|
||||
updateChecker: lazy(() => import("./router/update-checker").then((mod) => mod.updateCheckerRouter)),
|
||||
certificates: lazy(() => import("./router/certificates/certificate-router").then((mod) => mod.certificateRouter)),
|
||||
bangs: lazy(() => import("./router/bangs/bangs-router").then((mod) => mod.bangsRouter)),
|
||||
analytics: lazy(() => import("./router/analytics").then((mod) => mod.analyticsRouter)),
|
||||
info: lazy(() => import("./router/info").then((mod) => mod.infoRouter)),
|
||||
customWidget: lazy(() => import("./router/custom-widget/custom-widget-router").then((mod) => mod.customWidgetRouter)),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import z from "zod/v4";
|
||||
|
||||
import { sendServerAnalyticsAsync, trackEvent } from "@homarr/analytics";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
|
||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../trpc";
|
||||
|
||||
const getInstanceId = async (): Promise<string | null> => {
|
||||
if (env.NO_EXTERNAL_CONNECTION) return null;
|
||||
const settings = await getServerSettingByKeyAsync(db, "analytics");
|
||||
if (!settings.enableGeneral) return null;
|
||||
return settings.instanceId;
|
||||
};
|
||||
|
||||
export const analyticsRouter = createTRPCRouter({
|
||||
sendAnalytics: permissionRequiredProcedure.requiresPermission("admin").mutation(async () => {
|
||||
if (env.NO_EXTERNAL_CONNECTION) {
|
||||
throw new TRPCError({ code: "PRECONDITION_FAILED", message: "External connections are disabled" });
|
||||
}
|
||||
return { status: await sendServerAnalyticsAsync() };
|
||||
}),
|
||||
|
||||
trackFeature: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
feature: z.string().max(128),
|
||||
properties: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const instanceId = await getInstanceId();
|
||||
if (!instanceId) return;
|
||||
|
||||
trackEvent(instanceId, `feature:${input.feature}`, {
|
||||
...input.properties,
|
||||
userId: ctx.session.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
heartbeat: protectedProcedure.mutation(async ({ ctx }) => {
|
||||
const instanceId = await getInstanceId();
|
||||
if (!instanceId) return;
|
||||
|
||||
trackEvent(instanceId, "user-active", {
|
||||
userId: ctx.session.user.id,
|
||||
});
|
||||
}),
|
||||
});
|
||||
@@ -15,20 +15,36 @@ export const iconsRouter = createTRPCRouter({
|
||||
})
|
||||
.input(iconsFindSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const term = input.searchText?.toLowerCase().trim() ?? "";
|
||||
const keywords = (input.searchText ?? "").split(" ").filter((keyword) => keyword.length > 0);
|
||||
|
||||
let whereCondition = undefined;
|
||||
if (term.length > 0) {
|
||||
whereCondition = and(...keywords.map((keyword) => like(icons.name, `%${keyword}%`)));
|
||||
}
|
||||
|
||||
return {
|
||||
icons: await ctx.db.query.iconRepositories.findMany({
|
||||
with: {
|
||||
icons: {
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
url: true,
|
||||
columns: { id: true, name: true, url: true },
|
||||
where: whereCondition,
|
||||
orderBy: (table, { asc, sql }) => {
|
||||
// ponytail: SVG first, then alphabetical; with a search term,
|
||||
// prepend a relevance tier (exact > prefix > substring).
|
||||
const svgFirst = sql`CASE WHEN ${table.name} LIKE '%.svg' THEN 0 ELSE 1 END`;
|
||||
const nameAsc = asc(table.name);
|
||||
if (term.length === 0) {
|
||||
return [svgFirst, nameAsc];
|
||||
}
|
||||
const tier = sql`CASE
|
||||
WHEN LOWER(${table.name}) = ${term} THEN 0
|
||||
WHEN LOWER(${table.name}) LIKE ${term + "%"} THEN 1
|
||||
WHEN LOWER(${table.name}) LIKE ${`%${term}%`} THEN 2
|
||||
ELSE 3
|
||||
END`;
|
||||
return [tier, svgFirst, nameAsc];
|
||||
},
|
||||
where:
|
||||
(input.searchText?.length ?? 0) > 0
|
||||
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
and(...input.searchText!.split(" ").map((keyword) => like(icons.name, `%${keyword}%`)))
|
||||
: undefined,
|
||||
limit: input.limitPerGroup,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
integrationSecretKindObject,
|
||||
} from "@homarr/definitions";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import { invalidateIntegrationCacheAsync } from "@homarr/redis";
|
||||
import { byIdSchema } from "@homarr/validation/common";
|
||||
import {
|
||||
integrationCreateSchema,
|
||||
@@ -469,6 +470,10 @@ export const integrationRouter = createTRPCRouter({
|
||||
kind: integration.kind,
|
||||
url: input.url,
|
||||
});
|
||||
|
||||
// Invalidate all cached data for this integration so that widgets pick up the
|
||||
// new configuration immediately instead of serving stale (or errored) data.
|
||||
await invalidateIntegrationCacheAsync(input.id);
|
||||
}),
|
||||
delete: protectedProcedure
|
||||
.meta({
|
||||
@@ -490,6 +495,9 @@ export const integrationRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
await ctx.db.delete(integrations).where(eq(integrations.id, input.id));
|
||||
|
||||
// Clean up any cached data left behind by the deleted integration.
|
||||
await invalidateIntegrationCacheAsync(input.id);
|
||||
}),
|
||||
getIntegrationPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
|
||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||
|
||||
@@ -24,17 +24,18 @@ export const serverSettingsRouter = createTRPCRouter({
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await updateServerSettingByKeyAsync(
|
||||
ctx.db,
|
||||
input.settingsKey,
|
||||
input.value as ServerSettings[keyof ServerSettings],
|
||||
);
|
||||
const current = await getServerSettingByKeyAsync(ctx.db, input.settingsKey);
|
||||
await updateServerSettingByKeyAsync(ctx.db, input.settingsKey, {
|
||||
...current,
|
||||
...input.value,
|
||||
} as ServerSettings[keyof ServerSettings]);
|
||||
}),
|
||||
initSettings: onboardingProcedure
|
||||
.requiresStep("settings")
|
||||
.input(settingsInitSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await updateServerSettingByKeyAsync(ctx.db, "analytics", input.analytics);
|
||||
const currentAnalytics = await getServerSettingByKeyAsync(ctx.db, "analytics");
|
||||
await updateServerSettingByKeyAsync(ctx.db, "analytics", { ...currentAnalytics, ...input.analytics });
|
||||
await updateServerSettingByKeyAsync(ctx.db, "crawlingAndIndexing", input.crawlingAndIndexing);
|
||||
await nextOnboardingStepAsync(ctx.db, undefined);
|
||||
}),
|
||||
|
||||
@@ -75,9 +75,6 @@ describe("saveSettings", () => {
|
||||
settingsKey: "analytics",
|
||||
value: {
|
||||
enableGeneral: true,
|
||||
enableWidgetData: true,
|
||||
enableIntegrationData: true,
|
||||
enableUserData: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -87,9 +84,7 @@ describe("saveSettings", () => {
|
||||
settingKey: "analytics",
|
||||
value: SuperJSON.stringify({
|
||||
enableGeneral: true,
|
||||
enableWidgetData: true,
|
||||
enableIntegrationData: true,
|
||||
enableUserData: true,
|
||||
instanceId: null,
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -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));
|
||||
}),
|
||||
});
|
||||
@@ -20,6 +20,24 @@ import { expireDateAfter, generateSessionToken, sessionTokenCookieName } from ".
|
||||
|
||||
const logger = createLogger({ module: "authConfiguration" });
|
||||
|
||||
// All Auth.js cookies are prefixed with AUTH_COOKIE_PREFIX (default "homarr") so
|
||||
// Homarr's cookie jar is isolated from any other Auth.js/NextAuth app on the
|
||||
// same hostname (browsers scope cookies by host, not by port).
|
||||
// See https://github.com/homarr-labs/homarr/issues/5773
|
||||
const createCookies = (useSecureCookies: boolean) => {
|
||||
const prefix = env.AUTH_COOKIE_PREFIX;
|
||||
const securePrefix = useSecureCookies ? "__Secure-" : "";
|
||||
const hostPrefix = useSecureCookies ? "__Host-" : "";
|
||||
return {
|
||||
sessionToken: { name: sessionTokenCookieName },
|
||||
csrfToken: { name: `${hostPrefix}${prefix}.csrf-token` },
|
||||
callbackUrl: { name: `${securePrefix}${prefix}.callback-url` },
|
||||
pkceCodeVerifier: { name: `${securePrefix}${prefix}.pkce.cooldown` },
|
||||
state: { name: `${securePrefix}${prefix}.state` },
|
||||
nonce: { name: `${securePrefix}${prefix}.nonce` },
|
||||
};
|
||||
};
|
||||
|
||||
// See why it's unknown in the [...nextauth]/route.ts file
|
||||
export const createConfiguration = (
|
||||
provider: SupportedAuthProvider | "unknown",
|
||||
@@ -41,11 +59,7 @@ export const createConfiguration = (
|
||||
},
|
||||
},
|
||||
trustHost: true,
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
name: sessionTokenCookieName,
|
||||
},
|
||||
},
|
||||
cookies: createCookies(useSecureCookies),
|
||||
adapter,
|
||||
providers: filterProviders([
|
||||
Credentials(createCredentialsConfiguration(db)),
|
||||
|
||||
@@ -28,6 +28,11 @@ export const env = createEnv({
|
||||
AUTH_LOGOUT_REDIRECT_URL: z.string().url().optional(),
|
||||
AUTH_SESSION_EXPIRY_TIME: createDurationSchema("30d"),
|
||||
AUTH_PROVIDERS: authProvidersSchema,
|
||||
AUTH_COOKIE_PREFIX: z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[a-zA-Z0-9-_]+$/, "AUTH_COOKIE_PREFIX must only contain letters, numbers, hyphens and underscores")
|
||||
.default("homarr"),
|
||||
...(authProviders.includes("oidc")
|
||||
? {
|
||||
AUTH_OIDC_ISSUER: z.string().url(),
|
||||
|
||||
@@ -4,9 +4,11 @@ import { generateSecureRandomToken } from "@homarr/common/server";
|
||||
import type { Database } from "@homarr/db";
|
||||
|
||||
import { getCurrentUserPermissionsAsync } from "./callbacks";
|
||||
import { env } from "./env";
|
||||
|
||||
// Default of authjs
|
||||
export const sessionTokenCookieName = "authjs.session-token";
|
||||
// Prefixed (AUTH_COOKIE_PREFIX, default "homarr") to avoid cookie collisions
|
||||
// with other Auth.js apps on the same host. See https://github.com/homarr-labs/homarr/issues/5773
|
||||
export const sessionTokenCookieName = `${env.AUTH_COOKIE_PREFIX}.session-token`;
|
||||
|
||||
export const expireDateAfter = (seconds: number) => {
|
||||
return new Date(Date.now() + seconds * 1000);
|
||||
|
||||
@@ -20,6 +20,7 @@ interface CreateCronJobOptions {
|
||||
runOnStart?: boolean;
|
||||
preventManualExecution?: boolean;
|
||||
preventCustomInterval?: boolean;
|
||||
hidden?: boolean;
|
||||
expectedMaximumDurationInMillis?: number;
|
||||
beforeStart?: () => MaybePromise<void>;
|
||||
}
|
||||
@@ -121,6 +122,7 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
|
||||
},
|
||||
preventManualExecution: options.preventManualExecution ?? false,
|
||||
preventCustomInterval: options.preventCustomInterval ?? false,
|
||||
hidden: options.hidden ?? false,
|
||||
timezone: creatorOptions.timezone,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -122,15 +122,17 @@ export class JobManager {
|
||||
> {
|
||||
const configurations = await this.db.query.cronJobConfigurations.findMany();
|
||||
|
||||
return [...this.jobGroup.getJobRegistry().entries()].map(([name, job]) => {
|
||||
const config = configurations.find((config) => config.name === name);
|
||||
return {
|
||||
name,
|
||||
cron: config?.cronExpression ?? job.cronExpression,
|
||||
preventManualExecution: job.preventManualExecution,
|
||||
preventCustomInterval: job.preventCustomInterval,
|
||||
isEnabled: config?.isEnabled ?? true,
|
||||
};
|
||||
});
|
||||
return [...this.jobGroup.getJobRegistry().entries()]
|
||||
.filter(([_, job]) => !job.hidden)
|
||||
.map(([name, job]) => {
|
||||
const config = configurations.find((config) => config.name === name);
|
||||
return {
|
||||
name,
|
||||
cron: config?.cronExpression ?? job.cronExpression,
|
||||
preventManualExecution: job.preventManualExecution,
|
||||
preventCustomInterval: job.preventCustomInterval,
|
||||
isEnabled: config?.isEnabled ?? true,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import { sendServerAnalyticsAsync } from "@homarr/analytics";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
@@ -10,13 +8,8 @@ export const analyticsJob = createCronJob("analytics", EVERY_WEEK, {
|
||||
runOnStart: true,
|
||||
preventManualExecution: true,
|
||||
preventCustomInterval: true,
|
||||
hidden: true,
|
||||
}).withCallback(async () => {
|
||||
if (env.NO_EXTERNAL_CONNECTION) return;
|
||||
const analyticSetting = await getServerSettingByKeyAsync(db, "analytics");
|
||||
|
||||
if (!analyticSetting.enableGeneral) {
|
||||
return;
|
||||
}
|
||||
|
||||
await sendServerAnalyticsAsync();
|
||||
});
|
||||
@@ -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"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { FocusEventHandler } from "react";
|
||||
import { startTransition } from "react";
|
||||
import { startTransition, useEffect, useState } from "react";
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Combobox,
|
||||
Flex,
|
||||
@@ -18,7 +21,7 @@ import {
|
||||
useCombobox,
|
||||
} from "@mantine/core";
|
||||
import { useDebouncedValue, useUncontrolled } from "@mantine/hooks";
|
||||
import { IconUpload } from "@tabler/icons-react";
|
||||
import { IconMoodEmpty, IconSearchOff, IconUpload } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
@@ -34,7 +37,6 @@ interface IconPickerProps {
|
||||
error?: string | null;
|
||||
onFocus?: FocusEventHandler;
|
||||
onBlur?: FocusEventHandler;
|
||||
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
withAsterisk?: boolean;
|
||||
@@ -50,29 +52,30 @@ export const IconPicker = ({
|
||||
label,
|
||||
placeholder,
|
||||
}: IconPickerProps) => {
|
||||
const [value, setValue] = useUncontrolled({
|
||||
value: propsValue,
|
||||
onChange,
|
||||
});
|
||||
const [search, setSearch] = useUncontrolled({
|
||||
value,
|
||||
onChange: (value) => {
|
||||
setValue(value);
|
||||
},
|
||||
});
|
||||
const [previewUrl, setPreviewUrl] = useUncontrolled({
|
||||
value: propsValue ?? null,
|
||||
});
|
||||
const [value, setValue] = useUncontrolled({ value: propsValue, onChange });
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
// ponytail: reset the typed query when the committed value changes
|
||||
// externally (form reset, parent re-render) so the input reverts to the URL.
|
||||
useEffect(() => {
|
||||
setQuery("");
|
||||
}, [value]);
|
||||
|
||||
const { data: session } = useSession();
|
||||
|
||||
const tCommon = useScopedI18n("common");
|
||||
const [debouncedQuery] = useDebouncedValue(query, 100);
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(search, 100);
|
||||
// ponytail: only search when the typed query differs from the committed
|
||||
// value. On mount the input shows the existing URL (edit mode), which
|
||||
// would never match the bare filenames in the icon table.
|
||||
const searchText = (debouncedQuery !== (value ?? "") && debouncedQuery) || "";
|
||||
|
||||
// We use not useSuspenseQuery as it would cause an above Suspense boundary to trigger and so searching for something is bad UX.
|
||||
const { data } = clientApi.icon.findIcons.useQuery({
|
||||
searchText: debouncedSearch,
|
||||
});
|
||||
// ponytail: not useSuspenseQuery — an above Suspense boundary would
|
||||
// trigger on every keystroke and the dropdown flash is bad UX.
|
||||
const { data, isLoading, isError, refetch } = clientApi.icon.findIcons.useQuery(
|
||||
{ searchText },
|
||||
{ placeholderData: (prev) => prev },
|
||||
);
|
||||
|
||||
const combobox = useCombobox({
|
||||
onDropdownClose: () => combobox.resetSelectedOption(),
|
||||
@@ -90,25 +93,15 @@ export const IconPicker = ({
|
||||
>
|
||||
<UnstyledButton
|
||||
onClick={() => {
|
||||
const value = item.url;
|
||||
startTransition(() => {
|
||||
setPreviewUrl(value);
|
||||
setSearch(value);
|
||||
setValue(value);
|
||||
setValue(item.url);
|
||||
setQuery("");
|
||||
combobox.closeDropdown();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Indicator label="SVG" disabled={!item.url.endsWith(".svg")} size={16}>
|
||||
<Card
|
||||
p="sm"
|
||||
pos="relative"
|
||||
style={{
|
||||
overflow: "visible",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className={classes.iconCard}
|
||||
>
|
||||
<Card p="sm" pos="relative" style={{ overflow: "visible", cursor: "pointer" }} className={classes.iconCard}>
|
||||
<Box w={25} h={25}>
|
||||
<Image src={item.url} w={25} h={25} />
|
||||
</Box>
|
||||
@@ -130,6 +123,48 @@ export const IconPicker = ({
|
||||
);
|
||||
});
|
||||
|
||||
// ponytail: show the typed query if non-empty, else fall back to the URL.
|
||||
const inputValue = query || value || "";
|
||||
// ponytail: show the existing icon as a left-section preview.
|
||||
const leftSection = shouldShowPreview(value) && <img src={value} alt="" style={{ width: 20, height: 20 }} />;
|
||||
// ponytail: prop override -> real count -> loading text.
|
||||
const placeholderText =
|
||||
placeholder ||
|
||||
(data && tCommon("iconPicker.header", { countIcons: String(data.countIcons) })) ||
|
||||
tCommon("iconPicker.headerLoading");
|
||||
|
||||
const renderDropdown = () => {
|
||||
if (isError) {
|
||||
return (
|
||||
<Stack align="center" gap="xs" p="md">
|
||||
<IconSearchOff size={32} stroke={1.5} />
|
||||
<Text size="sm">{tCommon("iconPicker.error")}</Text>
|
||||
<Button size="xs" variant="default" onClick={() => refetch()}>
|
||||
{tCommon("action.tryAgain")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
if (isLoading) {
|
||||
return Array(15)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<Combobox.Option value={`skeleton-${index}`} key={index} disabled>
|
||||
<Skeleton height={25} visible />
|
||||
</Combobox.Option>
|
||||
));
|
||||
}
|
||||
if (totalOptions > 0) {
|
||||
return <Stack gap={4}>{groups}</Stack>;
|
||||
}
|
||||
return (
|
||||
<Stack align="center" gap="xs" p="md">
|
||||
<IconMoodEmpty size={32} stroke={1.5} />
|
||||
<Text size="sm">{tCommon("iconPicker.noResults", { search: debouncedQuery })}</Text>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Combobox store={combobox} withinPortal>
|
||||
<Combobox.Target>
|
||||
@@ -137,16 +172,11 @@ export const IconPicker = ({
|
||||
<InputBase
|
||||
flex={1}
|
||||
rightSection={<Combobox.Chevron />}
|
||||
leftSection={
|
||||
shouldShowPreview(previewUrl) ? <img src={previewUrl} alt="" style={{ width: 20, height: 20 }} /> : null
|
||||
}
|
||||
value={search}
|
||||
leftSection={leftSection}
|
||||
value={inputValue}
|
||||
onChange={(event) => {
|
||||
combobox.openDropdown();
|
||||
combobox.updateSelectedOptionIndex();
|
||||
setSearch(event.currentTarget.value);
|
||||
setValue(event.currentTarget.value);
|
||||
setPreviewUrl(null);
|
||||
setQuery(event.currentTarget.value);
|
||||
}}
|
||||
onClick={() => combobox.openDropdown()}
|
||||
onFocus={(event) => {
|
||||
@@ -156,25 +186,21 @@ export const IconPicker = ({
|
||||
onBlur={(event) => {
|
||||
onBlur?.(event);
|
||||
combobox.closeDropdown();
|
||||
setPreviewUrl(value);
|
||||
setSearch(value || "");
|
||||
setQuery("");
|
||||
}}
|
||||
rightSectionPointerEvents="none"
|
||||
withAsterisk={withAsterisk}
|
||||
error={error}
|
||||
label={label ?? tCommon("iconPicker.label")}
|
||||
placeholder={placeholder ?? tCommon("iconPicker.header", { countIcons: String(data?.countIcons ?? 0) })}
|
||||
placeholder={placeholderText}
|
||||
/>
|
||||
{session?.user.permissions.includes("media-upload") && (
|
||||
<UploadMedia
|
||||
onSuccess={(medias) => {
|
||||
const first = medias.at(0);
|
||||
if (!first) return;
|
||||
|
||||
startTransition(() => {
|
||||
setValue(first.url);
|
||||
setPreviewUrl(first.url);
|
||||
setSearch(first.url);
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -190,17 +216,7 @@ export const IconPicker = ({
|
||||
|
||||
<Combobox.Dropdown>
|
||||
<Combobox.Options mah={350} style={{ overflowY: "auto" }}>
|
||||
{totalOptions > 0 ? (
|
||||
<Stack gap={4}>{groups}</Stack>
|
||||
) : (
|
||||
Array(15)
|
||||
.fill(0)
|
||||
.map((_, index: number) => (
|
||||
<Combobox.Option value={`skeleton-${index}`} key={index} disabled>
|
||||
<Skeleton height={25} visible />
|
||||
</Combobox.Option>
|
||||
))
|
||||
)}
|
||||
{renderDropdown()}
|
||||
</Combobox.Options>
|
||||
</Combobox.Dropdown>
|
||||
</Combobox>
|
||||
@@ -209,8 +225,8 @@ export const IconPicker = ({
|
||||
|
||||
// This regex is used to prevent loading a preview like en or /en which would trigger a language change
|
||||
// See https://github.com/homarr-labs/homarr/issues/3070
|
||||
const localizationPathRegex = new RegExp(`^/?(${supportedLanguages.join("|")})(/.*)?$`, "i" /* ignore casing */);
|
||||
const shouldShowPreview = (value: string | null): value is string => {
|
||||
const localizationPathRegex = new RegExp(`^/?(${supportedLanguages.join("|")})(/.*)?$`, "i");
|
||||
const shouldShowPreview = (value: string | null | undefined): value is string => {
|
||||
if (!value) return false;
|
||||
return !localizationPathRegex.test(value);
|
||||
};
|
||||
@@ -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(),
|
||||
|
||||
@@ -132,8 +132,9 @@ const happyResponder = (method: string) => {
|
||||
aggregations: emptyAggregations(),
|
||||
start: 0,
|
||||
end: 1,
|
||||
legend: [],
|
||||
data: [[1000, 8_000_000_000]],
|
||||
// The "memory" graph reports available (free) memory: legend is ["time", "available"].
|
||||
legend: ["time", "available"],
|
||||
data: [[1000, 6_000_000_000]],
|
||||
},
|
||||
{
|
||||
name: "cputemp",
|
||||
@@ -194,15 +195,17 @@ describe("TrueNasIntegration", () => {
|
||||
version: "TrueNAS-SCALE-24.10",
|
||||
cpuModelName: "Intel Xeon",
|
||||
cpuTemp: 45,
|
||||
memAvailableInBytes: 16_000_000_000,
|
||||
memUsedInBytes: 8_000_000_000,
|
||||
// physmem (16 GB) minus the reported available 6 GB leaves 10 GB used.
|
||||
memAvailableInBytes: 6_000_000_000,
|
||||
memUsedInBytes: 10_000_000_000,
|
||||
uptime: 3600,
|
||||
rebootRequired: false,
|
||||
availablePkgUpdates: 0,
|
||||
loadAverage: null,
|
||||
gpu: [],
|
||||
network: { up: 20_000, down: 10_000 },
|
||||
fileSystem: [{ deviceName: "tank", available: "1000", used: "500", percentage: 50 }],
|
||||
// `available` is the free space left on the pool (free = 500), not its total size.
|
||||
fileSystem: [{ deviceName: "tank", available: "500", used: "500", percentage: 50 }],
|
||||
smart: [{ deviceName: "tank", healthy: true, overallStatus: "ONLINE", temperature: null }],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,14 +37,19 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
||||
const upload = this.extractNetworkTrafficData(netdata, 2); // Index 2 is "sent"
|
||||
const download = this.extractNetworkTrafficData(netdata, 1); // Index 1 is "received"
|
||||
|
||||
// The "memory" reporting graph reports available (free) memory, its legend is ["time", "available"].
|
||||
// Used memory is therefore the physical total minus what is still available (clamped against timing skew).
|
||||
const memAvailableInBytes = memoryData[1] ?? 0; // Index 0 is the UNIX timestamp, Index 1 is available bytes
|
||||
const memUsedInBytes = Math.max(systemInformation.physmem - memAvailableInBytes, 0);
|
||||
|
||||
return {
|
||||
cpuUtilization: cpuData.reduce((acc, item) => acc + (item > 100 ? 0 : item), 0) / cpuData.length,
|
||||
cpuTemp: Math.max(...cpuTempData.filter((_item, index) => index > 0)),
|
||||
memAvailableInBytes: systemInformation.physmem,
|
||||
memUsedInBytes: memoryData[1] ?? 0, // Index 0 is UNIX timestamp, Index 1 is free space in bytes
|
||||
memAvailableInBytes,
|
||||
memUsedInBytes,
|
||||
fileSystem: datasets.map((dataset) => ({
|
||||
deviceName: dataset.name,
|
||||
available: `${dataset.size}`, // TODO: can we use number instead of string here?
|
||||
available: `${dataset.free}`, // free space left on the pool
|
||||
used: `${dataset.allocated}`,
|
||||
percentage: (dataset.allocated / dataset.size) * 100,
|
||||
})),
|
||||
|
||||
@@ -59,8 +59,8 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
|
||||
cpuTemp: undefined, // Not implemented, see https://github.com/unraid/api/issues/1597
|
||||
fileSystem: systemInfo.array.disks.map((disk) => ({
|
||||
deviceName: disk.name,
|
||||
used: humanFileSize(disk.fsUsed * 1024), // API is in KiB (kibibytes), covert to bytes
|
||||
available: `${disk.size * 1024}`, // API is in KiB (kibibytes), covert to bytes
|
||||
used: humanFileSize(disk.fsUsed * 1024), // API is in KiB (kibibytes), convert to bytes
|
||||
available: `${(disk.size - disk.fsUsed) * 1024}`, // free space left on the disk, API is in KiB (kibibytes)
|
||||
percentage: (disk.fsUsed / disk.size) * 100, // The units are the same, therefore the actual unit is irrelevant
|
||||
})),
|
||||
smart: systemInfo.array.disks.map((disk) => ({
|
||||
|
||||
@@ -15,6 +15,7 @@ export {
|
||||
createGetSetChannel,
|
||||
createQueryCacheChannel,
|
||||
getAllQueryCacheAsync,
|
||||
invalidateIntegrationCacheAsync,
|
||||
} from "./lib/channel";
|
||||
|
||||
export { createIntegrationHistoryChannel } from "./lib/channels/history-channel";
|
||||
|
||||
@@ -261,6 +261,36 @@ export const createIntegrationOptionsChannel = <TData>(
|
||||
return createChannelWithLatestAndEvents<TData>(channelName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invalidates all cached data for a given integration by deleting every Redis key
|
||||
* that starts with `integration:${integrationId}:` as well as the integration's
|
||||
* session store entry. This ensures that stale data from a misconfigured or
|
||||
* updated integration is never served from cache.
|
||||
*/
|
||||
export const invalidateIntegrationCacheAsync = async (integrationId: string): Promise<void> => {
|
||||
const client = getSetClient as typeof getSetClient | null;
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const patterns = [`integration:${integrationId}:*`, `session-store:${integrationId}`];
|
||||
let deletedCount = 0;
|
||||
for (const pattern of patterns) {
|
||||
let cursor = "0";
|
||||
do {
|
||||
const [nextCursor, keys] = await client.scan(cursor, "MATCH", pattern, "COUNT", 200);
|
||||
cursor = nextCursor;
|
||||
if (keys.length > 0) {
|
||||
await client.del(...keys);
|
||||
deletedCount += keys.length;
|
||||
}
|
||||
} while (cursor !== "0");
|
||||
}
|
||||
if (deletedCount > 0) {
|
||||
logger.info("Invalidated integration cache", { integrationId, deletedKeys: deletedCount });
|
||||
}
|
||||
};
|
||||
|
||||
export const createWidgetOptionsChannel = <TData>(
|
||||
widgetKind: WidgetKind,
|
||||
queryKey: string,
|
||||
|
||||
@@ -95,8 +95,8 @@ const timePeriodConfig: Record<string, { type: string; perPage: number; cacheSec
|
||||
"1h": { type: "1m", perPage: 60, cacheSeconds: 60 },
|
||||
"12h": { type: "10m", perPage: 72, cacheSeconds: 300 },
|
||||
"24h": { type: "20m", perPage: 72, cacheSeconds: 600 },
|
||||
"1w": { type: "120m", perPage: 84, cacheSeconds: 1800 },
|
||||
"30d": { type: "480m", perPage: 90, cacheSeconds: 3600 },
|
||||
"1w": { type: "1440m", perPage: 9, cacheSeconds: 1800 },
|
||||
"30d": { type: "1440m", perPage: 32, cacheSeconds: 3600 },
|
||||
};
|
||||
|
||||
export interface BeszelStatsData {
|
||||
|
||||
@@ -16,9 +16,7 @@ export type ServerSettingsRecord = Record<(typeof defaultServerSettingsKeys)[num
|
||||
export const defaultServerSettings = {
|
||||
analytics: {
|
||||
enableGeneral: true,
|
||||
enableWidgetData: false,
|
||||
enableIntegrationData: false,
|
||||
enableUserData: false,
|
||||
instanceId: null as string | null,
|
||||
},
|
||||
crawlingAndIndexing: {
|
||||
noIndex: true,
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "应用",
|
||||
"description": "将应用嵌入到面板中。",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole 摘要",
|
||||
"description": "显示 DNS Hole 的摘要",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "发送匿名分析",
|
||||
"text": "Homarr 将使用开源软件 Umami 发送匿名分析数据。启用后,我们会收集:Homarr 版本、数据库类型(MySQL/PostgreSQL/SQLite)、Docker/Kubernetes 状态、已启用的身份验证方法以及各项功能的数量。所有数据均为匿名且汇总收集——绝不收集任何个人信息、URL、名称或可识别数据。这有助于我们的开源团队了解使用模式并确定开发优先级。"
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "组件数据",
|
||||
"text": "发送您已配置的组件(及其数量)。不包括URL、名称或任何其他数据。"
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "集成数据",
|
||||
"text": "发送您已配置的集成(及其数量)。不包括URL、名称或任何其他数据。"
|
||||
},
|
||||
"usersData": {
|
||||
"title": "用户数据",
|
||||
"text": "发送用户数量以及您是否激活了SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "已更新 {when}",
|
||||
"search": "搜索 {count} 容器",
|
||||
"selected": "{totalCount} 容器的 {selectCount}",
|
||||
"footer": "总计 {count} 容器"
|
||||
"footer": "总计 {count} 容器",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "crwdns5641:0crwdne5641:0",
|
||||
"loading": "crwdns5643:0crwdne5643:0",
|
||||
"placeholder": "crwdns5645:0crwdne5645:0",
|
||||
"noResults": "crwdns5647:0crwdne5647:0",
|
||||
"loadError": "crwdns5649:0crwdne5649:0"
|
||||
},
|
||||
"app": {
|
||||
"name": "crwdns732:0crwdne732:0",
|
||||
"description": "crwdns734:0crwdne734:0",
|
||||
@@ -1739,6 +1746,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",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "crwdns4811:0crwdne4811:0",
|
||||
"error": {
|
||||
"internalServerError": "crwdns5651:0crwdne5651:0"
|
||||
},
|
||||
"column": {
|
||||
"system": "crwdns4813:0crwdne4813:0",
|
||||
"cpu": "crwdns4815:0crwdne4815:0",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": "crwdns4863:0crwdne4863:0"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": "crwdns5653:0crwdne5653:0"
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "crwdns4865:0crwdne4865:0",
|
||||
"memory": "crwdns4867:0crwdne4867:0",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "crwdns4895:0crwdne4895:0",
|
||||
"error": {
|
||||
"internalServerError": "crwdns5655:0crwdne5655:0"
|
||||
},
|
||||
"history": "crwdns4897:0crwdne4897:0",
|
||||
"resolved": "crwdns4899:0crwdne4899:0",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "crwdns4905:0crwdne4905:0",
|
||||
"description": "crwdns4907:0crwdne4907:0",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "crwdns5657:0crwdne5657:0",
|
||||
"description": "crwdns5659:0crwdne5659:0"
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": "crwdns4909:0crwdne4909:0"
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "crwdns4927:0crwdne4927:0",
|
||||
"error": {
|
||||
"internalServerError": "crwdns5661:0crwdne5661:0",
|
||||
"liveConnectionFailed": "crwdns5663:0crwdne5663:0",
|
||||
"switchToHistorical": "crwdns5665:0crwdne5665:0",
|
||||
"systemNotFound": "crwdns5667:0crwdne5667:0"
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "crwdns4929:0crwdne4929:0",
|
||||
@@ -4378,20 +4413,8 @@
|
||||
"analytics": {
|
||||
"title": "crwdns1764:0crwdne1764:0",
|
||||
"general": {
|
||||
"title": "crwdns1766:0crwdne1766:0",
|
||||
"text": "crwdns3878:0crwdne3878:0"
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "crwdns1770:0crwdne1770:0",
|
||||
"text": "crwdns1772:0crwdne1772:0"
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "crwdns1774:0crwdne1774:0",
|
||||
"text": "crwdns1776:0crwdne1776:0"
|
||||
},
|
||||
"usersData": {
|
||||
"title": "crwdns1778:0crwdne1778:0",
|
||||
"text": "crwdns1780:0crwdne1780:0"
|
||||
"title": "crwdns5673:0crwdne5673:0",
|
||||
"text": "crwdns5675:0crwdne5675:0"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "crwdns1894:0{when}crwdne1894:0",
|
||||
"search": "crwdns3432:0{count}crwdne3432:0",
|
||||
"selected": "crwdns1898:0{selectCount}crwdnd1898:0{totalCount}crwdne1898:0",
|
||||
"footer": "crwdns3434:0{count}crwdne3434:0"
|
||||
"footer": "crwdns3434:0{count}crwdne3434:0",
|
||||
"totalCpu": "crwdns5669:0{cpu}crwdne5669:0",
|
||||
"totalMemory": "crwdns5671:0{memory}crwdne5671:0"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "Zobrazí shrnutí vaší DNS Hole",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Uživatelská data",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "App",
|
||||
"description": "Indlejrer en app i tavlen.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole Oversigt",
|
||||
"description": "Viser oversigten over dit DNS-hul",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Indsend anonyme analysedata",
|
||||
"text": "Homarr vil sende anonymiserede analyser ved hjælp af open source-softwaren Umami. Den indsamler aldrig personlige oplysninger og er derfor fuldt ud GDPR & CCPA kompatibel. Vi opfordrer dig til at aktivere analyser, fordi det hjælper vores open source-team til at identificere problemer og prioritere vores backlog."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget data",
|
||||
"text": "Send hvilke widgets (og deres antal) du har konfigureret. Indeholder ikke URL'er, navne eller andre data."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integrationsdata",
|
||||
"text": "Send hvilke integrationer (og deres antal) du har konfigureret. Indeholder ikke URL'er, navne eller andre data."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Brugerdata",
|
||||
"text": "Send mængden af brugere, og om du har aktiveret SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Opdateret {when}",
|
||||
"search": "Søg {count} containere",
|
||||
"selected": "{selectCount} af {totalCount} containere valgt",
|
||||
"footer": "Total {count} containere"
|
||||
"footer": "Total {count} containere",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "Bettet eine App in das Board ein.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole Zusammenfassung",
|
||||
"description": "Zeigt die Zusammenfassung deines DNS Holes an",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Anonymisierte Nutzungsdaten senden",
|
||||
"text": "Homarr schickt anonymisierte Nutzungsdaten mit der Open-Source-Software Umami. Es sammelt zu keinem Zeitpunkt personenbezogene Daten und ist daher vollständig DPR & CCPA konform. Wir empfehlen diese Nutzungsdaten zu aktivieren, da es unserem Open-Source-Team hilft Probleme zu identifizieren und unserem Backlog ermöglicht Priorität einzuräumen."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget Daten",
|
||||
"text": "Senden Sie die Widgets (und deren Anzahl) die Sie konfiguriert haben. Enthält keine URLs, Namen oder andere Daten."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integrationsdaten",
|
||||
"text": "Senden Sie die Intregrationen (und deren Anzahl) die Sie konfiguriert haben. Enthält keine URLs, Namen oder andere Daten."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Benutzerdaten",
|
||||
"text": "Senden Sie die Anzahl der Benutzer und ob Sie SSO aktiviert haben"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Aktualisiert {when}",
|
||||
"search": "{count} Container durchsuchen",
|
||||
"selected": "{selectCount} von {totalCount} ausgewählten Containern",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "Verbinde zuerst eine Integration",
|
||||
"loading": "Wird geladen...",
|
||||
"placeholder": "Wählen Sie eine Option",
|
||||
"noResults": "Keine Ergebnisse gefunden",
|
||||
"loadError": "Fehler beim Laden der Optionen"
|
||||
},
|
||||
"app": {
|
||||
"name": "App",
|
||||
"description": "Bettet eine App in das Board ein.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "ArchiveTeam Warrior",
|
||||
"description": "ArchiveTeam Warrior Status, Projekte und aktive Archivaufträge überwachen.",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": "Broadcast Nachrichten anzeigen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole Zusammenfassung",
|
||||
"description": "Zeigt die Zusammenfassung deines DNS Holes an",
|
||||
@@ -3323,58 +3339,58 @@
|
||||
},
|
||||
"uptimeKuma": {
|
||||
"name": "Uptime Kuma",
|
||||
"description": "",
|
||||
"description": "Laufzeit Statistiken von der Uptime Kuma Statusseite überwachen",
|
||||
"option": {
|
||||
"showAverageUptime": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Durchschnittliche Laufzeit anzeigen",
|
||||
"description": "Zeige den durchschnittlichen Prozentsatz der 24 Stunden Laufzeit an"
|
||||
},
|
||||
"showUptimeRing": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Uptime Ring anzeigen",
|
||||
"description": "Einen Fortschrittsring neben dem durchschnittlichen Laufzeit Wert anzeigen"
|
||||
},
|
||||
"showTotalMonitors": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Alle Monitore anzeigen",
|
||||
"description": "Zeigt die Gesamtzahl der konfigurierten Monitore an"
|
||||
},
|
||||
"showUpCount": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Aktive anzeigen",
|
||||
"description": "Zeigt an, wie viele Monitore derzeit aktiv sind"
|
||||
},
|
||||
"showDownCount": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Inaktive anzeigen",
|
||||
"description": "Zeigt an, wie viele Monitore derzeit nicht verfügbar sind"
|
||||
},
|
||||
"showPausedCount": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"label": "Pausierte anzeigen",
|
||||
"description": "Zeigt an, wie viele Monitore derzeit pausiert sind"
|
||||
}
|
||||
},
|
||||
"averageUptime": "",
|
||||
"averageUptime": "Durchschnittliche Laufzeit (24h)",
|
||||
"stats": {
|
||||
"totalMonitors": "",
|
||||
"upCount": "",
|
||||
"downCount": "",
|
||||
"pausedCount": ""
|
||||
"totalMonitors": "Alle Monitore",
|
||||
"upCount": "Aktiv",
|
||||
"downCount": "Inaktiv",
|
||||
"pausedCount": "Pausiert"
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
}
|
||||
},
|
||||
"audioStats": {
|
||||
"name": "",
|
||||
"name": "Audiostatistiken",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showArtists": {
|
||||
"label": "",
|
||||
"label": "Künstler anzeigen",
|
||||
"description": ""
|
||||
},
|
||||
"showAlbums": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"description": "Zeigt die Gesamtzahl der Alben in deiner Musikbibliothek an (Navidrome)"
|
||||
},
|
||||
"showSongs": {
|
||||
"label": "",
|
||||
"label": "Songs anzeigen",
|
||||
"description": ""
|
||||
},
|
||||
"showNowPlaying": {
|
||||
@@ -3403,10 +3419,10 @@
|
||||
},
|
||||
"showListeningTime": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
"description": "Zeigt deine gesamte Hörzeit (Audiobookshelf)"
|
||||
},
|
||||
"showActiveSessions": {
|
||||
"label": "",
|
||||
"label": "Aktive Streams anzeigen",
|
||||
"description": ""
|
||||
},
|
||||
"compactMode": {
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3587,13 +3606,13 @@
|
||||
"label": ""
|
||||
},
|
||||
"showMemory": {
|
||||
"label": ""
|
||||
"label": "Arbeitsspeicher anzeigen"
|
||||
},
|
||||
"showDisk": {
|
||||
"label": ""
|
||||
},
|
||||
"showGpu": {
|
||||
"label": ""
|
||||
"label": "GPU anzeigen"
|
||||
},
|
||||
"showLoadAvg": {
|
||||
"label": ""
|
||||
@@ -3602,13 +3621,13 @@
|
||||
"label": ""
|
||||
},
|
||||
"showTemp": {
|
||||
"label": ""
|
||||
"label": "Temperatur anzeigen"
|
||||
},
|
||||
"showBattery": {
|
||||
"label": ""
|
||||
},
|
||||
"showServices": {
|
||||
"label": ""
|
||||
"label": "Dienste anzeigen"
|
||||
},
|
||||
"showUptime": {
|
||||
"label": ""
|
||||
@@ -3617,18 +3636,21 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"cpu": "CPU",
|
||||
"memory": "Arbeitsspeicher",
|
||||
"disk": "",
|
||||
"gpu": "",
|
||||
"gpu": "GPU",
|
||||
"loadAvg": "",
|
||||
"net": "",
|
||||
"temp": "",
|
||||
"battery": "",
|
||||
"services": "",
|
||||
"services": "Dienste",
|
||||
"uptime": "",
|
||||
"agent": ""
|
||||
"agent": "Agent"
|
||||
}
|
||||
},
|
||||
"beszelAlerts": {
|
||||
@@ -3643,17 +3665,24 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"history": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "Verlauf",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
"triggered": "",
|
||||
"ok": ""
|
||||
"ok": "Ok"
|
||||
}
|
||||
},
|
||||
"beszelSystemStats": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3682,21 +3711,27 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"selectSystem": "System auswählen",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
"title": "CPU-Auslastung",
|
||||
"subtitle": "",
|
||||
"series": ""
|
||||
"series": "CPU"
|
||||
},
|
||||
"memory": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"series": "",
|
||||
"series": "Belegt",
|
||||
"cache": ""
|
||||
},
|
||||
"disk": {
|
||||
"title": "",
|
||||
"title": "Festplattenbelegung",
|
||||
"subtitle": "",
|
||||
"series": ""
|
||||
},
|
||||
@@ -4247,12 +4282,12 @@
|
||||
"message": ""
|
||||
},
|
||||
"status": {
|
||||
"completed": "",
|
||||
"notStarted": ""
|
||||
"completed": "Abgeschlossen",
|
||||
"notStarted": "Nicht gestartet"
|
||||
},
|
||||
"name": {
|
||||
"management": "",
|
||||
"dashboard": ""
|
||||
"dashboard": "Dashboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Anonymisierte Nutzungsdaten senden",
|
||||
"text": "Homarr sendet anonymisierte Analysen mit der Open-Source-Software Umami. Wenn aktiviert, sammeln wir die Homarr-Version, den Datenbanktyp (MySQL/PostgreSQL/SQLite), den Docker/Kubernetes Status, die Authentifizierungsmethoden und die Anzahl der einzelnen Features. Alle Daten sind anonym und aggregiert - es werden keine persönlichen Informationen, URLs, Namen oder identifizierbare Daten gesammelt. Dies hilft unserem Open-Source-Team, Nutzungsmuster zu verstehen und der Entwicklung Priorität einzuräumen."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget Daten",
|
||||
"text": "Senden Sie die Widgets (und deren Anzahl) die Sie konfiguriert haben. Enthält keine URLs, Namen oder andere Daten."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integrationsdaten",
|
||||
"text": "Senden Sie die Intregrationen (und deren Anzahl) die Sie konfiguriert haben. Enthält keine URLs, Namen oder andere Daten."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Benutzerdaten",
|
||||
"text": "Senden Sie die Anzahl der Benutzer und ob Sie SSO aktiviert haben"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4669,15 +4692,15 @@
|
||||
"subtitle": "",
|
||||
"description": "",
|
||||
"noApiKey": "",
|
||||
"createApiKeyButton": "",
|
||||
"createApiKeyButton": "API Key erstellen",
|
||||
"endpoint": {
|
||||
"title": ""
|
||||
},
|
||||
"authentication": {
|
||||
"title": "",
|
||||
"title": "Authentifizierung",
|
||||
"intro": "",
|
||||
"apiKey": {
|
||||
"title": "",
|
||||
"title": "API Key (manuell)",
|
||||
"description": ""
|
||||
},
|
||||
"oauth": {
|
||||
@@ -4690,12 +4713,12 @@
|
||||
"clientConfig": {
|
||||
"title": "",
|
||||
"streamableHttp": {
|
||||
"recommended": "",
|
||||
"recommended": "Empfohlen",
|
||||
"title": "",
|
||||
"worksWith": "",
|
||||
"addToConfig": "",
|
||||
"cursor": "",
|
||||
"claudeDesktop": "",
|
||||
"claudeDesktop": "{name}: {file}",
|
||||
"claudeCode": "",
|
||||
"vsCodeCopilot": "",
|
||||
"replaceHint": ""
|
||||
@@ -4706,16 +4729,16 @@
|
||||
}
|
||||
},
|
||||
"testConnection": {
|
||||
"title": "",
|
||||
"title": "Verbindung testen",
|
||||
"description": "",
|
||||
"hint": ""
|
||||
},
|
||||
"availableTools": {
|
||||
"title": "",
|
||||
"title": "Verfügbare Tools",
|
||||
"description": "",
|
||||
"columnType": "",
|
||||
"columnName": "",
|
||||
"columnDescription": ""
|
||||
"columnType": "Typ",
|
||||
"columnName": "Tool",
|
||||
"columnDescription": "Beschreibung"
|
||||
},
|
||||
"troubleshooting": {
|
||||
"title": "",
|
||||
@@ -4744,65 +4767,65 @@
|
||||
"oneClick": {
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"vsCode": "",
|
||||
"cursor": "",
|
||||
"copyUrl": "",
|
||||
"copied": "",
|
||||
"copyHint": "",
|
||||
"manualConfig": ""
|
||||
"vsCode": "In VS Code installieren",
|
||||
"cursor": "In Cursor installieren",
|
||||
"copyUrl": "MCP URL kopieren",
|
||||
"copied": "Kopiert!",
|
||||
"copyHint": "Für ChatGPT, Claude Code oder weitere MCP-Client",
|
||||
"manualConfig": "Manuelle Konfiguration"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"title": "",
|
||||
"metaTitle": "",
|
||||
"title": "Komplette Datenbanksicherung",
|
||||
"metaTitle": "Sichern & Wiederherstellen",
|
||||
"export": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"button": "",
|
||||
"error": "",
|
||||
"title": "Datenbank exportieren",
|
||||
"description": "Laden Sie eine vollständige Sicherung Ihrer SQLite-Datenbank herunter, inklusive aller Einstellungen, Benutzer, Boards und verschlüsselten Integrationsgeheimnissen. Das Backup wird als ZIP-Datei mit dem Verschlüsselungsschlüssel und den Metadaten gebündelt.",
|
||||
"button": "Sicherung herunterladen",
|
||||
"error": "Export fehlgeschlagen",
|
||||
"confirm": {
|
||||
"title": "",
|
||||
"warningTitle": "",
|
||||
"warningBody": "",
|
||||
"typePrompt": "",
|
||||
"submit": "",
|
||||
"cancel": ""
|
||||
"warningTitle": "Dieser Export enthält Ihre gesamte Datenbank.",
|
||||
"warningBody": "Das Backup umfasst alle Benutzer, Integrationsgeheimnisse (verschlüsselt), API-Keys, Board-Konfigurationen und App-Daten. Speichern Sie die exportierte Datei sicher ab und teilen Sie diese nicht.",
|
||||
"typePrompt": "Geben Sie \"{phrase}\" ein, um fortzufahren",
|
||||
"submit": "Datenbank exportieren",
|
||||
"cancel": "Abbrechen"
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"title": "",
|
||||
"title": "Datenbank wiederherstellen",
|
||||
"description": "",
|
||||
"warning": "",
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Ziehen Sie Ihre Backup-ZIP hierher oder klicken Sie zum Durchsuchen",
|
||||
"description": "Nur .zip Dateien aus einer Homarr SQLite-Sicherung werden akzeptiert"
|
||||
},
|
||||
"changeFile": "",
|
||||
"button": "",
|
||||
"changeFile": "Datei ändern",
|
||||
"button": "Datenbank wiederherstellen",
|
||||
"success": {
|
||||
"title": "",
|
||||
"message": ""
|
||||
"title": "Wiederherstellung abgeschlossen",
|
||||
"message": "Datenbank wurde erfolgreich wiederhergestellt. Die Anwendung wird neu gestartet..."
|
||||
},
|
||||
"error": ""
|
||||
"error": "Importieren fehlgeschlagen"
|
||||
},
|
||||
"preview": {
|
||||
"title": "",
|
||||
"exported": "",
|
||||
"loading": "",
|
||||
"loading": "Analysiere Backup-Datei...",
|
||||
"errorTitle": "",
|
||||
"entity": "",
|
||||
"count": "",
|
||||
"boards": "",
|
||||
"boards": "Boards",
|
||||
"entities": {
|
||||
"board": "",
|
||||
"user": "",
|
||||
"app": "",
|
||||
"integration": "",
|
||||
"item": "",
|
||||
"media": "",
|
||||
"search_engine": ""
|
||||
"board": "Boards",
|
||||
"user": "Benutzer",
|
||||
"app": "Apps",
|
||||
"integration": "Integrationen",
|
||||
"item": "Widgets",
|
||||
"media": "Medien",
|
||||
"search_engine": "Suchmaschinen"
|
||||
},
|
||||
"analyzeError": "",
|
||||
"invalidMissingMetadata": "",
|
||||
@@ -4810,11 +4833,11 @@
|
||||
},
|
||||
"restore": {
|
||||
"dropzone": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Backup-ZIP hierher ziehen",
|
||||
"description": "Akzeptiert .zip-Dateien von einem Homarr SQLite-Backup (max. 256 MB)"
|
||||
},
|
||||
"analyzing": "",
|
||||
"changeFile": "",
|
||||
"analyzing": "Analysiere Backup-Datei...",
|
||||
"changeFile": "Datei ändern",
|
||||
"continueToRestore": "",
|
||||
"tryAgain": "",
|
||||
"analysisError": "",
|
||||
@@ -4866,7 +4889,7 @@
|
||||
"message": ""
|
||||
},
|
||||
"failed": {
|
||||
"title": ""
|
||||
"title": "Wiederherstellung fehlgeschlagen"
|
||||
},
|
||||
"invalidMissingMetadata": "",
|
||||
"invalidMissingDb": "",
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Aktualisiert {when}",
|
||||
"search": "{count} Container durchsuchen",
|
||||
"selected": "{selectCount} von {totalCount} ausgewählten Containern",
|
||||
"footer": "Insgesamt {count} Container"
|
||||
"footer": "Insgesamt {count} Container",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -4952,15 +4977,15 @@
|
||||
"label": "Ports"
|
||||
},
|
||||
"host": {
|
||||
"label": ""
|
||||
"label": "Host"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"title": "Aktionen",
|
||||
"logs": {
|
||||
"label": "",
|
||||
"label": "Logs anzeigen",
|
||||
"page": {
|
||||
"title": ""
|
||||
"title": "{name} Logs"
|
||||
}
|
||||
},
|
||||
"start": {
|
||||
@@ -5355,12 +5380,12 @@
|
||||
}
|
||||
},
|
||||
"custom-widgets": {
|
||||
"label": "",
|
||||
"label": "Benutzerdefinierte Widgets",
|
||||
"new": {
|
||||
"label": ""
|
||||
"label": "Neu"
|
||||
},
|
||||
"edit": {
|
||||
"label": "",
|
||||
"label": "Bearbeiten",
|
||||
"flow": {
|
||||
"label": ""
|
||||
}
|
||||
@@ -5389,7 +5414,7 @@
|
||||
"docker": {
|
||||
"label": "Docker",
|
||||
"logs": {
|
||||
"label": ""
|
||||
"label": "Logs"
|
||||
}
|
||||
},
|
||||
"kubernetes": {
|
||||
@@ -5541,26 +5566,26 @@
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
"title": "",
|
||||
"title": "Einstellungen",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": "",
|
||||
"backHint": "",
|
||||
"backAction": ""
|
||||
"backAction": "Zurück"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"error": {
|
||||
"message": ""
|
||||
"message": "Einstellung konnte nicht aktualisiert werden"
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
"preferences": {
|
||||
"label": ""
|
||||
"label": "Einstellungen öffnen"
|
||||
},
|
||||
"boolean": {
|
||||
"enabled": "",
|
||||
"disabled": ""
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert"
|
||||
},
|
||||
"board": {
|
||||
"none": "",
|
||||
@@ -5571,34 +5596,34 @@
|
||||
"unavailable": ""
|
||||
},
|
||||
"colorScheme": {
|
||||
"label": "",
|
||||
"label": "Farbschema",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "Farbschema auswählen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"locale": {
|
||||
"label": "",
|
||||
"label": "Sprache",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "Bevorzugte Sprache auswählen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultSearchEngineId": {
|
||||
"label": "",
|
||||
"label": "Standard Suchmaschine",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "Standard Suchmaschine auswählen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"openSearchInNewTab": {
|
||||
"label": "",
|
||||
"label": "Suchergebnisse in neuem Tab öffnen",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "Suchergebnisse in neuem Tab öffnen"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5611,10 +5636,10 @@
|
||||
}
|
||||
},
|
||||
"firstDayOfWeek": {
|
||||
"label": "",
|
||||
"label": "Erster Tag der Woche",
|
||||
"children": {
|
||||
"detail": {
|
||||
"title": ""
|
||||
"title": "Ersten Tag der Woche auswählen"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5643,23 +5668,23 @@
|
||||
}
|
||||
},
|
||||
"fullPreferencesPage": {
|
||||
"label": ""
|
||||
"label": "Alle Einstellungen öffnen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"media": {
|
||||
"help": "",
|
||||
"help": "Suche nach Filmen oder Serien zum Abfragen",
|
||||
"requestMovie": "Einen Film anfordern",
|
||||
"requestSeries": "Eine Serie anfordern",
|
||||
"openIn": "In {kind} öffnen",
|
||||
"group": {
|
||||
"title": ""
|
||||
"title": "Filme und Serien"
|
||||
},
|
||||
"action": {
|
||||
"search": {
|
||||
"label": "",
|
||||
"label": "Suche nach Filmen oder Serien",
|
||||
"description": "",
|
||||
"disabled": {
|
||||
"noIntegration": "",
|
||||
@@ -6089,23 +6114,23 @@
|
||||
"description": ""
|
||||
},
|
||||
"appsCreate": {
|
||||
"title": "",
|
||||
"title": "App hinzufügen",
|
||||
"description": ""
|
||||
},
|
||||
"integrationsList": {
|
||||
"title": "",
|
||||
"title": "Ihre Integrationen",
|
||||
"description": ""
|
||||
},
|
||||
"integrationsCreate": {
|
||||
"title": "",
|
||||
"title": "Integration hinzufügen",
|
||||
"description": ""
|
||||
},
|
||||
"usersList": {
|
||||
"title": "",
|
||||
"title": "Ihre Benutzer",
|
||||
"description": ""
|
||||
},
|
||||
"usersCreate": {
|
||||
"title": "",
|
||||
"title": "Benutzer erstellen",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
@@ -6115,7 +6140,7 @@
|
||||
"description": ""
|
||||
},
|
||||
"editMode": {
|
||||
"title": "",
|
||||
"title": "Bearbeitungsmodus",
|
||||
"description": ""
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Εφαρμογή",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -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": {
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "App",
|
||||
"description": "Embeds an app into the board.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole Summary",
|
||||
"description": "Displays the summary of your DNS Hole",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -5704,10 +5729,10 @@
|
||||
"option": {
|
||||
"google": {
|
||||
"name": "",
|
||||
"description": ""
|
||||
"description": "Search the web with Google"
|
||||
},
|
||||
"bing": {
|
||||
"name": "",
|
||||
"name": "Bing",
|
||||
"description": "Search the web with Bing"
|
||||
},
|
||||
"duckduckgo": {
|
||||
@@ -6089,46 +6114,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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1412,7 +1412,10 @@
|
||||
"here": "here",
|
||||
"iconPicker": {
|
||||
"label": "Icon URL",
|
||||
"header": "Type name or objects to filter for icons... Homarr will search through {countIcons} icons for you."
|
||||
"header": "Type name or objects to filter for icons... Homarr will search through {countIcons} icons for you.",
|
||||
"headerLoading": "Type to search icons…",
|
||||
"noResults": "No icons match \"{search}\".",
|
||||
"error": "Couldn't load icons."
|
||||
},
|
||||
"colorScheme": {
|
||||
"options": {
|
||||
@@ -4413,20 +4416,8 @@
|
||||
"analytics": {
|
||||
"title": "Analytics",
|
||||
"general": {
|
||||
"title": "Send anonymous analytics",
|
||||
"text": "Homarr will send anonymized analytics using the open source software Umami. When enabled, we collect: Homarr version, database type (MySQL/PostgreSQL/SQLite), Docker/Kubernetes status, authentication methods enabled, and the number of each feature. All data is anonymous and aggregated - no personal information, URLs, names, or identifiable data is ever collected. This helps our open source team understand usage patterns and prioritize development."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget data",
|
||||
"text": "Send which widgets (and their quantity) you have configured. Does not include URLs, names or any other data."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integration data",
|
||||
"text": "Send which integrations (and their quantity) you have configured. Does not include URLs, names or any other data."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Users data",
|
||||
"text": "Send the amount of users and whether you've activated SSO"
|
||||
"title": "Help us improve Homarr",
|
||||
"text": "This helps us figure out what people actually use so we know where to spend our time. We see stuff like how many widgets or integrations you have, your Homarr version, and what database you're on. That's it — no personal info, no URLs, no names, nothing that could identify you or your setup."
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4954,7 +4945,9 @@
|
||||
"updated": "Updated {when}",
|
||||
"search": "Seach {count} containers",
|
||||
"selected": "{selectCount} of {totalCount} containers selected",
|
||||
"footer": "Total {count} containers"
|
||||
"footer": "Total {count} containers",
|
||||
"totalCpu": "Total CPU: {cpu}%",
|
||||
"totalMemory": "Total Mem: {memory}"
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Aplicación",
|
||||
"description": "Insertar una aplicación en el tablero.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "Resumen del agujero DNS",
|
||||
"description": "Muestra el resumen de tu agujero DNS",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Enviar estadísticas anónimas",
|
||||
"text": "Homarr enviará estadísticas anónimas usando el software de código abierto Umami. Nunca recopila información personal y por lo tanto cumple plenamente con GDPR y CCPA. Te animamos a habilitar esta característica ya que ayuda a nuestro equipo de código abierto a identificar problemas y priorizar su resolución."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Datos del widget",
|
||||
"text": "Enviar qué widgets (y su cantidad) has configurado. No incluye URLs, nombres ni ningún otro dato."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Datos de integración",
|
||||
"text": "Enviar qué integraciones (y su cantidad) has configurado. No incluye URLs, nombres ni ningún otro dato."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Datos de usuarios",
|
||||
"text": "Enviar la cantidad de usuarios y si has activado SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Actualizado {when}",
|
||||
"search": "Buscar {count} contenedores",
|
||||
"selected": "{selectCount} de {totalCount} contenedores seleccionados",
|
||||
"footer": "{count} contenedores en total"
|
||||
"footer": "{count} contenedores en total",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Sovellus",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Lähetä anonyymejä käyttötietoja",
|
||||
"text": "Homarr lähettää anonymisoitua analytiikkaa avoimen lähdekoodin Umami‑järjestelmän avulla. Kun analytiikka on käytössä, keräämme seuraavat tiedot: Homarrin version, tietokannan tyypin (MySQL/PostgreSQL/SQLite), Docker‑/Kubernetes‑tilan, käytössä olevat tunnistautumismenetelmät sekä eri ominaisuuksien lukumäärät. Kaikki tiedot on anonymisoitu ja koottu tilastomuotoon – mitään henkilötietoja, URL‑osoitteita, nimiä tai muita tunnistettavia tietoja ei koskaan kerätä. Tämä auttaa avoimen lähdekoodin tiimiämme ymmärtämään käyttötapoja ja priorisoimaan kehitystä."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integraatiotiedot",
|
||||
"text": "Lähetä mitä ja miten monta integraatiota olet määrittänyt. Ei sisällä URL-osoitteita, nimiä tai mitään muita tietoja."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Päivitetty {when}",
|
||||
"search": "Suodata {count} konttia...",
|
||||
"selected": "{selectCount}/{totalCount} kontista valittu",
|
||||
"footer": "Yhteensä {count} konttia"
|
||||
"footer": "Yhteensä {count} konttia",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "App",
|
||||
"description": "Intègre une application dans le tableau.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "Résumé du puit DNS",
|
||||
"description": "Affiche le résumé de votre puit DNS",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Envoyer des statistiques anonymes",
|
||||
"text": "Homarr enverra des statistiques anonymes en utilisant le logiciel open source Umami. Il ne recueille jamais aucune information personnelle et est donc entièrement conforme au RGPD & CCPA. Nous vous encourageons à activer les statistiques parce qu'elles aident notre équipe open source à identifier les problèmes et à prioriser notre backlog."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Données du widget",
|
||||
"text": "Envoie quels widgets (et leur quantité) vous avez configurés. N'inclut pas les URLs, les noms ou toute autre donnée."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Données d'intégration",
|
||||
"text": "Envoie quelles intégrations (et leur quantité) vous avez configurées. N'inclut pas les URLs, les noms ou toute autre donnée."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Données utilisateur",
|
||||
"text": "Envoie le nombre d'utilisateurs et si vous avez activé le SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Mis à jour {when}",
|
||||
"search": "Rechercher dans {count} conteneurs",
|
||||
"selected": "{selectCount} sur {totalCount} conteneurs sélectionnés",
|
||||
"footer": "Total des conteneurs {count}"
|
||||
"footer": "Total des conteneurs {count}",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "אפליקציה",
|
||||
"description": "הטמעת אפליקציה בלוח.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "סיכום חור אבטחה DNS",
|
||||
"description": "מציג את הסיכום חור אבטחת DNS שלך",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "שליחת ניתוח נתונים אנונימי",
|
||||
"text": "Homarr ישלח ניתוחים אנונימיים באמצעות התוכנה בקוד פתוח Umami. הוא לעולם אינו אוסף מידע אישי ולכן תואם לחלוטין לדרישות ה-GDPR וה-CCPA. אנו מעודדים אותך להפעיל ניתוחים, כיוון שזה מסייע לצוות הקוד הפתוח שלנו לזהות בעיות ולהעניק עדיפות למשימות שברשימת העדיפויות שלנו."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "נתוני ווידג׳ט",
|
||||
"text": "שלח אילו ווידג'טים (וכמותם) הוגדרו. אינו כולל כתובות רשת, שמות או כל מידע נוסף אחר."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "נתוני אינטגרציה",
|
||||
"text": "שלח אילו אינטגרציות (וכמותם) הוגדרו. לא כולל כתובות רשת, שמות או כל מידע נוסף אחר."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "נתוני משתמשים",
|
||||
"text": "שלח את כמות המשתמשים והאם הופעל SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "עודכן ב- {when}",
|
||||
"search": "חפש {count} קונטיינרים",
|
||||
"selected": "{selectCount} מ- {totalCount} קונטיינרים נבחרו",
|
||||
"footer": "סה״כ {count} קונטיינרים"
|
||||
"footer": "סה״כ {count} קונטיינרים",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Alkalmazás",
|
||||
"description": "Alkalmazást ágyaz be a táblába.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Blokkoló Összefoglaló",
|
||||
"description": "Megjeleníti a DNS Blokkoló összefoglalóját",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": "A Homarr az Umami nevű nyílt forráskódú szoftver segítségével névtelen elemzési adatokat küld. Ha ez az opció engedélyezve van, a következő adatokat gyűjtjük: a Homarr verziója, az adatbázis típusa (MySQL/PostgreSQL/SQLite), a Docker/Kubernetes állapota, az engedélyezett hitelesítési módszerek, valamint az egyes funkciók száma. Minden adat névtelen és összesített formában kerül rögzítésre – személyes adatokat, URL-eket, neveket vagy bármilyen azonosítható adatot soha nem gyűjtünk. Ez segít nyílt forráskódú fejlesztői csapatunknak a használati szokások megértésében és a fejlesztési feladatok fontossági sorrendjének meghatározásában."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "Connetti prima un'integrazione",
|
||||
"loading": "Caricamento...",
|
||||
"placeholder": "Seleziona un'opzione",
|
||||
"noResults": "Nessun risultato trovato",
|
||||
"loadError": "Impossibile caricare le opzioni"
|
||||
},
|
||||
"app": {
|
||||
"name": "App",
|
||||
"description": "Incorpora un'app nella board.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "ArchiveTeam Warrior",
|
||||
"description": "Monitora lo stato di ArchiveTeam Warrior e i lavori di archiviazione attivi.",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": "Mostra messaggi broadcast"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "Riepilogo del filtraggio DNS",
|
||||
"description": "Mostra il riepilogo del tuo filtro DNS",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "Nessun sistema trovato",
|
||||
"error": {
|
||||
"internalServerError": "Impossibile connettersi a Beszel. Controlla le tue impostazioni di integrazione."
|
||||
},
|
||||
"column": {
|
||||
"system": "Sistema",
|
||||
"cpu": "CPU",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": "Mostra versione agente"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": "Impossibile connettersi a Beszel. Controlla le tue impostazioni di integrazione."
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "CPU",
|
||||
"memory": "Memoria",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "Nessun avviso configurato",
|
||||
"error": {
|
||||
"internalServerError": "Impossibile connettersi a Beszel. Controlla le tue impostazioni di integrazione."
|
||||
},
|
||||
"history": "Cronologia",
|
||||
"resolved": "Risolto",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "Statistiche Sistema Beszel",
|
||||
"description": "Grafici a serie temporali per CPU, memoria, disco, rete e metriche dei container Docker",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "Sistema",
|
||||
"description": "Seleziona un sistema Beszel per visualizzare le statistiche per"
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": "Periodo di tempo"
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "Seleziona sistema",
|
||||
"error": {
|
||||
"internalServerError": "Impossibile connettersi a Beszel. Controlla le tue impostazioni di integrazione.",
|
||||
"liveConnectionFailed": "Connessione Live non riuscita. L'istanza di Beszel potrebbe non supportare lo streaming in tempo reale.",
|
||||
"switchToHistorical": "Passa alla visualizzazione storica",
|
||||
"systemNotFound": "Il sistema selezionato non è stato trovato. Scegli un sistema diverso."
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "Utilizzo CPU",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Invia analytics anonimi",
|
||||
"text": "Homarr invierà analisi anonime utilizzando il software open source Umami. Non raccoglie mai informazioni personali ed è quindi pienamente conforme al GDPR e al CCPA. Ti invitiamo ad abilitare l'analisi perché aiuta il nostro team open source a identificare i problemi e a dare priorità al nostro arretrato."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Dati widget",
|
||||
"text": "Invia quali widget (e la loro quantità) hai configurato. Non include URL, nomi o altri dati."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Dati di integrazione",
|
||||
"text": "Invia quali integrazioni (e la loro quantità) hai configurato. Non include URL, nomi o altri dati."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Dati utenti",
|
||||
"text": "Invia il numero di utenti e se hai attivato SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Aggiornato {when}",
|
||||
"search": "Cerca {count} container",
|
||||
"selected": "{selectCount} di {totalCount} containers selezionati",
|
||||
"footer": "Totale container {count}"
|
||||
"footer": "Totale container {count}",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "アプリ",
|
||||
"description": "アプリをボードに埋め込みます。",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole 概要",
|
||||
"description": "DNS Hole の概要を表示します",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "匿名の解析を送信",
|
||||
"text": "Homarrは、オープンソースソフトウェアUmamiを使用して匿名化された分析を送信します。 有効にすると、以下が収集されます。Homarrバージョン、データベースタイプ(MySQL/PostgreSQL/SQLite)、Docker/Kubernetesステータス、認証方法、各機能の数。 すべてのデータは匿名で集計されます。個人情報、URL、名前、または識別可能なデータは収集されません。 これにより、オープンソースチームは利用パターンを理解し、開発に優先順位を付けることができます。"
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "ウィジェットのデータ",
|
||||
"text": "あなたが設定したウィジェット(およびその数量)を送信します。URL・名前・その他のデータは含まれません。"
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "連携情報",
|
||||
"text": "あなたが設定した連携機能(およびその数量)を送信します。URL・名前・その他のデータは含まれません。"
|
||||
},
|
||||
"usersData": {
|
||||
"title": "ユーザー情報",
|
||||
"text": "SSOをアクティベートしたユーザーの数を送信します"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "{when} に更新しました",
|
||||
"search": "{count} 個のコンテナを検索",
|
||||
"selected": "{totalCount} 個のうち、{selectCount} 個のコンテナが選択されています",
|
||||
"footer": "合計 {count} 個のコンテナ"
|
||||
"footer": "合計 {count} 個のコンテナ",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "App",
|
||||
"description": "Een app insluiten op het bord.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS-hole overzicht",
|
||||
"description": "Toont het overzicht van je DNS-hole",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Anonieme analyses sturen",
|
||||
"text": "Homarr verstuurt geanonimiseerde analyses met behulp van de open source software Umami. Het verzamelt nooit persoonlijke informatie en is daarom volledig GDPR & CCPA compliant. We moedigen je aan om analytics in te schakelen omdat het ons open source team helpt bij het identificeren van problemen en het prioriteren van onze backlog."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widgetgegevens",
|
||||
"text": "Stuur welke widgets (en hun aantal) je hebt geconfigureerd. Bevat geen URL's, namen of andere gegevens."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integratiegegevens",
|
||||
"text": "Stuur welke integraties (en het aantal) je hebt geconfigureerd. Bevat geen URL's, namen of andere gegevens."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Gebruikersgegevens",
|
||||
"text": "Stuur het aantal gebruikers en of je SSO hebt geactiveerd"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "{when} bijgewerkt",
|
||||
"search": "{count} containers zoeken",
|
||||
"selected": "{selectCount} van {totalCount} containers geselecteerd",
|
||||
"footer": "Totaal {count} containers"
|
||||
"footer": "Totaal {count} containers",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "App",
|
||||
"description": "Bygger inn en app i tavlen.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole Sammendrag",
|
||||
"description": "Viser sammendrag av DNS Hole",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Send anonym brukerdata",
|
||||
"text": "Homarr vil sende anonymiserte analyser ved bruk av den åpne programvaren Umami. Når dette er aktivert, samler vi inn: Homarr-versjon, databastype (MySQL/PostgreSQL/SQLite), Docker/Kubernetes-status, aktiverte autentiseringsmetoder, og antallet av hver funksjon. Alle data er anonyme og aggregerte – ingen personlig informasjon, URL-er, navn eller identifiserbare data samles inn. Dette hjelper vårt open source-team med å forstå bruks‑mønstre og prioritere utvikling."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Widget data",
|
||||
"text": "Send hvilke widgeter (og deres antall) du har konfigurert. Inkluderer ikke nettadresser, navn eller andre data."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integrasjonsdata",
|
||||
"text": "Send hvilke integrasjoner (og deres antall) du har konfigurert. Inkluderer ikke nettadresser, navn eller andre data."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Brukeres data",
|
||||
"text": "Send antall brukere og om du har aktivert SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Oppdatert {when}",
|
||||
"search": "Søk {count} containere",
|
||||
"selected": "{selectCount} av {totalCount} containere valgt",
|
||||
"footer": "Totalt {count} containere"
|
||||
"footer": "Totalt {count} containere",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Aplikacja",
|
||||
"description": "Dodaje aplikację do tablicy.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "Podsumowanie Hole DNS",
|
||||
"description": "Wyświetla podsumowanie Twojego hosta DNS",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Wysyłaj anonimowe dane analityczne",
|
||||
"text": "Homarr będzie wysyłał anonimowe dane analityczne przy użyciu oprogramowania open source Umami. Nigdy nie zbiera żadnych danych osobowych, dlatego jest w pełni zgodny z RODO i CCPA. Zachęcamy do włączenia analityki, ponieważ pomaga to naszemu zespołowi open source w identyfikowaniu problemów i ustalaniu priorytetów w backlogu."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Dane widżetu",
|
||||
"text": "Wyślij informacje o tym, które widżety (i w jakiej ilości) masz skonfigurowane. Nie obejmuje to adresów URL, nazw ani żadnych innych danych."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Informacje o integracjach",
|
||||
"text": "Wyślij informacje o tym, które integracje (i w jakiej ilości) masz skonfigurowane. Nie obejmuje to adresów URL, nazw ani żadnych innych danych."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Dane użytkowników",
|
||||
"text": "Wyślij liczbę użytkowników oraz informację, czy SSO jest aktywne."
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Zaktualizowano {when}",
|
||||
"search": "Szukaj wśród {count} kontenerów",
|
||||
"selected": "Wybrano {selectCount} z {totalCount} kontenerów",
|
||||
"footer": "Łącznie {count} kontenerów"
|
||||
"footer": "Łącznie {count} kontenerów",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -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",
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Aplicativo",
|
||||
"description": "Incorpora um aplicativo no quadro.",
|
||||
@@ -1731,7 +1738,7 @@
|
||||
"label": "Abrir em nova guia"
|
||||
},
|
||||
"withBorder": {
|
||||
"label": ""
|
||||
"label": "Mostrar bordas"
|
||||
},
|
||||
"items": {
|
||||
"label": "Marcadores",
|
||||
@@ -1739,6 +1746,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 +2406,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 +2685,8 @@
|
||||
}
|
||||
},
|
||||
"paperlessNgx": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"name": "Paperless-ngx",
|
||||
"description": "Visão geral do seu arquivo de documentos Paperless-ngx",
|
||||
"documentsTotal": "",
|
||||
"documentsInbox": "",
|
||||
"correspondents": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4133,7 +4168,7 @@
|
||||
"items": {
|
||||
"documentation": "Documentação",
|
||||
"submitIssue": "Enviar um problema",
|
||||
"discord": "Discórdia da comunidade",
|
||||
"discord": "Discord da comunidade",
|
||||
"sourceCode": "Código-fonte"
|
||||
}
|
||||
},
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Enviar análises anônimas",
|
||||
"text": "O Homarr enviará análises anonimizadas usando o software de código aberto Umami. Quando habilitado, coletamos: versão do Homarr, tipo de banco de dados (MySQL/PostgreSQL/SQLite), status Docker/Kubernetes, métodos de autenticação habilitados e a quantidade de cada recurso. Todos os dados são anônimos e agregados - nenhuma informação pessoal, URLs, nomes ou dados identificáveis são coletados. Isso ajuda nossa equipe de código aberto a entender padrões de uso e priorizar o desenvolvimento."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Dados de widgets",
|
||||
"text": "Envia quais widgets (e suas quantidades) você configurou. Não inclui URLs, nomes ou quaisquer outros dados."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Dados de integrações",
|
||||
"text": "Envia quais integrações (e suas quantidades) você configurou. Não inclui URLs, nomes ou quaisquer outros dados."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Dados dos usuários",
|
||||
"text": "Envia a quantidade de usuários e se você ativou SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Atualizado em {when}",
|
||||
"search": "Buscar {count} contêineres",
|
||||
"selected": "{selectCount} de {totalCount} contêineres selecionados",
|
||||
"footer": "Total de contêineres: {count}"
|
||||
"footer": "Total de contêineres: {count}",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
@@ -5741,7 +5766,7 @@
|
||||
"label": "Reportar um problema"
|
||||
},
|
||||
"discord": {
|
||||
"label": "Discórdia da comunidade"
|
||||
"label": "Discord da comunidade"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Приложение",
|
||||
"description": "Встраивает приложение в панель.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "Сводка DNS-фильтра",
|
||||
"description": "Отображает сводную информацию о вашем DNS-фильтре",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Отправлять анонимную аналитику",
|
||||
"text": "Homarr отправляет обезличенную аналитику с помощью открытого ПО Umami. При включении собираются: версия Homarr, тип базы данных (MySQL/PostgreSQL/SQLite), статус Docker/Kubernetes, включённые методы аутентификации и количество используемых функций. Все данные анонимны и агрегированы - личная информация, URL-адреса, имена и другие идентифицирующие данные никогда не собираются. Это помогает нашей команде понимать типичные сценарии использования и определять приоритеты в разработке."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Данные о виджетах",
|
||||
"text": "Отправлять информацию о настроенных виджетах (и их количестве). Не включает URL-адреса, названия или другие данные."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Данные об интеграциях",
|
||||
"text": "Отправлять информацию о настроенных интеграциях (и их количестве). Не включает URL-адреса, названия или другие данные."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Данные о пользователях",
|
||||
"text": "Отправлять количество пользователей и информацию об использовании единого входа"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Обновлено {when}",
|
||||
"search": "Поиск среди {count} контейнеров",
|
||||
"selected": "Выбрано {selectCount} из {totalCount} контейнеров",
|
||||
"footer": "Всего {count} контейнеров"
|
||||
"footer": "Всего {count} контейнеров",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Aplikácia",
|
||||
"description": "Vloží aplikáciu do tabule.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "Zhrnutie dier DNS",
|
||||
"description": "Zobrazí súhrn vašej diery DNS",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Odoslať anonymné analýzy",
|
||||
"text": "Homarr bude posielať anonymizované analýzy pomocou open source softvéru Umami. Nikdy nezhromažďuje žiadne osobné údaje, a preto je plne v súlade s GDPR a CCPA. Odporúčame vám povoliť analýzu, pretože pomáha nášmu tímu s otvoreným zdrojom identifikovať problémy a uprednostňovať naše nevybavené veci."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Údaje widgetu",
|
||||
"text": "Pošlite, ktoré widgety (a ich množstvo) ste nakonfigurovali. Nezahŕňa adresy URL, mená ani iné údaje."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Integračné údaje",
|
||||
"text": "Pošlite, ktoré integrácie (a ich množstvo) ste nakonfigurovali. Nezahŕňa adresy URL, mená ani iné údaje."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Údaje o používateľoch",
|
||||
"text": "Pošlite počet používateľov a či ste aktivovali jednotné prihlásenie"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Aktualizované {when}",
|
||||
"search": "Vyhľadajte {count} kontajnery",
|
||||
"selected": "{selectCount} z {totalCount} vybratých kontajnerov",
|
||||
"footer": "Celkový počet kontajnerov: {count}"
|
||||
"footer": "Celkový počet kontajnerov: {count}",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Uppdaterad {when}",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Uygulama",
|
||||
"description": "Panelin içine uygulama yerleştirir.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Çözümleyici Özeti",
|
||||
"description": "DNS Çözümleyici özetini görüntüler",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Anonim analizler gönder",
|
||||
"text": "Homarr, açık kaynaklı yazılım Umami'yi kullanarak anonimleştirilmiş analizler gönderecektir. Hiçbir zaman kişisel bilgi toplamaz ve bu nedenle tamamen GDPR ve CCPA uyumludur. Analizleri etkinleştirmenizi öneririz çünkü, açık kaynak yazılım ekibimizin sorunları daha hızlı belirlemesine ve birikmiş işlerimizi önceliklendirmesine yardımcı olmaktadır."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Araç verileri",
|
||||
"text": "Hangi araçları (ve miktarlarını) yapılandırdığınızı gönderin. URL'leri, adları veya başka herhangi bir veri içermez."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Entegrasyon verileri",
|
||||
"text": "Entegrasyon (ve sayıları) yapılandırma verilerini gönder. URL'leri, alan adlarını veya başka herhangi bir veriyi içermez."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Kullanıcı verileri",
|
||||
"text": "Kullanıcı sayısını ve SSO etkinleştirme verilerini gönder"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "{when} Güncellendi",
|
||||
"search": "{count} konteyner içinde ara",
|
||||
"selected": "{selectCount} / {totalCount} konteyner seçildi",
|
||||
"footer": "Toplam {count} konteyner"
|
||||
"footer": "Toplam {count} konteyner",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -812,7 +812,7 @@
|
||||
"singleValue": "",
|
||||
"keyValue": "",
|
||||
"table": "",
|
||||
"statGrid": "",
|
||||
"statGrid": "Кольорові картки статистики в сітці — як-от віджети Pi-Hole або Speedtest",
|
||||
"progressBars": "",
|
||||
"statusIndicator": "",
|
||||
"countGrid": "",
|
||||
@@ -874,7 +874,7 @@
|
||||
"exportError": "",
|
||||
"duplicated": "",
|
||||
"duplicateError": "",
|
||||
"toggleError": "",
|
||||
"toggleError": "Не вдалося змінити стан віджету.",
|
||||
"deleteError": "",
|
||||
"previewError": "",
|
||||
"executed": "",
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "Додаток",
|
||||
"description": "Вбудовує додаток до дошки.",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "Надсилати анонімну аналітику",
|
||||
"text": "Homarr буде відправляти анонімну аналітику з використанням програмного забезпечення з відкритим вихідним кодом Umami. Він ніколи не збирає жодної особистої інформації і, таким чином, сумісний з GDPR та CCPA. Ми заохочуємо вас увімкнути аналітику, тому що це допомагає нашій команді визначати проблеми та пріоритетні задачі."
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "Дані віджетів",
|
||||
"text": "Надсилати, які віджети (і їх кількість) ви налаштували. Не містить URL, назви, або будь-які інші дані."
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "Дані інтеграції",
|
||||
"text": "Надсилати, які віджети (і їх кількість) ви налаштували. Не містить URL, назви, або будь-які інші дані."
|
||||
},
|
||||
"usersData": {
|
||||
"title": "Дані користувачів",
|
||||
"text": "Надсилати кількість користувачів і чи активували ви SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "Оновлено {when}",
|
||||
"search": "",
|
||||
"selected": "Вибрано {selectCount} із {totalCount} контейнерів",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
},
|
||||
"usersData": {
|
||||
"title": "",
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "",
|
||||
"search": "",
|
||||
"selected": "",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -1652,6 +1652,13 @@
|
||||
}
|
||||
},
|
||||
"widget": {
|
||||
"integrationSelect": {
|
||||
"configureIntegrationFirst": "",
|
||||
"loading": "",
|
||||
"placeholder": "",
|
||||
"noResults": "",
|
||||
"loadError": ""
|
||||
},
|
||||
"app": {
|
||||
"name": "應用程式",
|
||||
"description": "將應用程式新增至面板中",
|
||||
@@ -1739,6 +1746,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"archiveTeamWarrior": {
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"showBroadcastMessage": {
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnsHoleSummary": {
|
||||
"name": "DNS Hole 概況",
|
||||
"description": "顯示 DNS Hole 的概況",
|
||||
@@ -3561,6 +3577,9 @@
|
||||
}
|
||||
},
|
||||
"noRecords": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"column": {
|
||||
"system": "",
|
||||
"cpu": "",
|
||||
@@ -3617,6 +3636,9 @@
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"metric": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
@@ -3643,6 +3665,9 @@
|
||||
}
|
||||
},
|
||||
"empty": "",
|
||||
"error": {
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "",
|
||||
"resolved": "",
|
||||
"status": {
|
||||
@@ -3654,6 +3679,10 @@
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"systemId": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"timePeriod": {
|
||||
"label": ""
|
||||
},
|
||||
@@ -3683,6 +3712,12 @@
|
||||
}
|
||||
},
|
||||
"selectSystem": "",
|
||||
"error": {
|
||||
"internalServerError": "",
|
||||
"liveConnectionFailed": "",
|
||||
"switchToHistorical": "",
|
||||
"systemNotFound": ""
|
||||
},
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
@@ -4380,18 +4415,6 @@
|
||||
"general": {
|
||||
"title": "發送匿名分析",
|
||||
"text": "Homarr 使用開源軟體 Umami 發送匿名分析,他從不收集任何個人資料,因此完全符合 GDPR 和 CCPA 要求,我們鼓勵啟用分析,因為可以幫助我們的團隊識別問題,並確認待辦事項的優先級別"
|
||||
},
|
||||
"widgetData": {
|
||||
"title": "小工具數據",
|
||||
"text": "發送已設定的小工具 (及其數量),不包括網址、名稱或任何其他數據"
|
||||
},
|
||||
"integrationData": {
|
||||
"title": "集成數據",
|
||||
"text": "發送已設定的集成 (及其數量),不包括網址、名稱或任何其他數據"
|
||||
},
|
||||
"usersData": {
|
||||
"title": "使用者資料",
|
||||
"text": "發送使用者數量以及是否激活了 SSO"
|
||||
}
|
||||
},
|
||||
"crawlingAndIndexing": {
|
||||
@@ -4919,7 +4942,9 @@
|
||||
"updated": "已更新於 {when}",
|
||||
"search": "搜尋 {count} 容器",
|
||||
"selected": "{totalCount} 容器的 {selectCount}",
|
||||
"footer": ""
|
||||
"footer": "",
|
||||
"totalCpu": "",
|
||||
"totalMemory": ""
|
||||
},
|
||||
"field": {
|
||||
"name": {
|
||||
|
||||
@@ -3,9 +3,6 @@ import { z } from "zod/v4";
|
||||
export const settingsInitSchema = z.object({
|
||||
analytics: z.object({
|
||||
enableGeneral: z.boolean(),
|
||||
enableWidgetData: z.boolean(),
|
||||
enableIntegrationData: z.boolean(),
|
||||
enableUserData: z.boolean(),
|
||||
}),
|
||||
crawlingAndIndexing: z.object({
|
||||
noIndex: z.boolean(),
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function BeszelAlertsWidget({
|
||||
() => ({ integrationIds, includeHistory: options.showHistory, maxHistoryItems: options.maxHistoryItems }),
|
||||
[integrationIds, options.showHistory, options.maxHistoryItems],
|
||||
);
|
||||
const { data: results = [] } = clientApi.widget.beszel.getAlerts.useQuery(alertsInput, {
|
||||
const { data: results = [], error: alertsError } = clientApi.widget.beszel.getAlerts.useQuery(alertsInput, {
|
||||
staleTime: 15_000,
|
||||
refetchInterval: 15_000,
|
||||
retry: false,
|
||||
@@ -91,6 +91,8 @@ export default function BeszelAlertsWidget({
|
||||
const triggeredAlerts = alerts.filter((a) => a.triggered);
|
||||
const okAlerts = alerts.filter((a) => !a.triggered);
|
||||
|
||||
if (alertsError) throw alertsError;
|
||||
|
||||
return (
|
||||
<ScrollArea h="100%" style={{ pointerEvents: isEditMode ? "none" : undefined }}>
|
||||
<Stack gap="sm" p="sm">
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.clickableCard {
|
||||
transition: background-color 150ms;
|
||||
}
|
||||
|
||||
.clickableCard:hover {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { MantineSize } from "@mantine/core";
|
||||
import { Badge, Box, Card, Group, Progress, Text, Stack, useComputedColorScheme } from "@mantine/core";
|
||||
import { Badge, Box, Card, Group, Progress, Text, Stack } from "@mantine/core";
|
||||
import {
|
||||
Activity,
|
||||
Battery,
|
||||
@@ -18,13 +18,17 @@ import {
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import classes from "./component.module.css";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import type { BeszelSystemRow } from "../beszel/_shared/types";
|
||||
import { statusColorMap, thresholdColor } from "../beszel/_shared/colors";
|
||||
import { formatByteRate, formatLoadAvg, formatPercent, formatTemp, formatUptime } from "../beszel/_shared/format";
|
||||
import { useBeszelFilteredSystems, useBeszelSystemsSubscription } from "../beszel/_shared/hooks";
|
||||
import { BeszelSystemStatsModal } from "../beszel/_shared/system-stats-modal";
|
||||
|
||||
interface SizeConfig {
|
||||
iconSize: number;
|
||||
@@ -153,8 +157,8 @@ interface SystemCardProps {
|
||||
t: ReturnType<typeof useScopedI18n<"widget.beszelSystemGrid">>;
|
||||
size: SizeConfig;
|
||||
maxMetrics: number;
|
||||
backgroundColor: string;
|
||||
itemRadius: MantineSize;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const metricRenderers = [
|
||||
@@ -311,20 +315,23 @@ const metricRenderers = [
|
||||
},
|
||||
] as const;
|
||||
|
||||
const SystemCard = ({ system, options, t, size, maxMetrics, backgroundColor, itemRadius }: SystemCardProps) => {
|
||||
const SystemCard = ({ system, options, t, size, maxMetrics, itemRadius, onClick }: SystemCardProps) => {
|
||||
const visibleMetrics = metricRenderers.filter((m) => m.visible(system, options)).slice(0, maxMetrics);
|
||||
|
||||
return (
|
||||
<Card
|
||||
padding={size.cardPadding}
|
||||
radius={itemRadius}
|
||||
bg={backgroundColor}
|
||||
bg="transparent"
|
||||
h="100%"
|
||||
onClick={onClick}
|
||||
className={onClick ? classes.clickableCard : undefined}
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column" as const,
|
||||
border: "0.0625rem solid var(--border-color)",
|
||||
cursor: onClick ? "pointer" : undefined,
|
||||
}}
|
||||
>
|
||||
<Group gap="xs" mb={2}>
|
||||
@@ -354,12 +361,9 @@ export default function BeszelSystemGridWidget({
|
||||
}: WidgetComponentProps<"beszelSystemGrid">) {
|
||||
const t = useScopedI18n("widget.beszelSystemGrid");
|
||||
const board = useRequiredBoard();
|
||||
const colorScheme = useComputedColorScheme("light");
|
||||
const opacity = board.opacity / 100;
|
||||
const bgBase: Record<"light" | "dark", string> = { dark: "57, 57, 57", light: "246, 247, 248" };
|
||||
const backgroundColor = `rgba(${bgBase[colorScheme]}, ${opacity})`;
|
||||
const { openModal } = useModalAction(BeszelSystemStatsModal);
|
||||
|
||||
const { data: results = [] } = clientApi.widget.beszel.getSystems.useQuery(
|
||||
const { data: results = [], error: systemsError } = clientApi.widget.beszel.getSystems.useQuery(
|
||||
{ integrationIds },
|
||||
{ staleTime: 5_000, refetchInterval: 5_000, retry: false },
|
||||
);
|
||||
@@ -367,6 +371,8 @@ export default function BeszelSystemGridWidget({
|
||||
useBeszelSystemsSubscription(integrationIds, !isEditMode);
|
||||
const filteredSystems = useBeszelFilteredSystems(results, options.statusFilter);
|
||||
|
||||
if (systemsError) throw systemsError;
|
||||
|
||||
const cols = getColCount(width, filteredSystems.length);
|
||||
const rows = Math.ceil(filteredSystems.length / cols) || 1;
|
||||
const rawCellHeight = height / rows;
|
||||
@@ -388,18 +394,24 @@ export default function BeszelSystemGridWidget({
|
||||
overflow: scrollEnabled ? "auto" : "hidden",
|
||||
}}
|
||||
>
|
||||
{filteredSystems.map((system) => (
|
||||
<SystemCard
|
||||
key={system._key}
|
||||
system={system}
|
||||
options={options}
|
||||
t={t}
|
||||
size={size}
|
||||
maxMetrics={maxMetrics}
|
||||
backgroundColor={backgroundColor}
|
||||
itemRadius={board.itemRadius}
|
||||
/>
|
||||
))}
|
||||
{filteredSystems.map((system) => {
|
||||
const integrationId = system._key.split(":")[0] ?? "";
|
||||
const handleClick = isEditMode
|
||||
? undefined
|
||||
: () => openModal({ integrationId, systemId: system.id }, { title: system.name });
|
||||
return (
|
||||
<SystemCard
|
||||
key={system._key}
|
||||
system={system}
|
||||
options={options}
|
||||
t={t}
|
||||
size={size}
|
||||
maxMetrics={maxMetrics}
|
||||
itemRadius={board.itemRadius}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
.beszelStatsSystemToggle {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 6px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 150ms ease;
|
||||
transition: opacity 0.15s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.beszelStatsWrapper:hover .beszelStatsSystemToggle {
|
||||
.beszelStatsContainer:hover .beszelStatsSystemToggle {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -12,6 +15,7 @@ import classes from "./component.module.css";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { containerColors } from "../beszel/_shared/colors";
|
||||
import type { BeszelTimePeriod } from "../beszel/_shared/chart";
|
||||
import {
|
||||
BeszelChartPanel,
|
||||
CPU_Y_AXIS_DOMAIN,
|
||||
@@ -49,13 +53,20 @@ export default function BeszelSystemStatsWidget({
|
||||
integrationIds,
|
||||
isEditMode,
|
||||
width,
|
||||
boardId,
|
||||
itemId,
|
||||
setOptions,
|
||||
}: WidgetComponentProps<"beszelSystemStats">) {
|
||||
const t = useScopedI18n("widget.beszelSystemStats");
|
||||
const { data: systemsResult = [], isPending: systemsPending } = clientApi.widget.beszel.getSystems.useQuery(
|
||||
{ integrationIds },
|
||||
{ staleTime: 30 * 1000 },
|
||||
);
|
||||
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,
|
||||
error: systemsError,
|
||||
} = clientApi.widget.beszel.getSystems.useQuery({ integrationIds }, { staleTime: 30 * 1000, retry: false });
|
||||
|
||||
const systems = useMemo(
|
||||
() => systemsResult.flatMap((r) => r.systems.map((s) => ({ value: s.id, label: s.name }))),
|
||||
@@ -68,21 +79,26 @@ 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;
|
||||
const isLive = options.timePeriod === "1m";
|
||||
|
||||
const { data: statsResult } = clientApi.widget.beszel.getSystemStats.useQuery(
|
||||
const { data: statsResult, error: statsError } = clientApi.widget.beszel.getSystemStats.useQuery(
|
||||
{
|
||||
integrationIds,
|
||||
systemId: selectedSystem,
|
||||
timePeriod: options.timePeriod as "1m" | "1h" | "12h" | "24h" | "1w" | "30d",
|
||||
includeDocker: showDocker,
|
||||
},
|
||||
{ staleTime: 30 * 1000, enabled: !isLive && systemReady && selectedSystem !== "" },
|
||||
{ staleTime: 30 * 1000, enabled: !isLive && systemReady && selectedSystem !== "", retry: false },
|
||||
);
|
||||
|
||||
const { data: liveData, error: liveError } = useLiveStats(integrationIds, selectedSystem, isLive && systemReady);
|
||||
@@ -138,30 +154,31 @@ export default function BeszelSystemStatsWidget({
|
||||
const systemStats = activeStats?.systemStats;
|
||||
const containerStatsRaw = activeStats?.containerStats;
|
||||
|
||||
const cpuData = useSystemChartData(statsWhenShown(options.showCpu, systemStats), mappers.cpu, isLive);
|
||||
const memoryData = useSystemChartData(statsWhenShown(options.showMemory, systemStats), mappers.memory, isLive);
|
||||
const diskData = useSystemChartData(statsWhenShown(options.showDisk, systemStats), mappers.disk, isLive);
|
||||
const diskIOData = useSystemChartData(statsWhenShown(options.showDiskIO, systemStats), mappers.diskIO, isLive);
|
||||
const networkData = useSystemChartData(statsWhenShown(options.showNetwork, systemStats), mappers.network, isLive);
|
||||
const timePeriod = options.timePeriod as BeszelTimePeriod;
|
||||
const cpuData = useSystemChartData(statsWhenShown(options.showCpu, systemStats), mappers.cpu, timePeriod);
|
||||
const memoryData = useSystemChartData(statsWhenShown(options.showMemory, systemStats), mappers.memory, timePeriod);
|
||||
const diskData = useSystemChartData(statsWhenShown(options.showDisk, systemStats), mappers.disk, timePeriod);
|
||||
const diskIOData = useSystemChartData(statsWhenShown(options.showDiskIO, systemStats), mappers.diskIO, timePeriod);
|
||||
const networkData = useSystemChartData(statsWhenShown(options.showNetwork, systemStats), mappers.network, timePeriod);
|
||||
|
||||
const containerNames = useContainerNames(statsWhenShown(showDocker, containerStatsRaw));
|
||||
const dockerCpuData = useDockerChartData(
|
||||
statsWhenShown(options.showDockerCpu, containerStatsRaw),
|
||||
containerNames,
|
||||
"cpu",
|
||||
isLive,
|
||||
timePeriod,
|
||||
);
|
||||
const dockerMemData = useDockerChartData(
|
||||
statsWhenShown(options.showDockerMemory, containerStatsRaw),
|
||||
containerNames,
|
||||
"memory",
|
||||
isLive,
|
||||
timePeriod,
|
||||
);
|
||||
const dockerNetData = useDockerChartData(
|
||||
statsWhenShown(options.showDockerNetwork, containerStatsRaw),
|
||||
containerNames,
|
||||
"network",
|
||||
isLive,
|
||||
timePeriod,
|
||||
);
|
||||
|
||||
const containerSeries = useMemo(
|
||||
@@ -171,6 +188,9 @@ export default function BeszelSystemStatsWidget({
|
||||
|
||||
const cols = gridColumns[Number(width > 600)];
|
||||
|
||||
if (systemsError) throw systemsError;
|
||||
if (!isLive && statsError) throw statsError;
|
||||
|
||||
if (!systemsPending && !systemExists && systems.length > 0) {
|
||||
return (
|
||||
<Center h="100%">
|
||||
@@ -210,38 +230,29 @@ export default function BeszelSystemStatsWidget({
|
||||
return (
|
||||
<ScrollArea
|
||||
h="100%"
|
||||
className={classes.beszelStatsWrapper}
|
||||
className={classes.beszelStatsContainer}
|
||||
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}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "lucide-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useModalAction } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
@@ -27,6 +28,7 @@ import type { BeszelSystemRow } from "../beszel/_shared/types";
|
||||
import { loadAvgColor, statusColorMap, thresholdColor } from "../beszel/_shared/colors";
|
||||
import { formatByteRate, formatLoadAvg, formatPercent, formatTemp, formatUptime } from "../beszel/_shared/format";
|
||||
import { useBeszelFilteredSystems, useBeszelSystemsSubscription } from "../beszel/_shared/hooks";
|
||||
import { BeszelSystemStatsModal } from "../beszel/_shared/system-stats-modal";
|
||||
|
||||
const directionMultiplier: Record<string, number> = { asc: 1, desc: -1 };
|
||||
|
||||
@@ -66,7 +68,8 @@ export default function BeszelSystemTableWidget({
|
||||
width,
|
||||
}: WidgetComponentProps<"beszelSystemTable">) {
|
||||
const t = useScopedI18n("widget.beszelSystemTable");
|
||||
const { data: results = [] } = clientApi.widget.beszel.getSystems.useQuery(
|
||||
const { openModal } = useModalAction(BeszelSystemStatsModal);
|
||||
const { data: results = [], error: systemsError } = clientApi.widget.beszel.getSystems.useQuery(
|
||||
{ integrationIds },
|
||||
{ staleTime: 5_000, refetchInterval: 5_000, retry: false },
|
||||
);
|
||||
@@ -255,6 +258,13 @@ export default function BeszelSystemTableWidget({
|
||||
return cols.filter(Boolean) as DataTableColumn<SystemRowWithKey>[];
|
||||
}, [options, t, size]);
|
||||
|
||||
const handleRowClick = ({ record }: { record: SystemRowWithKey }) => {
|
||||
const integrationId = record._key.split(":")[0] ?? "";
|
||||
openModal({ integrationId, systemId: record.id }, { title: record.name });
|
||||
};
|
||||
|
||||
if (systemsError) throw systemsError;
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
style={{ pointerEvents: isEditMode ? "none" : undefined }}
|
||||
@@ -270,6 +280,7 @@ export default function BeszelSystemTableWidget({
|
||||
idAccessor="_key"
|
||||
height="100%"
|
||||
className="beszel-table"
|
||||
onRowClick={isEditMode ? undefined : handleRowClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,28 @@
|
||||
/* Make the DataTable transparent so the board card's opacity-controlled
|
||||
background shows through. The mantine-datatable defaults to
|
||||
var(--mantine-color-body) which is opaque. */
|
||||
.beszel-table,
|
||||
.beszel-table .mantine-datatable-table,
|
||||
.beszel-table .mantine-datatable-table thead,
|
||||
.beszel-table .mantine-datatable-table tbody,
|
||||
.beszel-table .mantine-datatable-table tfoot,
|
||||
.beszel-table th,
|
||||
.beszel-table td {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.beszel-table .mantine-datatable-table tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.beszel-table .mantine-datatable-table tbody tr:hover {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
|
||||
}
|
||||
|
||||
.beszel-table th {
|
||||
white-space: nowrap;
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 60%, transparent) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.beszel-table td {
|
||||
|
||||
@@ -8,18 +8,50 @@ import dayjs from "dayjs";
|
||||
|
||||
import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types";
|
||||
|
||||
const formatTime = (timestamp: string) => dayjs(timestamp).format("HH:mm");
|
||||
const formatTimeLive = (timestamp: string) => dayjs(timestamp).format("HH:mm:ss");
|
||||
export type BeszelTimePeriod = "1m" | "1h" | "12h" | "24h" | "1w" | "30d";
|
||||
|
||||
function prepareRecords<T>(records: T[], live: boolean) {
|
||||
if (live) {
|
||||
return { fmt: formatTimeLive, ordered: records };
|
||||
const timeFormats: Record<BeszelTimePeriod, string> = {
|
||||
"1m": "HH:mm:ss",
|
||||
"1h": "HH:mm",
|
||||
"12h": "HH:mm",
|
||||
"24h": "HH:mm",
|
||||
"1w": "MMM D",
|
||||
"30d": "MMM D",
|
||||
};
|
||||
|
||||
const periodDays: Partial<Record<BeszelTimePeriod, number>> = { "1w": 7, "30d": 30 };
|
||||
|
||||
function prepareRecords<T>(records: T[], timePeriod: BeszelTimePeriod) {
|
||||
const fmt = (timestamp: string) => dayjs(timestamp).format(timeFormats[timePeriod]);
|
||||
if (timePeriod === "1m") {
|
||||
return { fmt, ordered: records };
|
||||
}
|
||||
return { fmt: formatTime, ordered: [...records].toReversed() };
|
||||
return { fmt, ordered: [...records].toReversed() };
|
||||
}
|
||||
|
||||
function padTimeGrid(data: Record<string, unknown>[], timePeriod: BeszelTimePeriod) {
|
||||
const days = periodDays[timePeriod];
|
||||
if (!days) return data;
|
||||
|
||||
const fmt = (ts: string) => dayjs(ts).format(timeFormats[timePeriod]);
|
||||
const now = dayjs();
|
||||
const existingDays = new Set(data.map((d) => dayjs(d.rawTime as string).format("YYYY-MM-DD")));
|
||||
const result = [...data];
|
||||
|
||||
for (let i = days; i >= 0; i--) {
|
||||
const d = now.subtract(i, "day").startOf("day");
|
||||
const key = d.format("YYYY-MM-DD");
|
||||
if (!existingDays.has(key)) {
|
||||
const iso = d.toISOString();
|
||||
result.push({ time: fmt(iso), rawTime: iso });
|
||||
}
|
||||
}
|
||||
|
||||
return result.toSorted((a, b) => new Date(a.rawTime as string).getTime() - new Date(b.rawTime as string).getTime());
|
||||
}
|
||||
|
||||
const yAxisBase = { tickMargin: 0, tick: { fontSize: 10 } } as const;
|
||||
const chartStyle = { minWidth: 0, minHeight: 0 } as const;
|
||||
const chartStyle = { minWidth: 0, minHeight: 1 } as const;
|
||||
const panelStyle = { minWidth: 0, overflow: "hidden" } as const;
|
||||
export const CPU_Y_AXIS_DOMAIN: [number, string] = [0, "auto"];
|
||||
|
||||
@@ -77,7 +109,7 @@ const BeszelAreaChart = memo(
|
||||
dataKey="time"
|
||||
curveType="monotone"
|
||||
withGradient={false}
|
||||
connectNulls
|
||||
connectNulls={false}
|
||||
withDots={false}
|
||||
type={type}
|
||||
strokeWidth={1}
|
||||
@@ -96,16 +128,18 @@ const BeszelAreaChart = memo(
|
||||
export const useSystemChartData = (
|
||||
systemStats: BeszelSystemStatsRecord[] | undefined,
|
||||
mapFn: (stats: BeszelSystemStatsRecord["stats"]) => Record<string, unknown>,
|
||||
live = false,
|
||||
timePeriod: BeszelTimePeriod = "1h",
|
||||
) =>
|
||||
useMemo(() => {
|
||||
if (!systemStats) return [];
|
||||
const { fmt, ordered } = prepareRecords(systemStats, live);
|
||||
return ordered.map((r) => ({
|
||||
const { fmt, ordered } = prepareRecords(systemStats, timePeriod);
|
||||
const mapped = ordered.map((r) => ({
|
||||
time: fmt(r.created),
|
||||
rawTime: r.created,
|
||||
...mapFn(r.stats),
|
||||
}));
|
||||
}, [systemStats, mapFn, live]);
|
||||
return padTimeGrid(mapped, timePeriod);
|
||||
}, [systemStats, mapFn, timePeriod]);
|
||||
|
||||
export const useContainerNames = (containerStats: BeszelContainerStatsRecord[] | undefined, max = 15) => {
|
||||
const prevRef = useRef<string[]>([]);
|
||||
@@ -144,19 +178,20 @@ export const useDockerChartData = (
|
||||
containerStats: BeszelContainerStatsRecord[] | undefined,
|
||||
containerNames: string[],
|
||||
metric: "cpu" | "memory" | "network",
|
||||
live = false,
|
||||
timePeriod: BeszelTimePeriod = "1h",
|
||||
) =>
|
||||
useMemo(() => {
|
||||
if (!containerStats?.length) return [];
|
||||
const extract = defaultContainerExtractors[metric];
|
||||
if (!extract) return [];
|
||||
const { fmt, ordered } = prepareRecords(containerStats, live);
|
||||
return ordered.map((record) => {
|
||||
const point: Record<string, unknown> = { time: fmt(record.created) };
|
||||
const { fmt, ordered } = prepareRecords(containerStats, timePeriod);
|
||||
const mapped = ordered.map((record) => {
|
||||
const point: Record<string, unknown> = { time: fmt(record.created), rawTime: record.created };
|
||||
const byName = new Map(record.stats.map((c) => [c.n, c]));
|
||||
for (const name of containerNames) {
|
||||
point[name] = extract(byName.get(name));
|
||||
}
|
||||
return point;
|
||||
});
|
||||
}, [containerStats, containerNames, metric, live]);
|
||||
return padTimeGrid(mapped, timePeriod);
|
||||
}, [containerStats, containerNames, metric, timePeriod]);
|
||||
@@ -31,7 +31,10 @@ export const useBeszelFilteredSystems = (
|
||||
statusFilter: string,
|
||||
): SystemRowWithKey[] => {
|
||||
const allSystems = useMemo(
|
||||
() => results.flatMap((r) => r.systems.map((s) => ({ ...s, _key: `${r.integrationId}:${s.id}` }))),
|
||||
() =>
|
||||
results
|
||||
.flatMap((r) => r.systems.map((s) => ({ ...s, _key: `${r.integrationId}:${s.id}` })))
|
||||
.toSorted((a, b) => a.name.localeCompare(b.name)),
|
||||
[results],
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { SegmentedControl, SimpleGrid, Stack } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { createModal } from "@homarr/modals";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { BeszelTimePeriod } from "./chart";
|
||||
import { BeszelChartPanel, CPU_Y_AXIS_DOMAIN, useSystemChartData } from "./chart";
|
||||
import { chartAxisFormatters, formatByteRate, formatGB, formatPercent } from "./format";
|
||||
import { makeTooltipProps } from "./tooltip";
|
||||
|
||||
const tooltipPercent = makeTooltipProps(formatPercent);
|
||||
const tooltipGB = makeTooltipProps(formatGB, true);
|
||||
const tooltipRate = makeTooltipProps(formatByteRate, true);
|
||||
|
||||
const CHART_HEIGHT = 180;
|
||||
const MB = 1024 * 1024;
|
||||
|
||||
const timePeriodOptions: { value: BeszelTimePeriod; label: string }[] = [
|
||||
{ value: "1h", label: "1H" },
|
||||
{ value: "12h", label: "12H" },
|
||||
{ value: "24h", label: "24H" },
|
||||
{ value: "1w", label: "1W" },
|
||||
{ value: "30d", label: "30D" },
|
||||
];
|
||||
|
||||
interface BeszelSystemStatsModalProps {
|
||||
integrationId: string;
|
||||
systemId: string;
|
||||
}
|
||||
|
||||
export const BeszelSystemStatsModal = createModal<BeszelSystemStatsModalProps>(({ innerProps }) => {
|
||||
const t = useScopedI18n("widget.beszelSystemStats");
|
||||
const [timePeriod, setTimePeriod] = useState<BeszelTimePeriod>("1h");
|
||||
|
||||
const { data } = clientApi.widget.beszel.getSystemStats.useQuery(
|
||||
{
|
||||
integrationIds: [innerProps.integrationId],
|
||||
systemId: innerProps.systemId,
|
||||
timePeriod,
|
||||
includeDocker: false,
|
||||
},
|
||||
{ staleTime: 30_000 },
|
||||
);
|
||||
|
||||
const mappers = useMemo(
|
||||
() => ({
|
||||
cpu: (s: { cpu: number }) => ({ [t("chart.cpu.series")]: s.cpu }),
|
||||
memory: (s: { mu: number; mb: number }) => ({
|
||||
[t("chart.memory.series")]: s.mu,
|
||||
[t("chart.memory.cache")]: s.mb ?? 0,
|
||||
}),
|
||||
disk: (s: { du: number }) => ({ [t("chart.disk.series")]: s.du }),
|
||||
diskIO: (s: { dr?: number; dw?: number }) => ({
|
||||
[t("chart.diskIO.read")]: (s.dr ?? 0) * MB,
|
||||
[t("chart.diskIO.write")]: (s.dw ?? 0) * MB,
|
||||
}),
|
||||
network: (s: { ns: number; nr: number; b?: [number, number] }) => ({
|
||||
[t("chart.network.sent")]: s.b?.[0] ?? s.ns,
|
||||
[t("chart.network.recv")]: s.b?.[1] ?? s.nr,
|
||||
}),
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
|
||||
const series = useMemo(
|
||||
() => ({
|
||||
cpu: [{ name: t("chart.cpu.series"), color: "teal.6" }],
|
||||
memory: [
|
||||
{ name: t("chart.memory.series"), color: "teal.5" },
|
||||
{ name: t("chart.memory.cache"), color: "teal.8" },
|
||||
],
|
||||
disk: [{ name: t("chart.disk.series"), color: "grape.6" }],
|
||||
diskIO: [
|
||||
{ name: t("chart.diskIO.write"), color: "orange.6" },
|
||||
{ name: t("chart.diskIO.read"), color: "blue.6" },
|
||||
],
|
||||
network: [
|
||||
{ name: t("chart.network.sent"), color: "blue.6" },
|
||||
{ name: t("chart.network.recv"), color: "teal.6" },
|
||||
],
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
|
||||
const systemStats = data?.systemStats;
|
||||
const cpuData = useSystemChartData(systemStats, mappers.cpu, timePeriod);
|
||||
const memoryData = useSystemChartData(systemStats, mappers.memory, timePeriod);
|
||||
const diskData = useSystemChartData(systemStats, mappers.disk, timePeriod);
|
||||
const diskIOData = useSystemChartData(systemStats, mappers.diskIO, timePeriod);
|
||||
const networkData = useSystemChartData(systemStats, mappers.network, timePeriod);
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<SegmentedControl
|
||||
size="xs"
|
||||
value={timePeriod}
|
||||
onChange={(v) => setTimePeriod(v as BeszelTimePeriod)}
|
||||
data={timePeriodOptions}
|
||||
/>
|
||||
<SimpleGrid cols={2} spacing="md">
|
||||
{cpuData.length > 0 && (
|
||||
<BeszelChartPanel
|
||||
title={t("chart.cpu.title")}
|
||||
chartProps={{
|
||||
h: CHART_HEIGHT,
|
||||
data: cpuData,
|
||||
series: series.cpu,
|
||||
yAxisFormatter: chartAxisFormatters.percent,
|
||||
yAxisDomain: CPU_Y_AXIS_DOMAIN,
|
||||
tooltipProps: tooltipPercent,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{memoryData.length > 0 && (
|
||||
<BeszelChartPanel
|
||||
title={t("chart.memory.title")}
|
||||
chartProps={{
|
||||
h: CHART_HEIGHT,
|
||||
data: memoryData,
|
||||
type: "stacked",
|
||||
series: series.memory,
|
||||
yAxisFormatter: chartAxisFormatters.gb,
|
||||
tooltipProps: tooltipGB,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{diskData.length > 0 && (
|
||||
<BeszelChartPanel
|
||||
title={t("chart.disk.title")}
|
||||
chartProps={{
|
||||
h: CHART_HEIGHT,
|
||||
data: diskData,
|
||||
series: series.disk,
|
||||
yAxisFormatter: chartAxisFormatters.gb,
|
||||
tooltipProps: tooltipGB,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{diskIOData.length > 0 && (
|
||||
<BeszelChartPanel
|
||||
title={t("chart.diskIO.title")}
|
||||
chartProps={{
|
||||
h: CHART_HEIGHT,
|
||||
data: diskIOData,
|
||||
series: series.diskIO,
|
||||
yAxisFormatter: chartAxisFormatters.rate,
|
||||
tooltipProps: tooltipRate,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{networkData.length > 0 && (
|
||||
<BeszelChartPanel
|
||||
title={t("chart.network.title")}
|
||||
chartProps={{
|
||||
h: CHART_HEIGHT,
|
||||
data: networkData,
|
||||
series: series.network,
|
||||
yAxisFormatter: chartAxisFormatters.rate,
|
||||
tooltipProps: tooltipRate,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
}).withOptions({
|
||||
defaultTitle: (t) => t("widget.beszelSystemStats.name"),
|
||||
size: 1200,
|
||||
});
|
||||
@@ -3,6 +3,10 @@
|
||||
import type { CSSProperties } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
const styles: Record<string, CSSProperties> = {
|
||||
wrapper: {
|
||||
@@ -40,6 +44,7 @@ export interface TooltipPayloadItem {
|
||||
value: number;
|
||||
color: string;
|
||||
dataKey: string;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface PortalTooltipProps {
|
||||
@@ -50,16 +55,39 @@ interface PortalTooltipProps {
|
||||
showTotal?: boolean;
|
||||
}
|
||||
|
||||
const MARGIN = 14;
|
||||
|
||||
const PortalTooltipContent = ({ active, label, payload, formatter, showTotal }: PortalTooltipProps) => {
|
||||
const mouseRef = useRef({ x: 0, y: 0 });
|
||||
const [mouse, setMouse] = useState({ x: 0, y: 0 });
|
||||
const [pos, setPos] = useState({ x: 0, y: 0 });
|
||||
const rafRef = useRef(0);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleMouse = useCallback((e: MouseEvent) => {
|
||||
mouseRef.current = { x: e.clientX, y: e.clientY };
|
||||
cancelAnimationFrame(rafRef.current);
|
||||
rafRef.current = requestAnimationFrame(() => {
|
||||
setMouse(mouseRef.current);
|
||||
const el = tooltipRef.current;
|
||||
const mx = mouseRef.current.x;
|
||||
const my = mouseRef.current.y;
|
||||
|
||||
if (!el) {
|
||||
setPos({ x: mx + MARGIN, y: my });
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = el.getBoundingClientRect();
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight;
|
||||
|
||||
let x = mx + MARGIN;
|
||||
let y = my - height / 2;
|
||||
|
||||
if (x + width > vw - MARGIN) x = mx - width - MARGIN;
|
||||
if (y < MARGIN) y = MARGIN;
|
||||
if (y + height > vh - MARGIN) y = vh - MARGIN - height;
|
||||
|
||||
setPos({ x, y });
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -84,21 +112,22 @@ const PortalTooltipContent = ({ active, label, payload, formatter, showTotal }:
|
||||
|
||||
if (!sorted.length) return null;
|
||||
|
||||
const rawTime = sorted[0]?.payload?.rawTime as string | undefined;
|
||||
const tooltipLabel = rawTime ? dayjs(rawTime).format("MMM D, LT") : label;
|
||||
const total = sorted.reduce((sum, p) => sum + p.value, 0);
|
||||
|
||||
const portalStyle: CSSProperties = {
|
||||
position: "fixed",
|
||||
left: mouse.x + 14,
|
||||
top: mouse.y,
|
||||
transform: "translateY(-50%)",
|
||||
left: pos.x,
|
||||
top: pos.y,
|
||||
zIndex: 10000,
|
||||
pointerEvents: "none",
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div style={portalStyle}>
|
||||
<div ref={tooltipRef} style={portalStyle}>
|
||||
<div style={styles.wrapper}>
|
||||
<div style={styles.header}>{label}</div>
|
||||
<div style={styles.header}>{tooltipLabel}</div>
|
||||
{sorted.map((item) => (
|
||||
<div key={item.dataKey} style={styles.row}>
|
||||
<div style={{ ...styles.indicator, background: item.color }} />
|
||||
|
||||
@@ -200,6 +200,17 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo
|
||||
|
||||
const totalContainers = containers.length;
|
||||
|
||||
const totals = useMemo(() => {
|
||||
return containers.reduce(
|
||||
(acc, container) => {
|
||||
acc.cpu += safeValue(container.cpuUsage);
|
||||
acc.memory += safeValue(container.memoryUsage);
|
||||
return acc;
|
||||
},
|
||||
{ cpu: 0, memory: 0 }
|
||||
);
|
||||
}, [containers]);
|
||||
|
||||
const columns = useMemo(() => createColumns(t), [t]);
|
||||
|
||||
const table = useTranslatedMantineReactTable({
|
||||
@@ -261,12 +272,26 @@ export default function DockerWidget({ options, width, isEditMode }: WidgetCompo
|
||||
}}
|
||||
p={4}
|
||||
>
|
||||
<Group gap={4}>
|
||||
<IconBrandDocker size={20} />
|
||||
<Text size="sm">{t("table.footer", { count: totalContainers.toString() })}</Text>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconBrandDocker size={20} style={{ flexShrink: 0 }} />
|
||||
<Text size="sm" truncate="end">
|
||||
{t("table.footer", { count: totalContainers.toString() })}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Text size="sm">{t("table.updated", { when: relativeTime })}</Text>
|
||||
<Group gap="sm" wrap="wrap" justify="flex-end" style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text size="sm" style={{ whiteSpace: "nowrap" }}>
|
||||
{t("table.totalCpu", { cpu: totals.cpu.toFixed(2) })}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" style={{ whiteSpace: "nowrap" }}>
|
||||
{t("table.totalMemory", { memory: humanFileSize(totals.memory) })}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" style={{ whiteSpace: "nowrap" }}>
|
||||
{t("table.updated", { when: relativeTime })}
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -529,6 +529,16 @@ export default function DownloadClientsWidget({
|
||||
},
|
||||
{
|
||||
...columnsDefBase({ key: "time", showHeader: true }),
|
||||
sortingFn: (rowA, rowB) => {
|
||||
const timeA = rowA.getValue<ExtendedDownloadClientItem["time"]>("time");
|
||||
const timeB = rowB.getValue<ExtendedDownloadClientItem["time"]>("time");
|
||||
// Map time values to canonical sort keys:
|
||||
// positive = ETA in ms (sort as-is, smaller = sooner)
|
||||
// 0 = stalled/infinite (sort last in ascending = Infinity)
|
||||
// negative = time since completion in ms (sort first in ascending = very negative)
|
||||
const toSortKey = (time: number) => (time === 0 ? Infinity : time);
|
||||
return toSortKey(timeA) - toSortKey(timeB);
|
||||
},
|
||||
Cell: ({ cell }) => {
|
||||
const time = cell.getValue<ExtendedDownloadClientItem["time"]>();
|
||||
return time === 0 ? <IconInfinity size={16} /> : <Text size="xs">{dayjs().add(time).fromNow()}</Text>;
|
||||
|
||||
@@ -95,6 +95,12 @@ export const { definition, componentLoader } = createWidgetDefinition("healthMon
|
||||
return !integrationKinds.includes("proxmox");
|
||||
},
|
||||
},
|
||||
defaultTab: {
|
||||
shouldHide(_, integrationKinds) {
|
||||
// The system/cluster tabs only render for Proxmox, so the default tab choice does nothing otherwise
|
||||
return !integrationKinds.includes("proxmox");
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Progress,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import {
|
||||
@@ -33,6 +32,7 @@ import duration from "dayjs/plugin/duration";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { humanFileSize } from "@homarr/common";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
@@ -91,7 +91,7 @@ export const SystemHealthMonitoring = ({
|
||||
|
||||
return (
|
||||
<Stack h="100%" gap="sm" className="health-monitoring">
|
||||
{healthData.map(({ integrationId, integrationName, healthInfo, updatedAt }) => {
|
||||
{healthData.map(({ integrationId, integrationName, healthInfo }) => {
|
||||
const disksData = matchFileSystemAndSmart(healthInfo.fileSystem, healthInfo.smart);
|
||||
const memoryUsage = formatMemoryUsage(healthInfo.memAvailableInBytes, healthInfo.memUsedInBytes);
|
||||
return (
|
||||
@@ -196,11 +196,6 @@ export const SystemHealthMonitoring = ({
|
||||
<GpuRing key={gpu.gpuId} gpu={gpu} isTiny={isTiny} fahrenheit={options.fahrenheit} />
|
||||
))}
|
||||
</Flex>
|
||||
{
|
||||
<Text className="health-monitoring-status-update-time" c="dimmed" size="xs" ta="center">
|
||||
{t("widget.healthMonitoring.popover.lastSeen", { lastSeen: dayjs(updatedAt).fromNow() })}
|
||||
</Text>
|
||||
}
|
||||
{options.fileSystem &&
|
||||
disksData.map((disk) => {
|
||||
return (
|
||||
@@ -214,7 +209,7 @@ export const SystemHealthMonitoring = ({
|
||||
radius={board.itemRadius}
|
||||
p="xs"
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Stack gap="xs">
|
||||
<Group
|
||||
className="health-monitoring-disk-status"
|
||||
justify="space-between"
|
||||
@@ -228,14 +223,16 @@ export const SystemHealthMonitoring = ({
|
||||
{disk.deviceName}
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconTemperature className="health-monitoring-disk-temperature-icon" size="1rem" />
|
||||
<Text className="health-monitoring-disk-temperature-value" size="xs">
|
||||
{options.fahrenheit
|
||||
? `${(disk.temperature * 1.8 + 32).toFixed(1)}°F`
|
||||
: `${disk.temperature}°C`}
|
||||
</Text>
|
||||
</Group>
|
||||
{disk.temperature !== null && (
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconTemperature className="health-monitoring-disk-temperature-icon" size="1rem" />
|
||||
<Text className="health-monitoring-disk-temperature-value" size="xs">
|
||||
{options.fahrenheit
|
||||
? `${(disk.temperature * 1.8 + 32).toFixed(1)}°F`
|
||||
: `${disk.temperature}°C`}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconFileReport className="health-monitoring-disk-status-icon" size="1rem" />
|
||||
<Text className="health-monitoring-disk-status-value" size="xs">
|
||||
@@ -243,37 +240,26 @@ export const SystemHealthMonitoring = ({
|
||||
</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
<Progress.Root className="health-monitoring-disk-use" radius={board.itemRadius} h="md">
|
||||
<Tooltip label={disk.used}>
|
||||
<Progress.Section
|
||||
value={disk.percentage}
|
||||
color={progressColor(disk.percentage)}
|
||||
className="health-monitoring-disk-use-percentage"
|
||||
>
|
||||
<Progress.Label className="health-monitoring-disk-use-value" fz="xs">
|
||||
{t("widget.healthMonitoring.popover.used")}
|
||||
</Progress.Label>
|
||||
</Progress.Section>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
label={
|
||||
Number(disk.available) / 1024 ** 4 >= 1
|
||||
? `${(Number(disk.available) / 1024 ** 4).toFixed(2)} TiB`
|
||||
: `${(Number(disk.available) / 1024 ** 3).toFixed(2)} GiB`
|
||||
}
|
||||
>
|
||||
<Progress.Section
|
||||
className="health-monitoring-disk-available-percentage"
|
||||
value={100 - disk.percentage}
|
||||
color="default"
|
||||
>
|
||||
<Progress.Label className="health-monitoring-disk-available-value" fz="xs">
|
||||
{t("widget.healthMonitoring.popover.available")}
|
||||
</Progress.Label>
|
||||
</Progress.Section>
|
||||
</Tooltip>
|
||||
<Progress.Root className="health-monitoring-disk-use" radius={board.itemRadius} size="lg">
|
||||
<Progress.Section
|
||||
value={disk.percentage}
|
||||
color={progressColor(disk.percentage)}
|
||||
className="health-monitoring-disk-use-percentage"
|
||||
/>
|
||||
<Progress.Section
|
||||
className="health-monitoring-disk-available-percentage"
|
||||
value={100 - disk.percentage}
|
||||
color="default"
|
||||
/>
|
||||
</Progress.Root>
|
||||
<Group justify="space-between" gap={8} wrap="nowrap">
|
||||
<Text className="health-monitoring-disk-use-value" size="xs" c="dimmed">
|
||||
{t("widget.healthMonitoring.popover.used")} {formatFileSize(disk.used)}
|
||||
</Text>
|
||||
<Text className="health-monitoring-disk-available-value" size="xs" c="dimmed">
|
||||
{formatFileSize(disk.available)} {t("widget.healthMonitoring.popover.available")}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
@@ -307,6 +293,13 @@ export const progressColor = (percentage: number) => {
|
||||
else return "red";
|
||||
};
|
||||
|
||||
// Some integrations report file sizes as raw bytes (e.g. TrueNAS, Glances) while others pre-format
|
||||
// them (e.g. Unraid, dashdot). Format the former and pass the latter through untouched.
|
||||
const formatFileSize = (value: string) => {
|
||||
const bytes = Number(value);
|
||||
return Number.isFinite(bytes) ? humanFileSize(Math.round(bytes)) : value;
|
||||
};
|
||||
|
||||
interface FileSystem {
|
||||
deviceName: string;
|
||||
used: string;
|
||||
@@ -331,7 +324,7 @@ export const matchFileSystemAndSmart = (fileSystems: FileSystem[], smartData: Sm
|
||||
used: fileSystem.used,
|
||||
available: fileSystem.available,
|
||||
percentage: fileSystem.percentage,
|
||||
temperature: smartDisk?.temperature ?? 0,
|
||||
temperature: smartDisk?.temperature ?? null,
|
||||
overallStatus: smartDisk?.overallStatus ?? "",
|
||||
};
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Card, Group, Stack, useMantineColorScheme } from "@mantine/core";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Box, Card, Group, Stack, Tooltip, useMantineColorScheme } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
@@ -31,6 +32,90 @@ const getDisplayText = (item: { used: string; available: string; percentage: num
|
||||
}
|
||||
};
|
||||
|
||||
interface SystemDiskCardProps {
|
||||
deviceName: string;
|
||||
percentage: number;
|
||||
displayText: string;
|
||||
temperature: number | null | undefined;
|
||||
healthy: boolean;
|
||||
showBackgroundBar: boolean;
|
||||
}
|
||||
|
||||
const SystemDiskCard = ({
|
||||
deviceName,
|
||||
percentage,
|
||||
displayText,
|
||||
temperature,
|
||||
healthy,
|
||||
showBackgroundBar,
|
||||
}: SystemDiskCardProps) => {
|
||||
const board = useRequiredBoard();
|
||||
const scheme = useMantineColorScheme();
|
||||
const t = useI18n();
|
||||
const valueRef = useRef<HTMLParagraphElement>(null);
|
||||
const [valueFits, setValueFits] = useState(true);
|
||||
|
||||
// When the card is squeezed (small widget), the value line is clipped by the card's `overflow: hidden`.
|
||||
// Detect that and hide it, surfacing the value in a tooltip instead. The value keeps its layout space
|
||||
// (visibility, not display) so toggling it does not change the measurement and flap. The ref lives on
|
||||
// the value (not the Card) so the Tooltip can attach its own ref to the Card.
|
||||
useEffect(() => {
|
||||
const value = valueRef.current;
|
||||
const card = value?.offsetParent as HTMLElement | null; // the position:relative Card
|
||||
if (!value || !card) return;
|
||||
const measure = () => {
|
||||
setValueFits(value.getBoundingClientRect().bottom <= card.getBoundingClientRect().bottom + 1);
|
||||
};
|
||||
measure();
|
||||
const observer = new ResizeObserver(measure);
|
||||
observer.observe(card);
|
||||
return () => observer.disconnect();
|
||||
}, [displayText, healthy]);
|
||||
|
||||
const unhealthyLabel = t("widget.systemDisks.status.unhealthy");
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={healthy ? displayText : `${displayText} (${unhealthyLabel})`}
|
||||
disabled={valueFits}
|
||||
position="top"
|
||||
withinPortal
|
||||
>
|
||||
<Card
|
||||
radius={board.itemRadius}
|
||||
py={"xs"}
|
||||
bg={scheme.colorScheme === "dark" ? "dark.7" : "gray.1"}
|
||||
style={{ overflow: "hidden", position: "relative" }}
|
||||
>
|
||||
<Group justify="space-between" style={{ zIndex: 1 }}>
|
||||
<div>
|
||||
<p style={{ margin: 0 }}>
|
||||
<b>{deviceName}</b>
|
||||
</p>
|
||||
<p ref={valueRef} style={{ margin: 0, visibility: valueFits ? "visible" : "hidden" }}>
|
||||
<span>{displayText}</span>
|
||||
{!healthy && <span style={{ marginLeft: 5 }}>{unhealthyLabel}</span>}
|
||||
</p>
|
||||
</div>
|
||||
<div>{temperature ? <p style={{ margin: 0 }}>{temperature}°C</p> : null}</div>
|
||||
</Group>
|
||||
<Box
|
||||
bg={healthy ? "green" : "red"}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: `${percentage}%`,
|
||||
height: "100%",
|
||||
zIndex: 0,
|
||||
display: showBackgroundBar ? "block" : "none",
|
||||
}}
|
||||
></Box>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default function SystemResources({ integrationIds, options }: WidgetComponentProps<"systemDisks">) {
|
||||
const queryInput = { integrationIds };
|
||||
const { data = [] } = clientApi.widget.healthMonitoring.getSystemHealthStatus.useQuery(queryInput, {
|
||||
@@ -38,10 +123,6 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo
|
||||
});
|
||||
const utils = clientApi.useUtils();
|
||||
|
||||
const board = useRequiredBoard();
|
||||
const scheme = useMantineColorScheme();
|
||||
const t = useI18n();
|
||||
|
||||
clientApi.widget.healthMonitoring.subscribeSystemHealthStatus.useSubscription(queryInput, {
|
||||
onData(data) {
|
||||
utils.widget.healthMonitoring.getSystemHealthStatus.setData(queryInput, (oldData) =>
|
||||
@@ -63,45 +144,17 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo
|
||||
<Stack gap="xs" p="xs" h="100%">
|
||||
{fileSystem.map((item) => {
|
||||
const smartItem = smart.find((smart) => smart.deviceName === item.deviceName);
|
||||
const healthy = smartItem?.healthy ?? true; // fall back to healthy if no information is available
|
||||
|
||||
return (
|
||||
<Card
|
||||
radius={board.itemRadius}
|
||||
py={"xs"}
|
||||
bg={scheme.colorScheme === "dark" ? "dark.7" : "gray.1"}
|
||||
<SystemDiskCard
|
||||
key={`disk-${item.deviceName}`}
|
||||
style={{ overflow: "hidden", position: "relative" }}
|
||||
>
|
||||
<Group justify="space-between" style={{ zIndex: 1 }}>
|
||||
<div>
|
||||
<p style={{ margin: 0 }}>
|
||||
<b>{item.deviceName}</b>
|
||||
</p>
|
||||
<p style={{ margin: 0 }}>
|
||||
<span>{getDisplayText(item, options.displayMode)}</span>
|
||||
{!healthy && <span style={{ marginLeft: 5 }}>{t("widget.systemDisks.status.unhealthy")}</span>}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{smartItem?.temperature && options.showTemperatureIfAvailable ? (
|
||||
<p style={{ margin: 0 }}>{smartItem.temperature}°C</p>
|
||||
) : null}
|
||||
</div>
|
||||
</Group>
|
||||
<Box
|
||||
bg={healthy ? "green" : "red"}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: `${item.percentage}%`,
|
||||
height: "100%",
|
||||
zIndex: 0,
|
||||
display: options.showBackgroundBar ? "block" : "none",
|
||||
}}
|
||||
></Box>
|
||||
</Card>
|
||||
deviceName={item.deviceName}
|
||||
percentage={item.percentage}
|
||||
displayText={getDisplayText(item, options.displayMode)}
|
||||
temperature={options.showTemperatureIfAvailable ? smartItem?.temperature : undefined}
|
||||
healthy={smartItem?.healthy ?? true} // fall back to healthy if no information is available
|
||||
showBackgroundBar={options.showBackgroundBar}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
|
||||
Generated
+912
-1426
File diff suppressed because it is too large.
Load diff
+30
-29
@@ -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,11 +88,12 @@ 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
|
||||
"@umami/node": ^0.4.0
|
||||
posthog-js: ^1.255.0
|
||||
posthog-node: ^4.17.0
|
||||
"@vitejs/plugin-react": ^6.0.2
|
||||
"@vitest/coverage-v8": ^4.1.8
|
||||
"@vitest/ui": ^4.1.8
|
||||
@@ -130,16 +131,16 @@ catalog:
|
||||
glob: ^13.0.6
|
||||
ical.js: ^2.2.1
|
||||
ioredis: 5.11.1
|
||||
isomorphic-dompurify: ^3.16.0
|
||||
jotai: ^2.20.0
|
||||
isomorphic-dompurify: ^3.17.0
|
||||
jotai: ^2.20.1
|
||||
jsdom: ^29.1.1
|
||||
json5: ^2.2.3
|
||||
jsonpath-plus: ^10.4.0
|
||||
ldapts: 8.1.8
|
||||
lodash.clonedeep: ^4.5.0
|
||||
lucide-react: ^1.17.0
|
||||
"mantine-contextmenu": ^9.2.1
|
||||
mantine-datatable: ^9.2.2
|
||||
lucide-react: ^1.18.0
|
||||
"mantine-contextmenu": ^9.3.0
|
||||
mantine-datatable: ^9.3.0
|
||||
mantine-react-table: npm:mantine-react-table-open@9.0.3
|
||||
mcp-handler: ^1.1.0
|
||||
maria2: ^0.4.1
|
||||
@@ -163,12 +164,12 @@ catalog:
|
||||
react-quill-new: ^3.8.3
|
||||
react-simple-code-editor: ^0.14.1
|
||||
recharts: ^3.8.1
|
||||
sass: ^1.100.0
|
||||
sass: ^1.101.0
|
||||
semantic-release: ^25.0.5
|
||||
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
|
||||
@@ -216,7 +217,7 @@ overrides:
|
||||
"prismjs@<1.30.0": ">=1.30.0"
|
||||
"proxmox-api>undici": "7.27.1"
|
||||
"react-is": "^19.2.7"
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.61.1"
|
||||
"rollup@>=4.0.0 <4.22.4": ">=4.62.0"
|
||||
"sha.js@<=2.4.11": ">=2.4.12"
|
||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.2"
|
||||
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.2"
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontentLine truncated
|
||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontentLine truncated
|
||||
@@ -1 +1 @@
|
||||
[{"id":"zaxon793-jy8gpl0v-d89pbrkd-emwl5v04","name":"Mike","slug":"guest-5da646e8","imageUrl":"https://images.opencollective.com/guest-5da646e8/avatar.png"},{"id":"ywz9j4av-god8pgwg-db36mr35-nxklb0e7","name":"Guest","slug":"guest-554ec68f","imageUrl":"https://images.opencollective.com/guest-554ec68f/avatar.png"},{"id":"mlo94zn7-x08dpo09-xlxpewga-3vjbrky5","name":"Robert Hermann","slug":"robert-hermann","imageUrl":"https://images.opencollective.com/robert-hermann/avatar.png"},{"id":"zaxon793-jy8gpl0n-8r3pbrkd-emwl5v04","name":"Sebastian Raubuch","slug":"sebastian-raubuch","imageUrl":"https://images.opencollective.com/sebastian-raubuch/avatar.png"},{"id":"nmlo94zn-7x08dpod-ykm6ewga-3vjbrky5","name":"Didier LUTHI","slug":"didier-luthi","imageUrl":"https://images.opencollective.com/didier-luthi/avatar.png"},{"id":"vedj9wro-z3a56dgk-z3z67blg-8x4m0ykn","name":"Guest","slug":"guest-b1459a68","imageUrl":"https://images.opencollective.com/guest-b1459a68/avatar.png"},{"id":"gm9bnk80-437xqry4-8vepvzeo-ljdayw5r","name":"Mohister","slug":"mohister","imageUrl":"https://images.opencollective.com/mohister/1d8a592/avatar.png"},{"id":"vrgbk357-l4x96e70-m0apomew-a0jdyzn8","name":"sidens","slug":"sidens","imageUrl":"https://images.opencollective.com/sidens/avatar.png"},{"id":"mlo94zn7-x08dpo0d-l7epewga-3vjbrky5","name":"Chris","slug":"guest-937065bb","imageUrl":"https://images.opencollective.com/guest-937065bb/avatar.png"},{"id":"k9mbw7y4-8r3zq3ol-7zlq0ej5-lavnodgx","name":"Nick W","slug":"guest-7501840a","imageUrl":"https://images.opencollective.com/guest-7501840a/avatar.png"},{"id":"e0adkjrv-8xwm69o8-5al67z5n-4l93bgoy","name":"Jeppedy","slug":"guest-ab78e46a","imageUrl":"https://images.opencollective.com/guest-ab78e46a/avatar.png"},{"id":"a47byg9n-xozdp8xo-jgm6mjlv-03rek5w8","name":"naru","slug":"guest-02fd264a","imageUrl":"https://images.opencollective.com/guest-02fd264a/avatar.png"},{"id":"3k0exgzn-m8yj64g3-ll865wao-9r7b4dlv","name":"Jared Hocutt","slug":"jared-hocutt","imageUrl":"https://images.opencollective.com/jared-hocutt/avatar.png"},{"id":"eng0kzdy-vor4pz8l-exopbma8-37xlw95j","name":"Incognito","slug":"user-e63f4a5b","imageUrl":"https://images.opencollective.com/user-e63f4a5b/avatar.png"},{"id":"8k03reyd-5agmq50v-8vyplbwo-z7j4nxv9","name":"dazi","slug":"guest-11663a46","imageUrl":"https://images.opencollective.com/guest-11663a46/avatar.png"},{"id":"vedj9wro-z3a56dgd-zwl67blg-8x4m0ykn","name":"ZoopaMario","slug":"zoopamario","imageUrl":"https://images.opencollective.com/zoopamario/bea900d/avatar.png"},{"id":"a47byg9n-xozdp8xe-xmb6mjlv-03rek5w8","name":"FlamebergUA","slug":"guest-a59fd500","imageUrl":"https://images.opencollective.com/guest-a59fd500/avatar.png"},{"id":"8k03reyd-5agmq50y-kzoplbwo-z7j4nxv9","name":"StLCards","slug":"guest-e1de33dc","imageUrl":"https://images.opencollective.com/guest-e1de33dc/avatar.png"},{"id":"ejoxl3az-45w9pa54-dk46y870-mgkbrvdn","name":"Varadha","slug":"guest-6f68eed9","imageUrl":"https://images.opencollective.com/guest-6f68eed9/avatar.png"},{"id":"jrkx5lmn-v904qjl0-3oz68bwa-7zdygoe3","name":"Guest","slug":"guest-6da419b5","imageUrl":"https://images.opencollective.com/guest-6da419b5/avatar.png"},{"id":"mywxoz34-09rl6k3z-lmlqvenb-dj7gk85a","name":"Guest","slug":"guest-122d7e0a","imageUrl":"https://images.opencollective.com/guest-122d7e0a/avatar.png"},{"id":"3z8arxve-ymko60vm-3eaqgl5n-bj9w704d","name":"Ricardo","slug":"ricardo34","imageUrl":"https://images.opencollective.com/ricardo34/avatar.png"},{"id":"e0adkjrv-8xwm69or-reo67z5n-4l93bgoy","name":"GoM","slug":"guest-3944feab","imageUrl":"https://images.opencollective.com/guest-3944feab/avatar.png"},{"id":"gm9bnk80-437xqrnr-r5o6vzeo-ljdayw5r","name":"Brent Maxwell","slug":"brentmaxwell","imageUrl":"https://images.opencollective.com/brentmaxwell/avatar.png"},{"id":"gnxdwzj3-le5mpwzd-ll0py8rv-bko04a97","name":"Eric G","slug":"guest-478270f1","imageUrl":"https://images.opencollective.com/guest-478270f1/avatar.png"},{"id":"v349mrwg-z75lpyjo-ez8pa08d-jeybknox","name":"ninja","slug":"guest-0bb6ba36","imageUrl":"https://images.opencollective.com/guest-0bb6ba36/avatar.png"},{"id":"ywz9j4av-god8pgw0-53y6mr35-nxklb0e7","name":"s11","slug":"guest-6e9a2588","imageUrl":"https://images.opencollective.com/guest-6e9a2588/avatar.png"},{"id":"mlo94zn7-x08dpo30-v73pewga-3vjbrky5","name":"Incognito","slug":"user-cbad11af","imageUrl":"https://images.opencollective.com/user-cbad11af/avatar.png"},{"id":"v349mrwg-z75lpy7l-yzgpa08d-jeybknox","name":"Ronald Simmons","slug":"ronald-simmons","imageUrl":"https://images.opencollective.com/ronald-simmons/avatar.png"},{"id":"7ywz9j4a-vgod8pgl-me9qmr35-nxklb0e7","name":"Joel","slug":"guest-3376ab02","imageUrl":"https://images.opencollective.com/guest-3376ab02/avatar.png"},{"id":"v349mrwg-z75lpyjw-kxzpa08d-jeybknox","name":"Gokay Yamankurt","slug":"gokay-yamankurt","imageUrl":"https://images.opencollective.com/gokay-yamankurt/avatar.png"},{"id":"v349mrwg-z75lpyjv-59rpa08d-jeybknox","name":"Rhiz3K","slug":"rhiz3k","imagLine truncated
|
||||
[{"id":"zaxon793-jy8gpl0v-d89pbrkd-emwl5v04","name":"Mike","slug":"guest-5da646e8","imageUrl":"https://images.opencollective.com/guest-5da646e8/avatar.png"},{"id":"ywz9j4av-god8pgwg-db36mr35-nxklb0e7","name":"Guest","slug":"guest-554ec68f","imageUrl":"https://images.opencollective.com/guest-554ec68f/avatar.png"},{"id":"mlo94zn7-x08dpo09-xlxpewga-3vjbrky5","name":"Robert Hermann","slug":"robert-hermann","imageUrl":"https://images.opencollective.com/robert-hermann/avatar.png"},{"id":"zaxon793-jy8gpl0n-8r3pbrkd-emwl5v04","name":"Sebastian Raubuch","slug":"sebastian-raubuch","imageUrl":"https://images.opencollective.com/sebastian-raubuch/avatar.png"},{"id":"nmlo94zn-7x08dpod-ykm6ewga-3vjbrky5","name":"Didier LUTHI","slug":"didier-luthi","imageUrl":"https://images.opencollective.com/didier-luthi/avatar.png"},{"id":"vedj9wro-z3a56dgk-z3z67blg-8x4m0ykn","name":"Guest","slug":"guest-b1459a68","imageUrl":"https://images.opencollective.com/guest-b1459a68/avatar.png"},{"id":"gm9bnk80-437xqry4-8vepvzeo-ljdayw5r","name":"Mohister","slug":"mohister","imageUrl":"https://images.opencollective.com/mohister/1d8a592/avatar.png"},{"id":"vrgbk357-l4x96e70-m0apomew-a0jdyzn8","name":"sidens","slug":"sidens","imageUrl":"https://images.opencollective.com/sidens/avatar.png"},{"id":"mlo94zn7-x08dpo0d-l7epewga-3vjbrky5","name":"Chris","slug":"guest-937065bb","imageUrl":"https://images.opencollective.com/guest-937065bb/avatar.png"},{"id":"k9mbw7y4-8r3zq3ol-7zlq0ej5-lavnodgx","name":"Nick W","slug":"guest-7501840a","imageUrl":"https://images.opencollective.com/guest-7501840a/avatar.png"},{"id":"e0adkjrv-8xwm69o8-5al67z5n-4l93bgoy","name":"Jeppedy","slug":"guest-ab78e46a","imageUrl":"https://images.opencollective.com/guest-ab78e46a/avatar.png"},{"id":"a47byg9n-xozdp8xo-jgm6mjlv-03rek5w8","name":"naru","slug":"guest-02fd264a","imageUrl":"https://images.opencollective.com/guest-02fd264a/avatar.png"},{"id":"3k0exgzn-m8yj64g3-ll865wao-9r7b4dlv","name":"Jared Hocutt","slug":"jared-hocutt","imageUrl":"https://images.opencollective.com/jared-hocutt/avatar.png"},{"id":"eng0kzdy-vor4pz8l-exopbma8-37xlw95j","name":"Incognito","slug":"user-e63f4a5b","imageUrl":"https://images.opencollective.com/user-e63f4a5b/avatar.png"},{"id":"8k03reyd-5agmq50v-8vyplbwo-z7j4nxv9","name":"dazi","slug":"guest-11663a46","imageUrl":"https://images.opencollective.com/guest-11663a46/avatar.png"},{"id":"vedj9wro-z3a56dgd-zwl67blg-8x4m0ykn","name":"ZoopaMario","slug":"zoopamario","imageUrl":"https://images.opencollective.com/zoopamario/bea900d/avatar.png"},{"id":"a47byg9n-xozdp8xe-xmb6mjlv-03rek5w8","name":"FlamebergUA","slug":"guest-a59fd500","imageUrl":"https://images.opencollective.com/guest-a59fd500/avatar.png"},{"id":"8k03reyd-5agmq50y-kzoplbwo-z7j4nxv9","name":"StLCards","slug":"guest-e1de33dc","imageUrl":"https://images.opencollective.com/guest-e1de33dc/avatar.png"},{"id":"ejoxl3az-45w9pa54-dk46y870-mgkbrvdn","name":"Varadha","slug":"guest-6f68eed9","imageUrl":"https://images.opencollective.com/guest-6f68eed9/avatar.png"},{"id":"jrkx5lmn-v904qjl0-3oz68bwa-7zdygoe3","name":"Guest","slug":"guest-6da419b5","imageUrl":"https://images.opencollective.com/guest-6da419b5/avatar.png"},{"id":"mywxoz34-09rl6k3z-lmlqvenb-dj7gk85a","name":"Guest","slug":"guest-122d7e0a","imageUrl":"https://images.opencollective.com/guest-122d7e0a/avatar.png"},{"id":"3z8arxve-ymko60vm-3eaqgl5n-bj9w704d","name":"Ricardo","slug":"ricardo34","imageUrl":"https://images.opencollective.com/ricardo34/avatar.png"},{"id":"e0adkjrv-8xwm69or-reo67z5n-4l93bgoy","name":"GoM","slug":"guest-3944feab","imageUrl":"https://images.opencollective.com/guest-3944feab/avatar.png"},{"id":"gm9bnk80-437xqrnr-r5o6vzeo-ljdayw5r","name":"Brent Maxwell","slug":"brentmaxwell","imageUrl":"https://images.opencollective.com/brentmaxwell/avatar.png"},{"id":"gnxdwzj3-le5mpwzd-ll0py8rv-bko04a97","name":"Eric G","slug":"guest-478270f1","imageUrl":"https://images.opencollective.com/guest-478270f1/avatar.png"},{"id":"v349mrwg-z75lpyjo-ez8pa08d-jeybknox","name":"ninja","slug":"guest-0bb6ba36","imageUrl":"https://images.opencollective.com/guest-0bb6ba36/avatar.png"},{"id":"ywz9j4av-god8pgw0-53y6mr35-nxklb0e7","name":"s11","slug":"guest-6e9a2588","imageUrl":"https://images.opencollective.com/guest-6e9a2588/avatar.png"},{"id":"mlo94zn7-x08dpo30-v73pewga-3vjbrky5","name":"Incognito","slug":"user-cbad11af","imageUrl":"https://images.opencollective.com/user-cbad11af/avatar.png"},{"id":"v349mrwg-z75lpy7l-yzgpa08d-jeybknox","name":"Ronald Simmons","slug":"ronald-simmons","imageUrl":"https://images.opencollective.com/ronald-simmons/avatar.png"},{"id":"7ywz9j4a-vgod8pgl-me9qmr35-nxklb0e7","name":"Joel","slug":"guest-3376ab02","imageUrl":"https://images.opencollective.com/guest-3376ab02/avatar.png"},{"id":"v349mrwg-z75lpyjw-kxzpa08d-jeybknox","name":"Gokay Yamankurt","slug":"gokay-yamankurt","imageUrl":"https://images.opencollective.com/gokay-yamankurt/avatar.png"},{"id":"v349mrwg-z75lpyjv-59rpa08d-jeybknox","name":"Rhiz3K","slug":"rhiz3k","imagLine truncated
|
||||
@@ -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":"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/aaa1f083e04c572f953fde4563171e4a.jpeg"},{"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":"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/aaa1f083e04c572f953fde4563171e4a.jpeg"},{"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":"https://crowLine truncated
|
||||
Reference in new issue
Block a user