Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91e9d77f46 |
No files matched your search
@@ -0,0 +1,150 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { Response } from "undici";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
vi.hoisted(() => {
|
||||
process.env.CI = "true";
|
||||
process.env.NODE_ENV = "test";
|
||||
process.env.SECRET_ENCRYPTION_KEY = "0".repeat(64);
|
||||
});
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/http", () => ({
|
||||
fetchWithTrustedCertificatesAsync: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/logs", () => ({
|
||||
createLogger: () => ({
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import type { IntegrationSecret } from "../../base/types";
|
||||
import { UnraidIntegration } from "../unraid-integration";
|
||||
|
||||
const TEST_URL = "https://unraid.example.com";
|
||||
const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
|
||||
|
||||
const createIntegration = (secrets: IntegrationSecret[] = []) =>
|
||||
new UnraidIntegration({
|
||||
id: "test-unraid",
|
||||
name: "Test Unraid",
|
||||
url: TEST_URL,
|
||||
externalUrl: null,
|
||||
decryptedSecrets: secrets.length ? secrets : [{ kind: "apiKey", value: "secret-api-key" }],
|
||||
});
|
||||
|
||||
const graphqlResponse = (data: unknown) => ({
|
||||
data,
|
||||
});
|
||||
|
||||
const mockGraphQL = (data: unknown) => {
|
||||
mockFetch.mockResolvedValue(
|
||||
new Response(JSON.stringify(graphqlResponse(data)), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const GiB = 1024 * 1024 * 1024;
|
||||
|
||||
const systemInfoResponse = {
|
||||
metrics: {
|
||||
cpu: {
|
||||
percentTotal: 10,
|
||||
cpus: [{ percentTotal: 10 }, { percentTotal: 10 }],
|
||||
},
|
||||
memory: {
|
||||
available: 20 * GiB,
|
||||
used: 12 * GiB,
|
||||
free: 10 * GiB,
|
||||
total: 32 * GiB,
|
||||
percentTotal: 37.5,
|
||||
},
|
||||
},
|
||||
array: {
|
||||
state: "STARTED",
|
||||
capacity: {
|
||||
disks: {
|
||||
free: 100,
|
||||
total: 200,
|
||||
used: 100,
|
||||
},
|
||||
},
|
||||
disks: [
|
||||
{
|
||||
name: "disk1",
|
||||
size: 200 * 1024 * 1024,
|
||||
fsFree: 100 * 1024 * 1024,
|
||||
fsUsed: 100 * 1024 * 1024,
|
||||
status: "DISK_OK",
|
||||
temp: 38,
|
||||
},
|
||||
],
|
||||
},
|
||||
info: {
|
||||
devices: {
|
||||
network: [
|
||||
{
|
||||
speed: 1000,
|
||||
dhcp: true,
|
||||
model: "eth0",
|
||||
},
|
||||
],
|
||||
},
|
||||
os: {
|
||||
platform: "linux",
|
||||
distro: "Unraid",
|
||||
release: "7.3.2",
|
||||
uptime: new Date().toISOString(),
|
||||
},
|
||||
cpu: {
|
||||
manufacturer: "Intel",
|
||||
brand: "Intel Core i5",
|
||||
cores: 4,
|
||||
threads: 8,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("UnraidIntegration", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("computes used memory from metrics.memory.total - available instead of layout sizes", async () => {
|
||||
mockGraphQL(systemInfoResponse);
|
||||
const integration = createIntegration();
|
||||
|
||||
const result = await integration.getSystemInfoAsync();
|
||||
|
||||
expect(result.memUsedInBytes).toBe(12 * GiB);
|
||||
expect(result.memAvailableInBytes).toBe(20 * GiB);
|
||||
});
|
||||
|
||||
test("never reports negative used memory when available exceeds total", async () => {
|
||||
mockGraphQL({
|
||||
...systemInfoResponse,
|
||||
metrics: {
|
||||
...systemInfoResponse.metrics,
|
||||
memory: {
|
||||
...systemInfoResponse.metrics.memory,
|
||||
available: 40 * GiB,
|
||||
total: 32 * GiB,
|
||||
},
|
||||
},
|
||||
});
|
||||
const integration = createIntegration();
|
||||
|
||||
const result = await integration.getSystemInfoAsync();
|
||||
|
||||
expect(result.memUsedInBytes).toBe(0);
|
||||
expect(result.memAvailableInBytes).toBe(32 * GiB);
|
||||
});
|
||||
});
|
||||
@@ -47,8 +47,12 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
|
||||
cpuUtilizationNormalized = cpuUtilization / cpuCount;
|
||||
}
|
||||
|
||||
const totalMemory = systemInfo.info.memory.layout.reduce((acc, layout) => layout.size + acc, 0);
|
||||
const usedMemory = totalMemory * (systemInfo.metrics.memory.percentTotal / 100);
|
||||
// metrics.memory.* is documented as bytes (see MemoryUtilization schema), unlike
|
||||
// info.memory.layout[].size, which can be reported in KiB on some Unraid versions and caused a
|
||||
// 1024x under-reporting of used RAM. total - available matches Unraid's percentTotal calculation.
|
||||
|
||||
const totalMemory = systemInfo.metrics.memory.total;
|
||||
const usedMemory = Math.max(totalMemory - systemInfo.metrics.memory.available, 0);
|
||||
const uptime = dayjs(systemInfo.info.os.uptime);
|
||||
|
||||
return {
|
||||
@@ -143,11 +147,6 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
|
||||
brand,
|
||||
cores,
|
||||
threads
|
||||
},
|
||||
memory {
|
||||
layout {
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,13 +60,6 @@ export const unraidSystemInfoSchema = z.object({
|
||||
cores: z.number(),
|
||||
threads: z.number(),
|
||||
}),
|
||||
memory: z.object({
|
||||
layout: z.array(
|
||||
z.object({
|
||||
size: z.number(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -3163,8 +3163,8 @@
|
||||
"noColumns": "Spalten in Elementen auswählen",
|
||||
"noCommunications": "Kann keine Daten von der Integration laden",
|
||||
"noItems": "",
|
||||
"actionFailed": "Aktion fehlgeschlagen",
|
||||
"actionFailedMessage": "Diese Aktion konnte nicht abgeschlossen werden. Bitte versuche es erneut."
|
||||
"actionFailed": "",
|
||||
"actionFailedMessage": ""
|
||||
},
|
||||
"items": {
|
||||
"actions": {
|
||||
@@ -3452,7 +3452,7 @@
|
||||
"queued": ""
|
||||
},
|
||||
"type": {
|
||||
"movie": "Film",
|
||||
"movie": "",
|
||||
"episode": ""
|
||||
},
|
||||
"empty": {
|
||||
@@ -3812,7 +3812,7 @@
|
||||
},
|
||||
"resource": {
|
||||
"routers": "",
|
||||
"services": "Dienste",
|
||||
"services": "",
|
||||
"middlewares": ""
|
||||
},
|
||||
"status": {
|
||||
@@ -4098,7 +4098,7 @@
|
||||
"temp": "",
|
||||
"battery": "",
|
||||
"services": "Dienste",
|
||||
"uptime": "Verfügbarkeit",
|
||||
"uptime": "",
|
||||
"agent": "Agent"
|
||||
}
|
||||
},
|
||||
@@ -4173,12 +4173,12 @@
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "CPU Auslastung",
|
||||
"subtitle": "Durchschnittliche Systemweite CPU-Auslastung",
|
||||
"subtitle": "",
|
||||
"series": "CPU"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Speicherverbrauch",
|
||||
"subtitle": "Genaue Auslastung zum erfassten Zeitpunkt",
|
||||
"title": "",
|
||||
"subtitle": "",
|
||||
"series": "Belegt",
|
||||
"cache": ""
|
||||
},
|
||||
@@ -4189,7 +4189,7 @@
|
||||
},
|
||||
"diskIO": {
|
||||
"title": "Festplatten-E/A",
|
||||
"subtitle": "Durchsatz des Root-Dateisystems",
|
||||
"subtitle": "",
|
||||
"read": "Lesen",
|
||||
"write": "Schreiben"
|
||||
},
|
||||
@@ -6096,7 +6096,7 @@
|
||||
}
|
||||
},
|
||||
"availability": {
|
||||
"available": "Verfügbar",
|
||||
"available": "",
|
||||
"partiallyAvailable": "",
|
||||
"processing": "",
|
||||
"requested": "",
|
||||
|
||||
@@ -3179,7 +3179,7 @@
|
||||
"detailsTitle": ""
|
||||
},
|
||||
"downSpeed": {
|
||||
"columnTitle": "Down",
|
||||
"columnTitle": "",
|
||||
"detailsTitle": ""
|
||||
},
|
||||
"index": {
|
||||
@@ -3194,10 +3194,10 @@
|
||||
"columnTitle": ""
|
||||
},
|
||||
"name": {
|
||||
"columnTitle": "Job name"
|
||||
"columnTitle": ""
|
||||
},
|
||||
"progress": {
|
||||
"columnTitle": "Progress",
|
||||
"columnTitle": "",
|
||||
"detailsTitle": ""
|
||||
},
|
||||
"ratio": {
|
||||
@@ -3217,7 +3217,7 @@
|
||||
"detailsTitle": ""
|
||||
},
|
||||
"state": {
|
||||
"columnTitle": "State",
|
||||
"columnTitle": "",
|
||||
"detailsTitle": ""
|
||||
},
|
||||
"time": {
|
||||
@@ -3315,7 +3315,7 @@
|
||||
}
|
||||
},
|
||||
"mediaRequests-requestList": {
|
||||
"name": "Media Requests List",
|
||||
"name": "",
|
||||
"description": "",
|
||||
"option": {
|
||||
"linksTargetNewTab": {
|
||||
@@ -3337,15 +3337,15 @@
|
||||
"unknown": "",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
"requested": "Requested",
|
||||
"requested": "",
|
||||
"partiallyAvailable": "",
|
||||
"available": "",
|
||||
"blacklisted": "Blacklisted",
|
||||
"blacklisted": "",
|
||||
"deleted": ""
|
||||
},
|
||||
"status": {
|
||||
"pending": "",
|
||||
"approved": "Approved",
|
||||
"approved": "",
|
||||
"declined": "",
|
||||
"failed": "",
|
||||
"completed": ""
|
||||
@@ -3358,7 +3358,7 @@
|
||||
"option": {},
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "Media Stats",
|
||||
"main": "",
|
||||
"approved": "",
|
||||
"pending": "",
|
||||
"processing": "",
|
||||
@@ -3402,15 +3402,15 @@
|
||||
"panel": {
|
||||
"statistics": {
|
||||
"empty": "",
|
||||
"transcodes": "Transcodes",
|
||||
"transcodes": "",
|
||||
"transcodesCount": "",
|
||||
"healthChecksCount": "",
|
||||
"filesCount": "",
|
||||
"savedSpace": "",
|
||||
"healthChecks": "Health checks",
|
||||
"videoCodecs": "Codecs",
|
||||
"videoContainers": "Containers",
|
||||
"videoResolutions": "Resolutions"
|
||||
"healthChecks": "",
|
||||
"videoCodecs": "",
|
||||
"videoContainers": "",
|
||||
"videoResolutions": ""
|
||||
},
|
||||
"workers": {
|
||||
"empty": "",
|
||||
@@ -3620,7 +3620,7 @@
|
||||
"option": {},
|
||||
"card": {
|
||||
"vpn": {
|
||||
"countConnected": "{count} connected"
|
||||
"countConnected": ""
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -3637,14 +3637,14 @@
|
||||
"name": ""
|
||||
},
|
||||
"wifi": {
|
||||
"name": "Wi-Fi"
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"label": ""
|
||||
},
|
||||
"guests": {
|
||||
"label": "Guests"
|
||||
"label": ""
|
||||
}
|
||||
},
|
||||
"option": {
|
||||
@@ -3890,7 +3890,7 @@
|
||||
"option": {
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"gpu": "GPU",
|
||||
"gpu": "",
|
||||
"network": ""
|
||||
}
|
||||
},
|
||||
@@ -3908,7 +3908,7 @@
|
||||
"cpu": "",
|
||||
"memory": "",
|
||||
"gpu": "",
|
||||
"network": "NET",
|
||||
"network": "",
|
||||
"up": "",
|
||||
"down": ""
|
||||
}
|
||||
@@ -4956,7 +4956,7 @@
|
||||
"title": "",
|
||||
"status": {
|
||||
"idle": "",
|
||||
"running": "Running",
|
||||
"running": "",
|
||||
"error": "",
|
||||
"disabled": ""
|
||||
},
|
||||
@@ -5858,7 +5858,7 @@
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Search for anything",
|
||||
"placeholder": "",
|
||||
"nothingFound": "",
|
||||
"error": {
|
||||
"fetch": ""
|
||||
|
||||
@@ -604,7 +604,7 @@
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"search": "Trouver une app",
|
||||
"search": "Trouver une application",
|
||||
"page": {
|
||||
"list": {
|
||||
"title": "Applications",
|
||||
@@ -3358,7 +3358,7 @@
|
||||
"option": {},
|
||||
"titles": {
|
||||
"stats": {
|
||||
"main": "Stats des médias",
|
||||
"main": "Statistiques des médias",
|
||||
"approved": "Déjà approuvé",
|
||||
"pending": "En attente de validation",
|
||||
"processing": "En cours de traitement",
|
||||
@@ -4118,7 +4118,7 @@
|
||||
"internalServerError": ""
|
||||
},
|
||||
"history": "Historique",
|
||||
"resolved": "Résolu",
|
||||
"resolved": "Résolut",
|
||||
"status": {
|
||||
"triggered": "",
|
||||
"ok": ""
|
||||
@@ -4173,12 +4173,12 @@
|
||||
"chart": {
|
||||
"cpu": {
|
||||
"title": "",
|
||||
"subtitle": "Utilisation moyenne du CPU à l'échelle du système",
|
||||
"subtitle": "",
|
||||
"series": "CPU"
|
||||
},
|
||||
"memory": {
|
||||
"title": "Utilisation Mémoire",
|
||||
"subtitle": "Utilisation précise au moment enregistré",
|
||||
"subtitle": "",
|
||||
"series": "",
|
||||
"cache": ""
|
||||
},
|
||||
@@ -4189,7 +4189,7 @@
|
||||
},
|
||||
"diskIO": {
|
||||
"title": "",
|
||||
"subtitle": "Débit du système de fichiers root",
|
||||
"subtitle": "",
|
||||
"read": "",
|
||||
"write": ""
|
||||
},
|
||||
@@ -5766,7 +5766,7 @@
|
||||
"label": "Médias"
|
||||
},
|
||||
"apps": {
|
||||
"label": "Apps",
|
||||
"label": "Applications",
|
||||
"new": {
|
||||
"label": "Nouveau"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -3452,7 +3452,7 @@
|
||||
"queued": ""
|
||||
},
|
||||
"type": {
|
||||
"movie": "Film",
|
||||
"movie": "",
|
||||
"episode": ""
|
||||
},
|
||||
"empty": {
|
||||
|
||||
@@ -4887,11 +4887,11 @@
|
||||
"text": "Не індексувати сайт у пошукових системах і не показувати його в жодному результаті пошуку"
|
||||
},
|
||||
"noFollow": {
|
||||
"title": "Не переходити",
|
||||
"title": "Не переходити (No follow)",
|
||||
"text": "Не переходьте за посиланнями під час індексації. Якщо вимкнути це, то сканери намагатимуться переходити за всіма посиланнями на Homarr."
|
||||
},
|
||||
"noTranslate": {
|
||||
"title": "Не перекладати",
|
||||
"title": "Не перекладати (No translate)",
|
||||
"text": "Якщо мова сайту не є тією, яку користувач, імовірно, захоче прочитати, Google покаже посилання на переклад у результатах пошуку"
|
||||
},
|
||||
"noSiteLinksSearchBox": {
|
||||
|
||||
Reference in new issue
Block a user