chore(release): automatic release v1.49.0

This commit is contained in:
homarr-releases[bot]
2026-01-02 19:13:35 +00:00
committed by GitHub
38 changed files with 1537 additions and 966 deletions

View File

@@ -56,16 +56,16 @@
"@mantine/tiptap": "^8.3.10", "@mantine/tiptap": "^8.3.10",
"@million/lint": "1.0.14", "@million/lint": "1.0.14",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.90.12", "@tanstack/react-query": "^5.90.14",
"@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-query-devtools": "^5.91.2",
"@tanstack/react-query-next-experimental": "^5.91.0", "@tanstack/react-query-next-experimental": "^5.91.0",
"@trpc/client": "^11.8.1", "@trpc/client": "^11.8.1",
"@trpc/next": "^11.8.1", "@trpc/next": "^11.8.1",
"@trpc/react-query": "^11.8.1", "@trpc/react-query": "^11.8.1",
"@trpc/server": "^11.8.1", "@trpc/server": "^11.8.1",
"@xterm/addon-canvas": "^0.7.0", "@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "0.10.0", "@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^6.0.0",
"babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-compiler": "^1.0.0",
"chroma-js": "^3.2.0", "chroma-js": "^3.2.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -73,10 +73,10 @@
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"flag-icons": "^7.5.0", "flag-icons": "^7.5.0",
"glob": "^13.0.0", "glob": "^13.0.0",
"isomorphic-dompurify": "^2.34.0", "isomorphic-dompurify": "^2.35.0",
"jotai": "^2.16.0", "jotai": "^2.16.1",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "16.1.0", "next": "16.1.1",
"postcss-preset-mantine": "^1.18.0", "postcss-preset-mantine": "^1.18.0",
"prismjs": "^1.30.0", "prismjs": "^1.30.0",
"react": "19.2.3", "react": "19.2.3",

View File

