Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba88e95812 |
No files matched your search
+5
-12
@@ -15,24 +15,17 @@ COPY patches ./patches
|
||||
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
||||
# @homarr/definitions generates documentation types during install.
|
||||
COPY --parents ./packages/definitions/src ./
|
||||
# Workaround for pnpm/pnpm#5268: pnpm fetch crashes when patchedDependencies
|
||||
# are configured with nodeLinker: hoisted. The applyPatchToDir function tries
|
||||
# to chdir into node_modules/<pkg> which doesn't exist during fetch (only the
|
||||
# content-addressable store is populated). By temporarily switching to the
|
||||
# isolated linker, patches apply inside the virtual store (node_modules/.pnpm/...)
|
||||
# which IS created by pnpm fetch. The original hoisted linker is restored
|
||||
# before the install step so the final node_modules layout stays flat.
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
pnpm config set store-dir /pnpm/store && \
|
||||
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
|
||||
pnpm fetch --ignore-scripts && \
|
||||
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
|
||||
pnpm fetch --ignore-scripts
|
||||
|
||||
# Install only from the fetched, committed lockfile so local and Docker builds
|
||||
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
|
||||
# atomic binary replacement racing across the hoisted workspace (pnpm/pnpm#8200).
|
||||
# atomic binary replacement racing across the workspace (pnpm/pnpm#8200).
|
||||
# --force re-runs native build scripts (better-sqlite3 etc.) that the offline
|
||||
# store otherwise considers "already built" without the actual artifacts.
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1
|
||||
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1 --force
|
||||
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ import Alert from '@theme/Admonition';
|
||||
]}
|
||||
/>
|
||||
|
||||
The storage values shown by these widgets use the root ZFS dataset's usable `used` and `available` space, including RAIDZ parity overhead and dataset limits.
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ import temperature from './img/temperature.png';
|
||||
### Interactions
|
||||
|
||||
- Toggle entity state by clicking on the entity (only when clickable enabled).
|
||||
- After a successful toggle, the displayed state refreshes immediately.
|
||||
|
||||
### Adding the widget
|
||||
|
||||
@@ -65,4 +64,4 @@ import retrieveEntityId from './img/retrieve-entity-id.png';
|
||||
|
||||
<div>
|
||||
<img src={retrieveEntityId} alt="homeassistant retrieve-entity-id" width={256 * 2} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,7 +47,4 @@ import withDisplayname from './img/with-displayname.png';
|
||||
<AddingWidget />
|
||||
|
||||
### Configuration
|
||||
|
||||
Use the Home Assistant entity ID (for example, `automation.turn_on_lights`) in the **Automation ID** field, not the automation's internal ID.
|
||||
|
||||
<WidgetConfig configuration={smartHomeExecuteAutomationWidget.configuration} />
|
||||
@@ -30,6 +30,7 @@
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@nivo/line": "^0.99.0",
|
||||
"@scalar/api-reference-react": "catalog:",
|
||||
"@scalar/types": "catalog:",
|
||||
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"@tailwindcss/postcss": "^4.3.2",
|
||||
@@ -52,6 +53,7 @@
|
||||
"@docusaurus/types": "^3.10.1",
|
||||
"@fec/remark-a11y-emoji": "^4.0.2",
|
||||
"@tsconfig/docusaurus": "^2.0.9",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"cheerio": "^1.2.0",
|
||||
|
||||
@@ -50,8 +50,10 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/websocket": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"@mantine/charts": "catalog:",
|
||||
"@mantine/colors-generator": "catalog:",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/dates": "catalog:",
|
||||
"@mantine/dropzone": "catalog:",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"@mantine/modals": "catalog:",
|
||||
@@ -88,7 +90,9 @@
|
||||
"mantine-react-table": "catalog:",
|
||||
"mcp-handler": "catalog:",
|
||||
"next": "catalog:",
|
||||
"next-intl": "catalog:",
|
||||
"postcss-preset-mantine": "catalog:",
|
||||
"postcss-simple-vars": "catalog:",
|
||||
"posthog-js": "catalog:",
|
||||
"prismjs": "catalog:",
|
||||
"react": "catalog:",
|
||||
@@ -99,6 +103,7 @@
|
||||
"sql.js": "catalog:",
|
||||
"superjson": "catalog:",
|
||||
"trpc-to-mcp": "catalog:",
|
||||
"trpc-to-openapi": "catalog:",
|
||||
"use-deep-compare-effect": "catalog:",
|
||||
"yaml": "catalog:",
|
||||
"zod": "catalog:"
|
||||
|
||||
@@ -26,12 +26,14 @@
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@trpc/server": "catalog:",
|
||||
"dotenv": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"ws": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/ws": "catalog:",
|
||||
"esbuild": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
|
||||
@@ -7,6 +7,42 @@ import { createHomarrContainer } from "./shared/create-homarr-container";
|
||||
import { createSqliteDbFileAsync } from "./shared/e2e-db";
|
||||
|
||||
describe("Onboarding", () => {
|
||||
test("Credentials onboarding should be successful", async () => {
|
||||
// Arrange
|
||||
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||
const homarrContainer = await createHomarrContainer({
|
||||
mounts: {
|
||||
"/appdata": localMountPath,
|
||||
},
|
||||
}).start();
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
const actions = new OnboardingActions(page, db);
|
||||
const assertions = new OnboardingAssertions(page, db);
|
||||
|
||||
try {
|
||||
// Act
|
||||
await page.goto(`http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`);
|
||||
await actions.startOnboardingAsync("scratch");
|
||||
await actions.processUserStepAsync({
|
||||
username: "admin",
|
||||
password: "Comp(exP4sswOrd",
|
||||
confirmPassword: "Comp(exP4sswOrd",
|
||||
});
|
||||
await actions.processSettingsStepAsync();
|
||||
await actions.processIntegrationsStepAsync();
|
||||
|
||||
// Assert
|
||||
await assertions.assertFinishStepVisibleAsync();
|
||||
await assertions.assertUserAndAdminGroupInsertedAsync("admin");
|
||||
await assertions.assertDbOnboardingStepAsync("finish");
|
||||
} finally {
|
||||
await Promise.all([browser.close(), homarrContainer.stop()]);
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
test("External provider onboarding setup should be successful", async () => {
|
||||
// Arrange
|
||||
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||
|
||||
@@ -61,11 +61,13 @@
|
||||
"@semantic-release/release-notes-generator": "catalog:",
|
||||
"@testcontainers/redis": "catalog:",
|
||||
"@turbo/gen": "catalog:",
|
||||
"@types/node-unifi": "catalog:",
|
||||
"@vitejs/plugin-react": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"@vitest/ui": "catalog:",
|
||||
"conventional-changelog-conventionalcommits": "catalog:",
|
||||
"cross-env": "catalog:",
|
||||
"dotenv-cli": "catalog:",
|
||||
"jsdom": "catalog:",
|
||||
"json5": "catalog:",
|
||||
"oxfmt": "catalog:",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -47,23 +47,31 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "catalog:",
|
||||
"@paralleldrive/cuid2": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"@tiptap/react": "catalog:",
|
||||
"@trpc/client": "catalog:",
|
||||
"@trpc/react-query": "catalog:",
|
||||
"@trpc/server": "catalog:",
|
||||
"@trpc/tanstack-react-query": "catalog:",
|
||||
"drizzle-orm": "catalog:",
|
||||
"jsonpath-plus": "catalog:",
|
||||
"lodash.clonedeep": "catalog:",
|
||||
"next": "catalog:",
|
||||
"pg": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"superjson": "catalog:",
|
||||
"trpc-to-mcp": "catalog:",
|
||||
"trpc-to-openapi": "catalog:",
|
||||
"zod": "catalog:"
|
||||
"zod": "catalog:",
|
||||
"zod-form-data": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/pg": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||
@@ -42,13 +41,6 @@ export const smartHomeRouter = createTRPCRouter({
|
||||
const client = await createIntegrationAsync(integration);
|
||||
const success = await client.triggerToggleAsync(input.entityId);
|
||||
|
||||
if (!success) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Home Assistant failed to toggle the entity",
|
||||
});
|
||||
}
|
||||
|
||||
return success;
|
||||
}),
|
||||
executeAutomation: protectedProcedure
|
||||
@@ -63,13 +55,6 @@ export const smartHomeRouter = createTRPCRouter({
|
||||
.input(z.object({ automationId: z.string() }))
|
||||
.mutation(async ({ ctx: { integration }, input }) => {
|
||||
const client = await createIntegrationAsync(integration);
|
||||
const success = await client.triggerAutomationAsync(input.automationId);
|
||||
|
||||
if (!success) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Home Assistant failed to trigger the automation",
|
||||
});
|
||||
}
|
||||
await client.triggerAutomationAsync(input.automationId);
|
||||
}),
|
||||
});
|
||||
@@ -32,6 +32,7 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"bcrypt": "catalog:",
|
||||
"cookies": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"ldapts": "catalog:",
|
||||
"next": "catalog:",
|
||||
"next-auth": "catalog:",
|
||||
@@ -43,6 +44,8 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "catalog:",
|
||||
"@types/cookies": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/api": "workspace:^0.1.0",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"next": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"esbuild": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
|
||||
@@ -26,11 +26,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@mantine/core": "catalog:",
|
||||
"@paralleldrive/cuid2": "catalog:",
|
||||
"@tanstack/query-core": "catalog:",
|
||||
"@types/node-fetch": "catalog:",
|
||||
"axios": "catalog:",
|
||||
"cookie": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"next": "catalog:",
|
||||
"node-fetch": "catalog:",
|
||||
"octokit": "catalog:",
|
||||
"ofetch": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"undici": "catalog:",
|
||||
@@ -39,6 +45,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/node-fetch": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "catalog:",
|
||||
"axios": "catalog:",
|
||||
"better-sqlite3": "catalog:",
|
||||
"dns-caching": "catalog:",
|
||||
"drizzle-orm": "catalog:",
|
||||
@@ -44,12 +45,15 @@
|
||||
"mysql2": "catalog:",
|
||||
"pg": "catalog:",
|
||||
"superjson": "catalog:",
|
||||
"undici": "catalog:",
|
||||
"winston": "catalog:",
|
||||
"winston-transport": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/pg": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ import { UndiciHttpAgent } from "@homarr/core/infrastructure/http";
|
||||
import type { TrustedCertificateHostname } from "../certificates/hostnames";
|
||||
import { withTimeoutAsync } from "./timeout";
|
||||
|
||||
import packageJson from "../../../../../package.json";
|
||||
|
||||
export const getDefaultUserAgent = () => `Homarr/${packageJson.version} (+https://homarr.dev)`;
|
||||
|
||||
export const createCustomCheckServerIdentity = (
|
||||
trustedHostnames: TrustedCertificateHostname[],
|
||||
): typeof checkServerIdentity => {
|
||||
@@ -83,10 +79,6 @@ export const fetchWithTrustedCertificatesAsync = async (
|
||||
async (signal) =>
|
||||
fetch(url, {
|
||||
...fetchOptions,
|
||||
headers: {
|
||||
"User-Agent": getDefaultUserAgent(),
|
||||
...fetchOptions.headers,
|
||||
},
|
||||
signal,
|
||||
dispatcher: agent,
|
||||
}),
|
||||
@@ -97,10 +89,6 @@ export const fetchWithTrustedCertificatesAsync = async (
|
||||
const { bodyTimeout: _bodyTimeout, dispatcher: _dispatcher, ...fetchOptions } = options ?? {};
|
||||
return fetch(url, {
|
||||
...fetchOptions,
|
||||
headers: {
|
||||
"User-Agent": getDefaultUserAgent(),
|
||||
...fetchOptions.headers,
|
||||
},
|
||||
dispatcher: agent,
|
||||
});
|
||||
};
|
||||
@@ -26,6 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -38,11 +38,16 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"croner": "catalog:",
|
||||
"drizzle-orm": "catalog:",
|
||||
"pg": "catalog:",
|
||||
"semver-parser": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/pg": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/dates": "catalog:",
|
||||
"@paralleldrive/cuid2": "catalog:",
|
||||
"@testcontainers/mysql": "catalog:",
|
||||
"@testcontainers/postgresql": "catalog:",
|
||||
@@ -66,6 +67,7 @@
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/better-sqlite3": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/pg": "catalog:",
|
||||
"dotenv-cli": "catalog:",
|
||||
"esbuild": "catalog:",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
|
||||
@@ -25,11 +25,14 @@
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"dockerode": "catalog:"
|
||||
"@mantine/core": "catalog:",
|
||||
"dockerode": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,13 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/form": "catalog:",
|
||||
"react": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -30,11 +30,19 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"@trpc/server": "catalog:",
|
||||
"next": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,16 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@immich/sdk": "catalog:",
|
||||
"@jellyfin/sdk": "catalog:",
|
||||
"@mantine/core": "catalog:",
|
||||
"axios": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"http-cookie-agent": "catalog:",
|
||||
"ical.js": "catalog:",
|
||||
"maria2": "catalog:",
|
||||
"node-fetch": "catalog:",
|
||||
"octokit": "catalog:",
|
||||
"proxmox-api": "catalog:",
|
||||
"superjson": "catalog:",
|
||||
"timezones-ical-library": "catalog:",
|
||||
"tsdav": "catalog:",
|
||||
"undici": "catalog:",
|
||||
@@ -51,6 +57,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/node-fetch": "catalog:",
|
||||
"@types/node-unifi": "catalog:",
|
||||
"@types/ws": "catalog:",
|
||||
"@types/xml2js": "catalog:",
|
||||
|
||||
@@ -134,10 +134,7 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
|
||||
*/
|
||||
private async postAsync(path: `/api/${string}`, body: Record<string, string>) {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url(path), {
|
||||
headers: {
|
||||
...this.getAuthHeaders(),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: this.getAuthHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
@@ -148,14 +148,6 @@ const happyResponder = (method: string) => {
|
||||
];
|
||||
case "pool.query":
|
||||
return [{ name: "tank", status: "ONLINE", healthy: true, free: 500, size: 1000, allocated: 500 }];
|
||||
case "pool.dataset.query":
|
||||
return [
|
||||
{
|
||||
id: "tank",
|
||||
used: { parsed: 200 },
|
||||
available: { parsed: 300 },
|
||||
},
|
||||
];
|
||||
case "interface.query":
|
||||
return [{ id: "eth0", name: "eth0" }];
|
||||
case "reporting.netdata_get_data":
|
||||
@@ -199,18 +191,6 @@ describe("TrueNasIntegration", () => {
|
||||
// JSON-RPC does not use the legacy "connect" session handshake.
|
||||
expect(ws.sentPayloads.some((payload) => payload.msg === "connect")).toBe(false);
|
||||
|
||||
const datasetPayload = ws.sentPayloads.find((payload) => payload.method === "pool.dataset.query");
|
||||
expect(datasetPayload).toMatchObject({
|
||||
params: [
|
||||
[["id", "in", ["tank"]]],
|
||||
{
|
||||
extra: {
|
||||
properties: ["used", "available"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
version: "TrueNAS-SCALE-24.10",
|
||||
cpuModelName: "Intel Xeon",
|
||||
@@ -224,25 +204,12 @@ describe("TrueNasIntegration", () => {
|
||||
loadAverage: null,
|
||||
gpu: [],
|
||||
network: { up: 20_000, down: 10_000 },
|
||||
// `available` and `used` come from the root dataset, not the physical pool.
|
||||
fileSystem: [{ deviceName: "tank", available: "300", used: "200", percentage: 40 }],
|
||||
// `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 }],
|
||||
});
|
||||
});
|
||||
|
||||
test("reports zero usage for an empty root dataset", async () => {
|
||||
ws.setResponder((method) =>
|
||||
method === "pool.dataset.query"
|
||||
? [{ id: "tank", used: { parsed: 0 }, available: { parsed: 0 } }]
|
||||
: happyResponder(method),
|
||||
);
|
||||
const integration = createIntegration(credentials);
|
||||
|
||||
const result = await integration.getSystemInfoAsync();
|
||||
|
||||
expect(result.fileSystem[0]?.percentage).toBe(0);
|
||||
});
|
||||
|
||||
test("authenticates with an API key when one is configured", async () => {
|
||||
const integration = createIntegration([{ kind: "apiKey", value: "api-key-123" }]);
|
||||
|
||||
|
||||
@@ -49,10 +49,9 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
||||
memUsedInBytes,
|
||||
fileSystem: datasets.map((dataset) => ({
|
||||
deviceName: dataset.name,
|
||||
available: `${dataset.available}`,
|
||||
used: `${dataset.used}`,
|
||||
percentage:
|
||||
dataset.used + dataset.available === 0 ? 0 : (dataset.used / (dataset.used + dataset.available)) * 100,
|
||||
available: `${dataset.free}`, // free space left on the pool
|
||||
used: `${dataset.allocated}`,
|
||||
percentage: (dataset.allocated / dataset.size) * 100,
|
||||
})),
|
||||
availablePkgUpdates: 0,
|
||||
network: {
|
||||
@@ -118,40 +117,12 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
||||
return null;
|
||||
})
|
||||
.filter((pool) => pool !== null);
|
||||
|
||||
if (activePools.length === 0) return [];
|
||||
|
||||
const datasets = await this.requestAsync("pool.dataset.query", [
|
||||
[["id", "in", activePools.map((pool) => pool.name)]],
|
||||
{
|
||||
extra: {
|
||||
properties: ["used", "available"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
const parsedDatasets = await poolDatasetSchema.parseAsync(datasets);
|
||||
|
||||
const poolsWithUsableSpace = activePools
|
||||
.map((pool) => {
|
||||
const dataset = parsedDatasets.find((candidate) => candidate.id === pool.name);
|
||||
if (!dataset || dataset.used.parsed === null || dataset.available.parsed === null) return null;
|
||||
|
||||
return {
|
||||
name: pool.name,
|
||||
status: pool.status,
|
||||
healthy: pool.healthy,
|
||||
used: dataset.used.parsed,
|
||||
available: dataset.available.parsed,
|
||||
};
|
||||
})
|
||||
.filter((pool) => pool !== null);
|
||||
|
||||
logger.debug("Retrieved pools", {
|
||||
url: this.integration.url,
|
||||
totalCount: result.length,
|
||||
activeCount: poolsWithUsableSpace.length,
|
||||
activeCount: activePools.length,
|
||||
});
|
||||
return poolsWithUsableSpace;
|
||||
return activePools;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,14 +263,6 @@ const poolSchema = z.array(
|
||||
}),
|
||||
);
|
||||
|
||||
const poolDatasetSchema = z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
used: z.object({ parsed: z.number().min(0).nullable() }),
|
||||
available: z.object({ parsed: z.number().min(0).nullable() }),
|
||||
}),
|
||||
);
|
||||
|
||||
const reportingNetDataSchema = z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { HomeAssistantIntegration } from "../src";
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/http", () => ({
|
||||
fetchWithTrustedCertificatesAsync: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
|
||||
const integration = new HomeAssistantIntegration({
|
||||
id: "home-assistant",
|
||||
name: "Home Assistant",
|
||||
url: "http://home-assistant.local:8123",
|
||||
externalUrl: null,
|
||||
decryptedSecrets: [{ kind: "apiKey", value: "secret" }],
|
||||
});
|
||||
|
||||
describe("HomeAssistantIntegration actions", () => {
|
||||
beforeEach(() => {
|
||||
mockFetch.mockReset();
|
||||
mockFetch.mockResolvedValue({ ok: true } as never);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["toggle", () => integration.triggerToggleAsync("light.desk"), "/api/services/homeassistant/toggle", "light.desk"],
|
||||
[
|
||||
"automation",
|
||||
() => integration.triggerAutomationAsync("automation.turn_on_lights"),
|
||||
"/api/services/automation/trigger",
|
||||
"automation.turn_on_lights",
|
||||
],
|
||||
])("sends the %s service call as JSON", async (_action, trigger, path, entityId) => {
|
||||
await expect(trigger()).resolves.toBe(true);
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(new URL(`http://home-assistant.local:8123${path}`), {
|
||||
headers: {
|
||||
Authorization: "Bearer secret",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ entity_id: entityId }),
|
||||
method: "POST",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -33,8 +33,11 @@
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/dates": "catalog:",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"mantine-react-table": "catalog:",
|
||||
"next": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
@@ -42,6 +45,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,9 @@
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/adm-zip": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0"
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"undici": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -29,14 +29,19 @@
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"dockerode": "catalog:",
|
||||
"octokit": "catalog:",
|
||||
"semver-parser": "catalog:",
|
||||
"superjson": "catalog:",
|
||||
"undici": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,4 @@ export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandle
|
||||
|
||||
return result.data.state;
|
||||
},
|
||||
// The widget already polls every 30 seconds and refetches after toggles.
|
||||
cacheTtlMs: 0,
|
||||
});
|
||||
@@ -26,6 +26,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,12 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/dates": "catalog:",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"@mantine/spotlight": "catalog:",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"jotai": "catalog:",
|
||||
"next": "catalog:",
|
||||
"react": "catalog:",
|
||||
@@ -47,6 +50,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -24,16 +24,21 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gfazioli/mantine-onboarding-tour": "catalog:",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "catalog:",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/dates": "catalog:",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"crypto-js": "catalog:",
|
||||
"csstype": "catalog:",
|
||||
"mantine-react-table": "catalog:",
|
||||
"next": "catalog:",
|
||||
"react": "catalog:",
|
||||
@@ -44,6 +49,9 @@
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/crypto-js": "catalog:",
|
||||
"@types/css-modules": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,13 @@
|
||||
"dependencies": {
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@mantine/dates": "catalog:",
|
||||
"zod": "catalog:",
|
||||
"zod-form-data": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -59,9 +59,12 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "catalog:",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/dates": "catalog:",
|
||||
"@mantine/form": "catalog:",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"@mantine/tiptap": "catalog:",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"@tanstack/react-query": "catalog:",
|
||||
"@tiptap/extension-color": "catalog:",
|
||||
"@tiptap/extension-highlight": "catalog:",
|
||||
"@tiptap/extension-image": "catalog:",
|
||||
@@ -78,6 +81,8 @@
|
||||
"@tiptap/extension-underline": "catalog:",
|
||||
"@tiptap/react": "catalog:",
|
||||
"@tiptap/starter-kit": "catalog:",
|
||||
"@trpc/client": "catalog:",
|
||||
"@trpc/server": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"jsonpath-plus": "catalog:",
|
||||
@@ -85,6 +90,8 @@
|
||||
"mantine-datatable": "catalog:",
|
||||
"mantine-react-table": "catalog:",
|
||||
"next": "catalog:",
|
||||
"next-intl": "catalog:",
|
||||
"prosemirror-model": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-jsx-parser": "catalog:",
|
||||
@@ -96,6 +103,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@types/video.js": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
|
||||
Generated
+458
-48
File diff suppressed because it is too large.
Load diff
+12
-1
@@ -35,6 +35,7 @@ catalog:
|
||||
"@mantine/spotlight": ^9.4.1
|
||||
"@mantine/tiptap": ^9.4.1
|
||||
"@octokit/auth-app": ^8.2.0
|
||||
"@octokit/plugin-rest-endpoint-methods": ^17.0.0
|
||||
"@paralleldrive/cuid2": ^3.3.0
|
||||
"@playwright/test": ^1.61.1
|
||||
"@semantic-release/changelog": ^7.0.0
|
||||
@@ -45,6 +46,7 @@ catalog:
|
||||
"@semantic-release/release-notes-generator": ^14.1.1
|
||||
"@t3-oss/env-nextjs": ^0.13.11
|
||||
"@tabler/icons-react": ^3.44.0
|
||||
"@tanstack/query-core": ^5.101.2
|
||||
"@tanstack/react-devtools": ^0.10.8
|
||||
"@tanstack/react-query": ^5.101.2
|
||||
"@tanstack/react-query-devtools": ^5.101.2
|
||||
@@ -83,15 +85,22 @@ catalog:
|
||||
"@types/css-modules": ^1.0.5
|
||||
"@types/dockerode": ^4.0.1
|
||||
"@types/node": ^24.13.3
|
||||
"@types/node-fetch": ^2.6.13
|
||||
"@types/node-unifi": ^2.5.1
|
||||
"@types/pg": ^8.20.0
|
||||
"@types/prismjs": ^1.26.6
|
||||
"@types/react": 19.2.17
|
||||
"@types/react-dom": 19.2.3
|
||||
"@scalar/api-reference-react": ^0.9.54
|
||||
"@scalar/types": ^0.16.2
|
||||
"@types/video.js": ^7.3.58
|
||||
"@types/ws": ^8.18.1
|
||||
"@types/xml2js": ^0.4.14
|
||||
axios: ^1.15.0
|
||||
csstype: ^3.2.3
|
||||
http-cookie-agent: ^6.0.8
|
||||
node-fetch: ^2.7.0
|
||||
ofetch: ^1.5.1
|
||||
posthog-js: ^1.399.1
|
||||
posthog-node: ^5.40.0
|
||||
"@vitejs/plugin-react": ^6.0.3
|
||||
@@ -102,6 +111,7 @@ catalog:
|
||||
"@xterm/xterm": ^6.0.0
|
||||
adm-zip: 0.5.18
|
||||
jszip: ^3.10.1
|
||||
prosemirror-model: ^1.25.10
|
||||
sql.js: ^1.14.1
|
||||
babel-plugin-react-compiler: ^1.0.0
|
||||
bcrypt: ^6.0.0
|
||||
@@ -154,6 +164,7 @@ catalog:
|
||||
oxlint: ^1.73.0
|
||||
pg: ^8.22.0
|
||||
postcss-preset-mantine: ^1.18.0
|
||||
postcss-simple-vars: ^7.0.1
|
||||
prismjs: ^1.30.0
|
||||
proxmox-api: 1.1.1
|
||||
react: 19.2.7
|
||||
@@ -184,6 +195,7 @@ catalog:
|
||||
vite-tsconfig-paths: ^6.1.1
|
||||
vitest: ^4.1.10
|
||||
winston: 3.19.0
|
||||
winston-transport: ^4.9.0
|
||||
ws: ^8.21.0
|
||||
xml2js: ^0.6.2
|
||||
yaml: 2.9.0
|
||||
@@ -192,7 +204,6 @@ catalog:
|
||||
zod-validation-error: ^5.0.0
|
||||
|
||||
catalogMode: strict
|
||||
nodeLinker: hoisted
|
||||
strictPeerDependencies: false
|
||||
engineStrict: true
|
||||
verifyDepsBeforeRun: warn
|
||||
|
||||
Reference in new issue
Block a user