@@ -76,6 +76,7 @@ const createColumns = (
accessorKey: "ports", accessorKey: "ports",
header: t("docker.field.ports.label"), header: t("docker.field.ports.label"),
Cell({ cell }) { Cell({ cell }) {
if (!cell.row.original.ports.length) return null;
return ( return (
<OverflowBadge overflowCount={1} data={cell.row.original.ports.map((port) => port.PrivatePort.toString())} /> <OverflowBadge overflowCount={1} data={cell.row.original.ports.map((port) => port.PrivatePort.toString())} />
); );

View File

@@ -49,7 +49,7 @@
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/node": "^24.10.4", "@types/node": "^24.10.4",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"esbuild": "^0.27.1", "esbuild": "^0.27.2",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"tsx": "4.20.4", "tsx": "4.20.4",

View File

@@ -34,7 +34,7 @@
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"esbuild": "^0.27.1", "esbuild": "^0.27.2",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"typescript": "^5.9.3" "typescript": "^5.9.3"

View File

@@ -4,10 +4,10 @@
"dependencies": { "dependencies": {
"better-sqlite3": "^12.5.0" "better-sqlite3": "^12.5.0"
}, },
"packageManager": "pnpm@10.26.1", "packageManager": "pnpm@10.26.2",
"engines": { "engines": {
"node": ">=24.12.0", "node": ">=24.12.0",
"pnpm": ">=10.26.1" "pnpm": ">=10.26.2"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [

View File

@@ -314,6 +314,13 @@
<br/> <br/>
<p align="center">Unifi<br/>Controller</p> <p align="center">Unifi<br/>Controller</p>
</a> </a>
</td>
<td align="center">
<a href="https://homarr.dev/docs/integrations/unraid" target="_blank" rel="noreferrer noopener">
<img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/unraid.svg" alt="Unraid" width="90" height="90" />
<br/>
<p align="center">Unraid</p>
</a>
</td></tr> </td></tr>
</tbody> </tbody>
</table> </table>

View File

@@ -42,27 +42,27 @@
"@semantic-release/github": "^12.0.2", "@semantic-release/github": "^12.0.2",
"@semantic-release/npm": "^13.1.3", "@semantic-release/npm": "^13.1.3",
"@semantic-release/release-notes-generator": "^14.1.0", "@semantic-release/release-notes-generator": "^14.1.0",
"@testcontainers/redis": "^11.10.0", "@testcontainers/redis": "^11.11.0",
"@turbo/gen": "^2.7.1", "@turbo/gen": "^2.7.2",
"@vitejs/plugin-react": "^5.1.2", "@vitejs/plugin-react": "^5.1.2",
"@vitest/coverage-v8": "^4.0.16", "@vitest/coverage-v8": "^4.0.16",
"@vitest/ui": "^4.0.16", "@vitest/ui": "^4.0.16",
"conventional-changelog-conventionalcommits": "^9.1.0", "conventional-changelog-conventionalcommits": "^9.1.0",
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"jsdom": "^27.3.0", "jsdom": "^27.4.0",
"json5": "^2.2.3", "json5": "^2.2.3",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"semantic-release": "^25.0.2", "semantic-release": "^25.0.2",
"testcontainers": "^11.10.0", "testcontainers": "^11.11.0",
"turbo": "^2.7.1", "turbo": "^2.7.2",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^6.0.3",
"vitest": "^4.0.16" "vitest": "^4.0.16"
}, },
"packageManager": "pnpm@10.26.1", "packageManager": "pnpm@10.26.2",
"engines": { "engines": {
"node": ">=24.12.0", "node": ">=24.12.0",
"pnpm": ">=10.26.1" "pnpm": ">=10.26.2"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
@@ -82,9 +82,9 @@
"axios@>=1.0.0 <1.8.2": ">=1.13.2", "axios@>=1.0.0 <1.8.2": ">=1.13.2",
"brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1", "brace-expansion@>=2.0.0 <=2.0.1": ">=4.0.1",
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1", "brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
"esbuild@<=0.24.2": ">=0.27.1", "esbuild@<=0.24.2": ">=0.27.2",
"form-data@>=4.0.0 <4.0.4": ">=4.0.5", "form-data@>=4.0.0 <4.0.4": ">=4.0.5",
"hono@<4.6.5": ">=4.11.1", "hono@<4.6.5": ">=4.11.3",
"linkifyjs@<4.3.2": ">=4.3.2", "linkifyjs@<4.3.2": ">=4.3.2",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6", "nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
"prismjs@<1.30.0": ">=1.30.0", "prismjs@<1.30.0": ">=1.30.0",

View File

@@ -41,13 +41,13 @@
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@kubernetes/client-node": "^1.4.0", "@kubernetes/client-node": "^1.4.0",
"@tanstack/react-query": "^5.90.12", "@tanstack/react-query": "^5.90.14",
"@trpc/client": "^11.8.1", "@trpc/client": "^11.8.1",
"@trpc/react-query": "^11.8.1", "@trpc/react-query": "^11.8.1",
"@trpc/server": "^11.8.1", "@trpc/server": "^11.8.1",
"@trpc/tanstack-react-query": "^11.8.1", "@trpc/tanstack-react-query": "^11.8.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"next": "16.1.0", "next": "16.1.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"superjson": "2.2.6", "superjson": "2.2.6",

View File

@@ -9,7 +9,7 @@ import { createTRPCRouter, publicProcedure } from "../../trpc";
export const healthMonitoringRouter = createTRPCRouter({ export const healthMonitoringRouter = createTRPCRouter({
getSystemHealthStatus: publicProcedure getSystemHealthStatus: publicProcedure
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "mock")) .concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "mock"))
.query(async ({ ctx }) => { .query(async ({ ctx }) => {
return await Promise.all( return await Promise.all(
ctx.integrations.map(async (integration) => { ctx.integrations.map(async (integration) => {
@@ -26,7 +26,7 @@ export const healthMonitoringRouter = createTRPCRouter({
); );
}), }),
subscribeSystemHealthStatus: publicProcedure subscribeSystemHealthStatus: publicProcedure
.concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "mock")) .concat(createManyIntegrationMiddleware("query", "openmediavault", "dashDot", "truenas", "unraid", "mock"))
.subscription(({ ctx }) => { .subscription(({ ctx }) => {
return observable<{ integrationId: string; healthInfo: SystemHealthMonitoring; timestamp: Date }>((emit) => { return observable<{ integrationId: string; healthInfo: SystemHealthMonitoring; timestamp: Date }>((emit) => {
const unsubscribes: (() => void)[] = []; const unsubscribes: (() => void)[] = [];

View File

@@ -32,8 +32,8 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"cookies": "^0.9.1", "cookies": "^0.9.1",
"ldapts": "8.0.30", "ldapts": "8.0.35",
"next": "16.1.0", "next": "16.1.1",
"next-auth": "5.0.0-beta.30", "next-auth": "5.0.0-beta.30",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",

View File

@@ -34,7 +34,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"esbuild": "^0.27.1", "esbuild": "^0.27.2",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }

View File

@@ -29,7 +29,7 @@
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
"@paralleldrive/cuid2": "^3.1.0", "@paralleldrive/cuid2": "^3.1.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"next": "16.1.0", "next": "16.1.1",
"octokit": "^5.0.5", "octokit": "^5.0.5",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",

View File

@@ -28,7 +28,7 @@
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
"@homarr/cron-jobs": "workspace:^0.1.0", "@homarr/cron-jobs": "workspace:^0.1.0",
"@tanstack/react-query": "^5.90.12", "@tanstack/react-query": "^5.90.14",
"@trpc/client": "^11.8.1", "@trpc/client": "^11.8.1",
"@trpc/server": "^11.8.1", "@trpc/server": "^11.8.1",
"@trpc/tanstack-react-query": "^11.8.1", "@trpc/tanstack-react-query": "^11.8.1",

View File

@@ -49,8 +49,8 @@
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@mantine/core": "^8.3.10", "@mantine/core": "^8.3.10",
"@paralleldrive/cuid2": "^3.1.0", "@paralleldrive/cuid2": "^3.1.0",
"@testcontainers/mysql": "^11.10.0", "@testcontainers/mysql": "^11.11.0",
"@testcontainers/postgresql": "^11.10.0", "@testcontainers/postgresql": "^11.11.0",
"better-sqlite3": "^12.5.0", "better-sqlite3": "^12.5.0",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-kit": "^0.31.8", "drizzle-kit": "^0.31.8",
@@ -67,7 +67,7 @@
"@types/better-sqlite3": "7.6.13", "@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0", "@types/pg": "^8.16.0",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"esbuild": "^0.27.1", "esbuild": "^0.27.2",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"tsx": "4.20.4", "tsx": "4.20.4",

View File

@@ -298,6 +298,13 @@ export const integrationDefs = {
category: ["healthMonitoring"], category: ["healthMonitoring"],
documentationUrl: createDocumentationLink("/docs/integrations/truenas"), documentationUrl: createDocumentationLink("/docs/integrations/truenas"),
}, },
unraid: {
name: "Unraid",
secretKinds: [["apiKey"]],
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/unraid.svg",
category: ["healthMonitoring"],
documentationUrl: createDocumentationLink("/docs/integrations/unraid"),
},
// This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page) // This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page)
mock: { mock: {
name: "Mock", name: "Mock",

View File

@@ -38,6 +38,7 @@ import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
import { QuayIntegration } from "../quay/quay-integration"; import { QuayIntegration } from "../quay/quay-integration";
import { TrueNasIntegration } from "../truenas/truenas-integration"; import { TrueNasIntegration } from "../truenas/truenas-integration";
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration"; import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
import { UnraidIntegration } from "../unraid/unraid-integration";
import type { Integration, IntegrationInput } from "./integration"; import type { Integration, IntegrationInput } from "./integration";
export const createIntegrationAsync = async <TKind extends keyof typeof integrationCreators>( export const createIntegrationAsync = async <TKind extends keyof typeof integrationCreators>(
@@ -101,6 +102,7 @@ export const integrationCreators = {
ntfy: NTFYIntegration, ntfy: NTFYIntegration,
mock: MockIntegration, mock: MockIntegration,
truenas: TrueNasIntegration, truenas: TrueNasIntegration,
unraid: UnraidIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>; } satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = { type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {

View File

@@ -22,6 +22,7 @@ export { PiHoleIntegrationV6 } from "./pi-hole/v6/pi-hole-integration-v6";
export { PlexIntegration } from "./plex/plex-integration"; export { PlexIntegration } from "./plex/plex-integration";
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration"; export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
export { TrueNasIntegration } from "./truenas/truenas-integration"; export { TrueNasIntegration } from "./truenas/truenas-integration";
export { UnraidIntegration } from "./unraid/unraid-integration";
export { OPNsenseIntegration } from "./opnsense/opnsense-integration"; export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
export { ICalIntegration } from "./ical/ical-integration"; export { ICalIntegration } from "./ical/ical-integration";

View File

@@ -0,0 +1,189 @@
import dayjs from "dayjs";
import type { fetch as undiciFetch } from "undici/types/fetch";
import { humanFileSize } from "@homarr/common";
import { ResponseError } from "@homarr/common/server";
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { HandleIntegrationErrors } from "../base/errors/decorator";
import type { IntegrationTestingInput } from "../base/integration";
import { Integration } from "../base/integration";
import type { TestingResult } from "../base/test-connection/test-connection-service";
import type { ISystemHealthMonitoringIntegration } from "../interfaces/health-monitoring/health-monitoring-integration";
import type { SystemHealthMonitoring } from "../interfaces/health-monitoring/health-monitoring-types";
import type { UnraidSystemInfo } from "./unraid-types";
import { unraidSystemInfoSchema } from "./unraid-types";
const logger = createLogger({ module: "UnraidIntegration" });
@HandleIntegrationErrors([])
export class UnraidIntegration extends Integration implements ISystemHealthMonitoringIntegration {
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
await this.queryGraphQLAsync<{ info: UnraidSystemInfo }>(
`
query {
info {
os { platform }
}
}
`,
input.fetchAsync,
);
return { success: true };
}
public async getSystemInfoAsync(): Promise<SystemHealthMonitoring> {
const systemInfo = await this.getSystemInformationAsync();
const cpuUtilization = systemInfo.metrics.cpu.cpus.reduce((acc, val) => acc + val.percentTotal, 0);
const cpuCount = systemInfo.info.cpu.cores;
// We use "info" object instead of the stats since this is the exact amount the kernel sees, which is what Unraid displays.
const totalMemory = systemInfo.info.memory.layout.reduce((acc, layout) => layout.size + acc, 0);
const usedMemory = totalMemory * (systemInfo.metrics.memory.percentTotal / 100);
const uptime = dayjs(systemInfo.info.os.uptime);
return {
version: systemInfo.info.os.release,
cpuModelName: systemInfo.info.cpu.brand,
cpuUtilization: cpuUtilization / cpuCount,
memUsedInBytes: usedMemory,
memAvailableInBytes: totalMemory - usedMemory,
uptime: dayjs().diff(uptime, "seconds"),
network: null, // Not implemented, see https://github.com/unraid/api/issues/1602
loadAverage: null,
rebootRequired: false,
availablePkgUpdates: 0,
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
percentage: (disk.fsUsed / disk.size) * 100, // The units are the same, therefore the actual unit is irrelevant
})),
smart: systemInfo.array.disks.map((disk) => ({
deviceName: disk.name,
temperature: disk.temp,
overallStatus: disk.status,
})),
};
}
private async getSystemInformationAsync(): Promise<UnraidSystemInfo> {
logger.debug("Retrieving system information", {
url: this.url("/graphql"),
});
const query = `
query {
metrics {
cpu {
percentTotal
cpus {
percentTotal
}
},
memory {
available
used
free
total
swapFree
swapTotal
swapUsed
percentTotal
}
}
array {
state
capacity {
disks {
free
total
used
}
}
disks {
name
size
fsFree
fsUsed
status
temp
}
}
info {
devices {
network {
speed
dhcp
model
model
}
}
os {
platform,
distro,
release,
uptime
},
cpu {
manufacturer,
brand,
cores,
threads
},
memory {
layout {
size
}
}
}
}
`;
const response = await this.queryGraphQLAsync<UnraidSystemInfo>(query);
const result = await unraidSystemInfoSchema.parseAsync(response);
logger.debug("Retrieved system information", {
url: this.url("/graphql"),
});
return result;
}
private async queryGraphQLAsync<T>(
query: string,
fetchAsync: typeof undiciFetch = fetchWithTrustedCertificatesAsync,
): Promise<T> {
const url = this.url("/graphql");
const apiKey = this.getSecretValue("apiKey");
logger.debug("Sending GraphQL query", {
url: url.toString(),
});
const response = await fetchAsync(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({ query }),
});
if (!response.ok) {
throw new ResponseError(response);
}
const json = (await response.json()) as { data: T; errors?: { message: string }[] };
if (json.errors) {
throw new Error(`GraphQL errors: ${json.errors.map((error) => error.message).join(", ")}`);
}
return json.data;
}
}

View File

@@ -0,0 +1,73 @@
import z from "zod";
export const unraidSystemInfoSchema = z.object({
metrics: z.object({
cpu: z.object({
percentTotal: z.number(),
cpus: z.array(
z.object({
percentTotal: z.number(),
}),
),
}),
memory: z.object({
available: z.number(),
used: z.number(),
free: z.number(),
total: z.number().min(0),
percentTotal: z.number().min(0).max(100),
}),
}),
array: z.object({
state: z.string(),
capacity: z.object({
disks: z.object({
free: z.coerce.number(),
total: z.coerce.number(),
used: z.coerce.number(),
}),
}),
disks: z.array(
z.object({
name: z.string(),
size: z.number(),
fsFree: z.number(),
fsUsed: z.number(),
status: z.string(),
temp: z.number(),
}),
),
}),
info: z.object({
devices: z.object({
network: z.array(
z.object({
speed: z.number(),
dhcp: z.boolean(),
model: z.string(),
}),
),
}),
os: z.object({
platform: z.string(),
distro: z.string(),
release: z.string(),
uptime: z.coerce.date(),
}),
cpu: z.object({
manufacturer: z.string(),
brand: z.string(),
cores: z.number(),
threads: z.number(),
}),
memory: z.object({
layout: z.array(
z.object({
size: z.number(),
}),
),
}),
}),
});
export type UnraidSystemInfo = z.infer<typeof unraidSystemInfoSchema>;

View File

@@ -36,7 +36,7 @@
"@mantine/core": "^8.3.10", "@mantine/core": "^8.3.10",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"next": "16.1.0", "next": "16.1.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"zod": "^4.2.1" "zod": "^4.2.1"

View File

@@ -40,7 +40,7 @@
"@mantine/core": "^8.3.10", "@mantine/core": "^8.3.10",
"@mantine/hooks": "^8.3.10", "@mantine/hooks": "^8.3.10",
"adm-zip": "0.5.16", "adm-zip": "0.5.16",
"next": "16.1.0", "next": "16.1.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"superjson": "2.2.6", "superjson": "2.2.6",

View File

@@ -12,9 +12,7 @@ export const dockerContainersRequestHandler = createCachedWidgetRequestHandler({
queryKey: "dockerContainersResult", queryKey: "dockerContainersResult",
widgetKind: "dockerContainers", widgetKind: "dockerContainers",
async requestAsync() { async requestAsync() {
const containers = await getContainersWithStatsAsync(); return await getContainersWithStatsAsync();
return containers;
}, },
cacheDuration: dayjs.duration(20, "seconds"), cacheDuration: dayjs.duration(20, "seconds"),
}); });
@@ -28,7 +26,7 @@ async function getContainersWithStatsAsync() {
dockerInstances.map(async ({ instance, host }) => { dockerInstances.map(async ({ instance, host }) => {
const instanceContainers = await instance.listContainers({ all: true }); const instanceContainers = await instance.listContainers({ all: true });
return instanceContainers return instanceContainers
.filter((container) => dockerLabels.hide in container.Labels === false) .filter((container) => !(dockerLabels.hide in container.Labels))
.map((container) => ({ ...container, instance: host })); .map((container) => ({ ...container, instance: host }));
}), }),
).then((res) => res.flat()); ).then((res) => res.flat());
@@ -45,7 +43,21 @@ async function getContainersWithStatsAsync() {
const instance = dockerInstances.find(({ host }) => host === container.instance)?.instance; const instance = dockerInstances.find(({ host }) => host === container.instance)?.instance;
if (!instance) return null; if (!instance) return null;
const stats = await instance.getContainer(container.Id).stats({ stream: false, "one-shot": true }); // Get stats, falling back to an empty stats object if fetch fails
// calculateCpuUsage and calculateMemoryUsage will return 0 for invalid/missing stats
const stats = await instance
.getContainer(container.Id)
.stats({ stream: false, "one-shot": true })
.catch(
() =>
({
cpu_stats: { online_cpus: 0, cpu_usage: { total_usage: 0 }, system_cpu_usage: 0 },
memory_stats: { usage: 0 },
}) as ContainerStats,
);
const cpuUsage = calculateCpuUsage(stats);
const memoryUsage = calculateMemoryUsage(stats);
return { return {
id: container.Id, id: container.Id,
@@ -57,19 +69,8 @@ async function getContainersWithStatsAsync() {
if (!extractedImage) return false; if (!extractedImage) return false;
return icon.name.toLowerCase().includes(extractedImage.toLowerCase()); return icon.name.toLowerCase().includes(extractedImage.toLowerCase());
})?.url ?? null, })?.url ?? null,
cpuUsage: calculateCpuUsage(stats), cpuUsage,
// memory usage by default includes cache, which should not be shown as it is also not shown with docker stats command memoryUsage,
// The below type is probably wrong, sometimes stats can be undefined
// See https://docs.docker.com/reference/cli/docker/container/stats/ how it is / was calculated
memoryUsage:
stats.memory_stats.usage -
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(stats.memory_stats.stats?.cache ??
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
stats.memory_stats.stats?.total_inactive_file ??
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
stats.memory_stats.stats?.inactive_file ??
0),
image: container.Image, image: container.Image,
ports: container.Ports, ports: container.Ports,
}; };
@@ -79,10 +80,36 @@ async function getContainersWithStatsAsync() {
} }
function calculateCpuUsage(stats: ContainerStats): number { function calculateCpuUsage(stats: ContainerStats): number {
const numberOfCpus = stats.cpu_stats.online_cpus || 1; // Handle containers with missing or invalid stats (e.g., exited, dead containers)
if (!stats.cpu_stats.online_cpus || stats.cpu_stats.online_cpus === 0 || !stats.cpu_stats.cpu_usage.total_usage) {
return 0;
}
const numberOfCpus = stats.cpu_stats.online_cpus;
const usage = stats.cpu_stats.system_cpu_usage; const usage = stats.cpu_stats.system_cpu_usage;
if (usage === 0) return 0; if (!usage || usage === 0) {
return 0;
}
return (stats.cpu_stats.cpu_usage.total_usage / usage) * numberOfCpus * 100; return (stats.cpu_stats.cpu_usage.total_usage / usage) * numberOfCpus * 100;
} }
function calculateMemoryUsage(stats: ContainerStats): number {
// Handle containers with missing or invalid stats (e.g., exited, dead containers)
if (!stats.memory_stats.usage) {
return 0;
}
// memory usage by default includes cache, which should not be shown as it is also not shown with docker stats command
// See https://docs.docker.com/reference/cli/docker/container/stats/ how it is / was calculated
return (
stats.memory_stats.usage -
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(stats.memory_stats.stats?.cache ??
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
stats.memory_stats.stats?.total_inactive_file ??
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
stats.memory_stats.stats?.inactive_file ??
0)
);
}

View File

@@ -27,7 +27,7 @@
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@mantine/dates": "^8.3.10", "@mantine/dates": "^8.3.10",
"next": "16.1.0", "next": "16.1.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3" "react-dom": "19.2.3"
}, },

View File

@@ -37,8 +37,8 @@
"@mantine/hooks": "^8.3.10", "@mantine/hooks": "^8.3.10",
"@mantine/spotlight": "^8.3.10", "@mantine/spotlight": "^8.3.10",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"jotai": "^2.16.0", "jotai": "^2.16.1",
"next": "16.1.0", "next": "16.1.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"use-deep-compare-effect": "^1.8.1" "use-deep-compare-effect": "^1.8.1"

View File

@@ -32,7 +32,7 @@
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"deepmerge": "4.3.1", "deepmerge": "4.3.1",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "16.1.0", "next": "16.1.1",
"next-intl": "4.6.1", "next-intl": "4.6.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3" "react-dom": "19.2.3"

View File

@@ -1941,19 +1941,19 @@
"description": "Estadísticas de tus contenedores (Este widget sólo puede ser añadido con privilegios de administrador)", "description": "Estadísticas de tus contenedores (Este widget sólo puede ser añadido con privilegios de administrador)",
"option": { "option": {
"enableRowSorting": { "enableRowSorting": {
"label": "" "label": "Habilitar ordenar elementos"
}, },
"defaultSort": { "defaultSort": {
"label": "", "label": "Columna usada para ordenar por defecto",
"option": { "option": {
"name": "", "name": "Nombre",
"state": "", "state": "Estado",
"cpuUsage": "", "cpuUsage": "Uso de CPU",
"memoryUsage": "" "memoryUsage": "Uso de memoria"
} }
}, },
"descendingDefaultSort": { "descendingDefaultSort": {
"label": "" "label": "Invertir orden"
} }
}, },
"error": { "error": {

View File

@@ -7,7 +7,7 @@
"description": "Pour commencer, veuillez selectionner comment vous souhaitez configurer votre instance Homarr.", "description": "Pour commencer, veuillez selectionner comment vous souhaitez configurer votre instance Homarr.",
"action": { "action": {
"scratch": "Partir de zéro", "scratch": "Partir de zéro",
"importOldmarr": "Importer à partir d'Homarr avant la 1.0" "importOldmarr": "Importer depuis Homarr avant la 1.0"
} }
}, },
"import": { "import": {
@@ -27,7 +27,7 @@
"description": "Configurer le comportement d'importation" "description": "Configurer le comportement d'importation"
}, },
"boardSelection": { "boardSelection": {
"title": "{count} tableaux trouvés", "title": "Tableaux {count} trouvés",
"description": "Choisissez tous les tableaux avec la taille que vous souhaitez importer", "description": "Choisissez tous les tableaux avec la taille que vous souhaitez importer",
"action": { "action": {
"selectAll": "Tout sélectionner", "selectAll": "Tout sélectionner",
@@ -113,7 +113,7 @@
"subtitle": "Bon retour parmi nous ! Veuillez entrer vos identifiants" "subtitle": "Bon retour parmi nous ! Veuillez entrer vos identifiants"
}, },
"invite": { "invite": {
"title": "Rejoindre Homarr", "title": "Rejoignez Homarr",
"subtitle": "Bienvenue sur Homarr! Veuillez créer votre compte", "subtitle": "Bienvenue sur Homarr! Veuillez créer votre compte",
"description": "Vous avez été invité par {username}" "description": "Vous avez été invité par {username}"
}, },
@@ -649,14 +649,14 @@
"app": { "app": {
"option": { "option": {
"existing": { "existing": {
"title": "", "title": "Existant",
"label": "" "label": "Sélectionner une application existante"
}, },
"new": { "new": {
"title": "", "title": "Nouveau",
"url": { "url": {
"label": "", "label": "Lien de l'application",
"description": "" "description": "Lien d'ouverture de l'application depuis le tableau de bord"
} }
} }
} }
@@ -676,9 +676,9 @@
}, },
"app": { "app": {
"action": { "action": {
"add": "", "add": "Lier une application",
"remove": "", "remove": "Délier",
"select": "" "select": "Sélectionnez une application à lier"
} }
} }
}, },
@@ -709,7 +709,7 @@
"description": "L'intégration \"{kind}\" peut être utilisée avec les moteurs de recherche. Cochez ceci pour configurer automatiquement le moteur de recherche." "description": "L'intégration \"{kind}\" peut être utilisée avec les moteurs de recherche. Cochez ceci pour configurer automatiquement le moteur de recherche."
}, },
"app": { "app": {
"sectionTitle": "" "sectionTitle": "Application liée"
}, },
"createApp": { "createApp": {
"label": "Créer une application", "label": "Créer une application",
@@ -991,8 +991,8 @@
"newLabel": "Nouvel ID d'installation" "newLabel": "Nouvel ID d'installation"
}, },
"privateKey": { "privateKey": {
"label": "", "label": "Clé privée",
"newLabel": "" "newLabel": "Nouvelle clé privée"
} }
} }
}, },
@@ -1044,14 +1044,14 @@
} }
}, },
"common": { "common": {
"success": "", "success": "Succès",
"beta": "Bêta", "beta": "Bêta",
"error": "Erreur", "error": "Erreur",
"action": { "action": {
"add": "Ajouter", "add": "Ajouter",
"apply": "Appliquer", "apply": "Appliquer",
"backToOverview": "Retourner à l'aperçu", "backToOverview": "Retourner à l'aperçu",
"change": "", "change": "Modifier",
"create": "Créer", "create": "Créer",
"createAnother": "Créer et recommencer", "createAnother": "Créer et recommencer",
"edit": "Modifier", "edit": "Modifier",
@@ -1174,8 +1174,8 @@
}, },
"unit": { "unit": {
"speed": { "speed": {
"kilometersPerHour": "", "kilometersPerHour": "km/h",
"milesPerHour": "" "milesPerHour": "mph"
} }
} }
}, },
@@ -1190,7 +1190,7 @@
"label": "Titre" "label": "Titre"
}, },
"customCssClasses": { "customCssClasses": {
"label": "" "label": "Classes Css personnalisées"
}, },
"borderColor": { "borderColor": {
"label": "Couleur de la bordure" "label": "Couleur de la bordure"
@@ -1321,21 +1321,21 @@
"label": "Activer la vérification du statut" "label": "Activer la vérification du statut"
}, },
"layout": { "layout": {
"label": "", "label": "Mise en page",
"option": { "option": {
"row": "", "row": "Horizontale",
"row-reverse": "", "row-reverse": "Horizontal (inversé)",
"column": "", "column": "Vertical",
"column-reverse": "" "column-reverse": "Vertical (inversé)"
} }
}, },
"descriptionDisplayMode": { "descriptionDisplayMode": {
"label": "", "label": "Mode d'affichage de la description",
"description": "", "description": "Choisissez comment afficher la description de l'application",
"option": { "option": {
"normal": "", "normal": "Au sein du widget",
"tooltip": "", "tooltip": "Comme infobulle",
"hidden": "" "hidden": "Masquer"
} }
} }
}, },
@@ -1584,11 +1584,11 @@
}, },
"placeholder": "Commencer à écrire vos notes", "placeholder": "Commencer à écrire vos notes",
"dismiss": { "dismiss": {
"title": "", "title": "Abandonner les modifications ?",
"message": "", "message": "Vous avez des modifications non enregistrées dans votre bloc-notes. Êtes-vous sûr de vouloir les supprimer ?",
"action": { "action": {
"discard": "", "discard": "Ignorer les modifications",
"keepEditing": "" "keepEditing": "Continuer les modifications"
} }
} }
}, },
@@ -1743,7 +1743,7 @@
"name": "Calendrier", "name": "Calendrier",
"description": "Afficher les événements de vos intégrations dans une vue calendrier pendant une certaine période de temps relative", "description": "Afficher les événements de vos intégrations dans une vue calendrier pendant une certaine période de temps relative",
"duration": { "duration": {
"allDay": "" "allDay": "Tous les jours"
}, },
"option": { "option": {
"releaseType": { "releaseType": {
@@ -1780,7 +1780,7 @@
"description": "Uniquement sur la météo actuelle" "description": "Uniquement sur la météo actuelle"
}, },
"useImperialSpeed": { "useImperialSpeed": {
"label": "" "label": "Utiliser mph pour la vitesse du vent"
}, },
"location": { "location": {
"label": "Lieu de la météo" "label": "Lieu de la météo"
@@ -1941,19 +1941,19 @@
"description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)", "description": "Statistiques de vos conteneurs (Ce widget ne peut être ajouté qu'avec les privilèges d'administrateur)",
"option": { "option": {
"enableRowSorting": { "enableRowSorting": {
"label": "" "label": "Activer le tri des éléments"
}, },
"defaultSort": { "defaultSort": {
"label": "", "label": "Colonne de tri par défaut",
"option": { "option": {
"name": "", "name": "Nom",
"state": "", "state": "État",
"cpuUsage": "", "cpuUsage": "Utilisation du CPU",
"memoryUsage": "" "memoryUsage": "Utilisation mémoire"
} }
}, },
"descendingDefaultSort": { "descendingDefaultSort": {
"label": "" "label": "Inverser le tri"
} }
}, },
"error": { "error": {
@@ -2036,21 +2036,21 @@
"name": "Nom", "name": "Nom",
"id": "Id", "id": "Id",
"metadata": { "metadata": {
"title": "", "title": "Statistiques détaillées",
"video": { "video": {
"title": "", "title": "Vidéo",
"resolution": "" "resolution": "Résolution"
}, },
"audio": { "audio": {
"title": "", "title": "Audio",
"channelCount": "", "channelCount": "Canaux audio",
"codec": "" "codec": "Codec audio"
}, },
"transcoding": { "transcoding": {
"title": "", "title": "Transcodage",
"container": "", "container": "Conteneur",
"resolution": "Résolution", "resolution": "Résolution",
"target": "" "target": "Codec cible"
} }
} }
} }
@@ -2248,7 +2248,7 @@
"unknown": "Inconnu", "unknown": "Inconnu",
"pending": "En attente", "pending": "En attente",
"processing": "Traitement en cours", "processing": "Traitement en cours",
"requested": "", "requested": "En attente",
"partiallyAvailable": "Partiel", "partiallyAvailable": "Partiel",
"available": "Disponible", "available": "Disponible",
"blacklisted": "Sur la liste noire", "blacklisted": "Sur la liste noire",
@@ -2361,7 +2361,7 @@
"label": "Nombre maximum de publications" "label": "Nombre maximum de publications"
}, },
"hideDescription": { "hideDescription": {
"label": "" "label": "Masquer la description"
} }
} }
}, },
@@ -2542,7 +2542,7 @@
}, },
"firewall": { "firewall": {
"name": "Surveillance du pare-feu", "name": "Surveillance du pare-feu",
"description": "", "description": "Affiche un résumé des pare-feu",
"tab": { "tab": {
"system": "Système", "system": "Système",
"interfaces": "Interfaces" "interfaces": "Interfaces"
@@ -2556,16 +2556,16 @@
"widget": { "widget": {
"fwname": "Nom", "fwname": "Nom",
"version": "Version", "version": "Version",
"versiontitle": "", "versiontitle": "Versions",
"cputitle": "", "cputitle": "Utilisation du CPU",
"memorytitle": "", "memorytitle": "Utilisation de la mémoire",
"cpu": "", "cpu": "CPU",
"memory": "", "memory": "Mémoire",
"interfaces": { "interfaces": {
"name": "", "name": "nom",
"trans": "", "trans": "Transmis",
"recv": "", "recv": "Reçus",
"title": "" "title": "Interfaces réseau"
} }
} }
}, },
@@ -2576,37 +2576,37 @@
"option": {} "option": {}
}, },
"systemResources": { "systemResources": {
"name": "", "name": "Ressources du système",
"description": "", "description": "CPU, Mémoire, Disque et autre utilisation matérielle de votre système",
"option": { "option": {
"hasShadow": { "hasShadow": {
"label": "" "label": "Activer l'ombrage des cartes"
}, },
"visibleCharts": { "visibleCharts": {
"label": "", "label": "Graphiques visibles",
"description": "", "description": "Sélectionnez les graphiques à afficher.",
"option": { "option": {
"cpu": "", "cpu": "CPU",
"memory": "", "memory": "Mémoire",
"network": "" "network": "Réseau"
} }
}, },
"labelDisplayMode": { "labelDisplayMode": {
"label": "", "label": "Mode d'affichage de l'étiquette",
"option": { "option": {
"textWithIcon": "", "textWithIcon": "Afficher le texte avec l'icône",
"text": "", "text": "Afficher uniquement le texte",
"icon": "", "icon": "Afficher uniquement l'icône",
"hidden": "" "hidden": "Masquer l'étiquette"
} }
} }
}, },
"card": { "card": {
"cpu": "", "cpu": "CPU",
"memory": "", "memory": "RAM",
"network": "", "network": "NET",
"up": "", "up": "UP",
"down": "" "down": "HORS SERVICE"
} }
} }
}, },
@@ -3030,8 +3030,8 @@
"integration": "Intégrations", "integration": "Intégrations",
"app": "Applications", "app": "Applications",
"group": "Groupes", "group": "Groupes",
"searchEngine": "", "searchEngine": "Moteurs de recherche",
"media": "" "media": "Médias"
}, },
"statisticLabel": { "statisticLabel": {
"boards": "Tableaux de bord", "boards": "Tableaux de bord",
@@ -3040,8 +3040,8 @@
"authorization": "Autorisation" "authorization": "Autorisation"
}, },
"heroBanner": { "heroBanner": {
"title": "", "title": "Bienvenue sur votre",
"subtitle": "" "subtitle": "Tableau {app}"
} }
}, },
"board": { "board": {
@@ -3387,19 +3387,19 @@
"label": "Conteneurs Docker" "label": "Conteneurs Docker"
}, },
"firewallCpu": { "firewallCpu": {
"label": "" "label": "Processeur du pare-feu"
}, },
"firewallMemory": { "firewallMemory": {
"label": "" "label": "Mémoire du pare-feu"
}, },
"firewallVersion": { "firewallVersion": {
"label": "" "label": "Version du pare-feu"
}, },
"firewallInterfaces": { "firewallInterfaces": {
"label": "" "label": "Interfaces du pare-feu"
}, },
"weather": { "weather": {
"label": "" "label": "Météo"
} }
}, },
"interval": { "interval": {
@@ -3410,10 +3410,10 @@
"weeklyMonday": "Chaque semaine le lundi", "weeklyMonday": "Chaque semaine le lundi",
"update": { "update": {
"success": { "success": {
"message": "" "message": "Intervalle mis à jour avec succès"
}, },
"error": { "error": {
"message": "" "message": "L'intervalle de mise à jour a échoué"
} }
} }
}, },
@@ -3422,55 +3422,55 @@
}, },
"field": { "field": {
"name": { "name": {
"label": "" "label": "Nom"
}, },
"interval": { "interval": {
"label": "Intervalle de planification" "label": "Intervalle de planification"
}, },
"lastExecution": { "lastExecution": {
"label": "" "label": "Dernière exécution"
}, },
"actions": { "actions": {
"label": "" "label": "Actions"
} }
}, },
"table": { "table": {
"search": "" "search": "Rechercher des tâches {count}..."
}, },
"action": { "action": {
"refresh": { "refresh": {
"label": "" "label": "Rafraîchir"
} }
}, },
"refresh": { "refresh": {
"success": { "success": {
"message": "" "message": "Tâches actualisées avec succès"
}, },
"error": { "error": {
"message": "" "message": "Échec de l'actualisation des tâches"
} }
}, },
"trigger": { "trigger": {
"success": { "success": {
"message": "" "message": "Tâche déclenchée avec succès"
}, },
"error": { "error": {
"message": "" "message": "Échec du déclenchement de la tâche"
} }
}, },
"enable": { "enable": {
"success": { "success": {
"message": "" "message": "Tâche activée avec succès"
} }
}, },
"disable": { "disable": {
"success": { "success": {
"message": "" "message": "Tâche désactivée avec succès"
} }
}, },
"toggle": { "toggle": {
"error": { "error": {
"message": "" "message": "Impossible de basculer le statut de la tâche"
} }
} }
}, },
@@ -3527,19 +3527,19 @@
"subtitle": "{count} utilisés dans le Code d'Homarr" "subtitle": "{count} utilisés dans le Code d'Homarr"
}, },
"hotkeys": { "hotkeys": {
"title": "", "title": "Raccourcis clavier",
"subtitle": "", "subtitle": "Raccourcis clavier pour améliorer votre flux de travail",
"field": { "field": {
"shortcut": "", "shortcut": "Raccourci",
"action": "" "action": "Action"
}, },
"action": { "action": {
"toggleBoardEdit": "", "toggleBoardEdit": "Activer/désactiver le mode d'édition du tableau",
"toggleColorScheme": "", "toggleColorScheme": "Basculer en mode clair/sombre",
"saveNotebook": "", "saveNotebook": "Enregistrer le bloc-notes (uniquement à l'intérieur du widget notebook)",
"openSpotlight": "" "openSpotlight": "Ouvrir la recherche"
}, },
"note": "" "note": "Astuce : Mod correspond à la touche Ctrl ou ⌘ sur macOS"
} }
} }
} }
@@ -3995,7 +3995,7 @@
"tools": { "tools": {
"label": "Outils", "label": "Outils",
"tasks": { "tasks": {
"label": "" "label": "Tâches"
}, },
"docker": { "docker": {
"label": "Docker" "label": "Docker"

File diff suppressed because it is too large Load Diff

View File

@@ -177,8 +177,8 @@
} }
}, },
"forgotPassword": { "forgotPassword": {
"label": "", "label": "Ste pozabili geslo?",
"description": "" "description": "Administrator lahko zažene ukaz:"
} }
}, },
"register": { "register": {
@@ -622,7 +622,7 @@
"description": "", "description": "",
"action": "" "action": ""
}, },
"add": "" "add": "Dodaj aplikacijo"
} }
}, },
"integration": { "integration": {
@@ -1059,10 +1059,10 @@
"insert": "Vstavite", "insert": "Vstavite",
"remove": "Odstrani", "remove": "Odstrani",
"save": "Shrani", "save": "Shrani",
"saveChanges": "Shranjevanje sprememb", "saveChanges": "Shrani spremembe",
"cancel": "Prekliči", "cancel": "Prekliči",
"delete": "Izbriši", "delete": "Izbriši",
"discard": "", "discard": "Zapusti",
"close": "", "close": "",
"confirm": "Potrdi", "confirm": "Potrdi",
"continue": "", "continue": "",
@@ -1125,13 +1125,13 @@
"userAvatar": { "userAvatar": {
"menu": { "menu": {
"switchToDarkMode": "", "switchToDarkMode": "",
"switchToLightMode": "", "switchToLightMode": "Svetli način",
"management": "", "management": "Nastavitve",
"preferences": "Vaše želje", "preferences": "Prilagoditev",
"logout": "", "logout": "Odjava",
"login": "Prijava", "login": "Prijava",
"homeBoard": "", "homeBoard": "Vaša nadzorna plošča",
"loggedOut": "", "loggedOut": "Odjava uspešna",
"updateAvailable": "" "updateAvailable": ""
} }
}, },
@@ -1182,7 +1182,7 @@
"section": { "section": {
"dynamic": { "dynamic": {
"action": { "action": {
"create": "", "create": "Nova dinamična vsebina",
"remove": "" "remove": ""
}, },
"option": { "option": {
@@ -1208,7 +1208,7 @@
} }
}, },
"action": { "action": {
"create": "", "create": "Nova kategorija",
"edit": "", "edit": "",
"remove": "", "remove": "",
"moveUp": "Premaknite se navzgor", "moveUp": "Premaknite se navzgor",
@@ -1243,12 +1243,12 @@
}, },
"item": { "item": {
"action": { "action": {
"create": "", "create": "Nova vsebina",
"import": "", "import": "",
"edit": "", "edit": "Uredi gradnik",
"moveResize": "", "moveResize": "Premakni/spremeni",
"duplicate": "", "duplicate": "Podvoji",
"remove": "" "remove": "Izbriši"
}, },
"menu": { "menu": {
"label": { "label": {
@@ -2638,8 +2638,8 @@
"edit": { "edit": {
"notification": { "notification": {
"success": { "success": {
"title": "", "title": "Spremembe so uveljavljene",
"message": "" "message": "Vsebina je bila uspšeno shranjena"
}, },
"error": { "error": {
"title": "", "title": "",
@@ -2647,8 +2647,8 @@
} }
}, },
"confirmLeave": { "confirmLeave": {
"title": "", "title": "Neshranjene spremembe",
"message": "" "message": "Vsebina ni shranjena, želite nadaljevati?"
} }
}, },
"oldImport": { "oldImport": {
@@ -2716,16 +2716,16 @@
}, },
"field": { "field": {
"pageTitle": { "pageTitle": {
"label": "" "label": "Naslov strani"
}, },
"metaTitle": { "metaTitle": {
"label": "" "label": "Meta naslov"
}, },
"logoImageUrl": { "logoImageUrl": {
"label": "" "label": "URL logotipa"
}, },
"faviconImageUrl": { "faviconImageUrl": {
"label": "" "label": "URL ikone"
}, },
"backgroundImageUrl": { "backgroundImageUrl": {
"label": "", "label": "",
@@ -2835,7 +2835,7 @@
"metaTitle": "" "metaTitle": ""
}, },
"setting": { "setting": {
"title": "", "title": "Nastavitve za {boardName} nadzorno ploščo",
"section": { "section": {
"general": { "general": {
"title": "Splošno", "title": "Splošno",
@@ -2844,9 +2844,9 @@
"layout": { "layout": {
"title": "Postavitev", "title": "Postavitev",
"responsive": { "responsive": {
"title": "", "title": "Predloga",
"action": { "action": {
"add": "" "add": "Dodaj predlogo"
} }
} }
}, },
@@ -2959,14 +2959,14 @@
"layout": { "layout": {
"field": { "field": {
"name": { "name": {
"label": "" "label": "Ime"
}, },
"columnCount": { "columnCount": {
"label": "" "label": "Število stolpcev"
}, },
"breakpoint": { "breakpoint": {
"label": "", "label": "Prekinitvene točke",
"description": "" "description": "Postavitev bo uporabljena na vseh zaslonih, večjih od te prelomne točke, do naslednje večje prelomne točke."
} }
} }
}, },
@@ -4046,7 +4046,7 @@
} }
}, },
"search": { "search": {
"placeholder": "", "placeholder": "Iskanje vsebine",
"nothingFound": "", "nothingFound": "",
"error": { "error": {
"fetch": "" "fetch": ""

View File

@@ -34,7 +34,7 @@
"@mantine/hooks": "^8.3.10", "@mantine/hooks": "^8.3.10",
"@tabler/icons-react": "^3.35.0", "@tabler/icons-react": "^3.35.0",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "16.1.0", "next": "16.1.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"svgson": "^5.3.1" "svgson": "^5.3.1"

View File

@@ -72,7 +72,7 @@
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "^1.3.0",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "16.1.0", "next": "16.1.1",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",

View File

@@ -47,7 +47,7 @@ export const SystemResourceMemoryChart = ({
return ( return (
<Paper px={3} py={2} withBorder shadow="md" radius="md"> <Paper px={3} py={2} withBorder shadow="md" radius="md">
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
{humanFileSize(value)} / {humanFileSize(totalCapacityInBytes)} ( {humanFileSize(Math.round(value))} / {humanFileSize(totalCapacityInBytes)} (
{Math.round((value / totalCapacityInBytes) * 100)}%) {Math.round((value / totalCapacityInBytes) * 100)}%)
</Text> </Text>
</Paper> </Paper>

View File

@@ -22,6 +22,7 @@ export default function SystemResources({ integrationIds, options }: WidgetCompo
}); });
const memoryCapacityInBytes = const memoryCapacityInBytes =
(data[0]?.healthInfo.memAvailableInBytes ?? 0) + (data[0]?.healthInfo.memUsedInBytes ?? 0); (data[0]?.healthInfo.memAvailableInBytes ?? 0) + (data[0]?.healthInfo.memUsedInBytes ?? 0);
const [items, setItems] = useState<{ cpu: number; memory: number; network: { up: number; down: number } | null }[]>( const [items, setItems] = useState<{ cpu: number; memory: number; network: { up: number; down: number } | null }[]>(
data.map((item) => ({ data.map((item) => ({
cpu: item.healthInfo.cpuUtilization, cpu: item.healthInfo.cpuUtilization,

View File

@@ -14,7 +14,7 @@ const labelDisplayModeOptions = {
export const { definition, componentLoader } = createWidgetDefinition("systemResources", { export const { definition, componentLoader } = createWidgetDefinition("systemResources", {
icon: IconGraphFilled, icon: IconGraphFilled,
supportedIntegrations: ["dashDot", "openmediavault", "truenas"], supportedIntegrations: ["dashDot", "openmediavault", "truenas", "unraid"],
createOptions() { createOptions() {
return optionsBuilder.from((factory) => ({ return optionsBuilder.from((factory) => ({
hasShadow: factory.switch({ defaultValue: true }), hasShadow: factory.switch({ defaultValue: true }),

985
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -17,14 +17,14 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@next/eslint-plugin-next": "16.1.0", "@next/eslint-plugin-next": "16.1.1",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-config-turbo": "^2.7.1", "eslint-config-turbo": "^2.7.2",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^6.1.1", "eslint-plugin-react-hooks": "^6.1.1",
"typescript-eslint": "^8.50.0" "typescript-eslint": "^8.50.1"
}, },
"devDependencies": { "devDependencies": {
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",