Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b74d73de0 | ||
|
|
44d7426e9f | ||
|
|
c08ad3a69a | ||
|
|
25b7657a79 | ||
|
|
df438b4cd2 | ||
|
|
a1f91eb385 | ||
|
|
56e6b23af6 | ||
|
|
5488bf4d18 | ||
|
|
7652eb8c1c | ||
|
|
e761c10f32 | ||
|
|
f6137b2dcc | ||
|
|
425dd90871 |
No files matched your search
+12
-5
@@ -15,17 +15,24 @@ COPY patches ./patches
|
|||||||
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
||||||
# @homarr/definitions generates documentation types during install.
|
# @homarr/definitions generates documentation types during install.
|
||||||
COPY --parents ./packages/definitions/src ./
|
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 \
|
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||||
pnpm config set store-dir /pnpm/store && \
|
pnpm config set store-dir /pnpm/store && \
|
||||||
pnpm fetch --ignore-scripts
|
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
|
||||||
|
pnpm fetch --ignore-scripts && \
|
||||||
|
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
|
||||||
|
|
||||||
# Install only from the fetched, committed lockfile so local and Docker builds
|
# Install only from the fetched, committed lockfile so local and Docker builds
|
||||||
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
|
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
|
||||||
# atomic binary replacement racing across the workspace (pnpm/pnpm#8200).
|
# atomic binary replacement racing across the hoisted 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 \
|
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||||
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1 --force
|
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ 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
|
### Adding the integration
|
||||||
<AddingIntegration />
|
<AddingIntegration />
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import temperature from './img/temperature.png';
|
|||||||
### Interactions
|
### Interactions
|
||||||
|
|
||||||
- Toggle entity state by clicking on the entity (only when clickable enabled).
|
- Toggle entity state by clicking on the entity (only when clickable enabled).
|
||||||
|
- After a successful toggle, the displayed state refreshes immediately.
|
||||||
|
|
||||||
### Adding the widget
|
### Adding the widget
|
||||||
|
|
||||||
@@ -64,4 +65,4 @@ import retrieveEntityId from './img/retrieve-entity-id.png';
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<img src={retrieveEntityId} alt="homeassistant retrieve-entity-id" width={256 * 2} />
|
<img src={retrieveEntityId} alt="homeassistant retrieve-entity-id" width={256 * 2} />
|
||||||
</div>
|
</div>
|
||||||
@@ -47,4 +47,7 @@ import withDisplayname from './img/with-displayname.png';
|
|||||||
<AddingWidget />
|
<AddingWidget />
|
||||||
|
|
||||||
### Configuration
|
### 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} />
|
<WidgetConfig configuration={smartHomeExecuteAutomationWidget.configuration} />
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
"@mdx-js/react": "^3.1.1",
|
"@mdx-js/react": "^3.1.1",
|
||||||
"@nivo/line": "^0.99.0",
|
"@nivo/line": "^0.99.0",
|
||||||
"@scalar/api-reference-react": "catalog:",
|
"@scalar/api-reference-react": "catalog:",
|
||||||
"@scalar/types": "catalog:",
|
|
||||||
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
|
"@signalwire/docusaurus-plugin-llms-txt": "^1.2.2",
|
||||||
"@tabler/icons-react": "catalog:",
|
"@tabler/icons-react": "catalog:",
|
||||||
"@tailwindcss/postcss": "^4.3.2",
|
"@tailwindcss/postcss": "^4.3.2",
|
||||||
@@ -53,7 +52,6 @@
|
|||||||
"@docusaurus/types": "^3.10.1",
|
"@docusaurus/types": "^3.10.1",
|
||||||
"@fec/remark-a11y-emoji": "^4.0.2",
|
"@fec/remark-a11y-emoji": "^4.0.2",
|
||||||
"@tsconfig/docusaurus": "^2.0.9",
|
"@tsconfig/docusaurus": "^2.0.9",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"@types/react-dom": "catalog:",
|
"@types/react-dom": "catalog:",
|
||||||
"cheerio": "^1.2.0",
|
"cheerio": "^1.2.0",
|
||||||
|
|||||||
@@ -50,10 +50,8 @@
|
|||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@homarr/websocket": "workspace:^0.1.0",
|
"@homarr/websocket": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"@mantine/charts": "catalog:",
|
|
||||||
"@mantine/colors-generator": "catalog:",
|
"@mantine/colors-generator": "catalog:",
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/dates": "catalog:",
|
|
||||||
"@mantine/dropzone": "catalog:",
|
"@mantine/dropzone": "catalog:",
|
||||||
"@mantine/hooks": "catalog:",
|
"@mantine/hooks": "catalog:",
|
||||||
"@mantine/modals": "catalog:",
|
"@mantine/modals": "catalog:",
|
||||||
@@ -90,9 +88,7 @@
|
|||||||
"mantine-react-table": "catalog:",
|
"mantine-react-table": "catalog:",
|
||||||
"mcp-handler": "catalog:",
|
"mcp-handler": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"next-intl": "catalog:",
|
|
||||||
"postcss-preset-mantine": "catalog:",
|
"postcss-preset-mantine": "catalog:",
|
||||||
"postcss-simple-vars": "catalog:",
|
|
||||||
"posthog-js": "catalog:",
|
"posthog-js": "catalog:",
|
||||||
"prismjs": "catalog:",
|
"prismjs": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
@@ -103,7 +99,6 @@
|
|||||||
"sql.js": "catalog:",
|
"sql.js": "catalog:",
|
||||||
"superjson": "catalog:",
|
"superjson": "catalog:",
|
||||||
"trpc-to-mcp": "catalog:",
|
"trpc-to-mcp": "catalog:",
|
||||||
"trpc-to-openapi": "catalog:",
|
|
||||||
"use-deep-compare-effect": "catalog:",
|
"use-deep-compare-effect": "catalog:",
|
||||||
"yaml": "catalog:",
|
"yaml": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
|
|||||||
@@ -26,14 +26,12 @@
|
|||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@trpc/server": "catalog:",
|
|
||||||
"dotenv": "catalog:",
|
"dotenv": "catalog:",
|
||||||
"tsx": "catalog:",
|
"tsx": "catalog:",
|
||||||
"ws": "catalog:"
|
"ws": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/ws": "catalog:",
|
"@types/ws": "catalog:",
|
||||||
"esbuild": "catalog:",
|
"esbuild": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
|
|||||||
@@ -7,42 +7,6 @@ import { createHomarrContainer } from "./shared/create-homarr-container";
|
|||||||
import { createSqliteDbFileAsync } from "./shared/e2e-db";
|
import { createSqliteDbFileAsync } from "./shared/e2e-db";
|
||||||
|
|
||||||
describe("Onboarding", () => {
|
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 () => {
|
test("External provider onboarding setup should be successful", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { db, localMountPath } = await createSqliteDbFileAsync();
|
const { db, localMountPath } = await createSqliteDbFileAsync();
|
||||||
|
|||||||
@@ -61,13 +61,11 @@
|
|||||||
"@semantic-release/release-notes-generator": "catalog:",
|
"@semantic-release/release-notes-generator": "catalog:",
|
||||||
"@testcontainers/redis": "catalog:",
|
"@testcontainers/redis": "catalog:",
|
||||||
"@turbo/gen": "catalog:",
|
"@turbo/gen": "catalog:",
|
||||||
"@types/node-unifi": "catalog:",
|
|
||||||
"@vitejs/plugin-react": "catalog:",
|
"@vitejs/plugin-react": "catalog:",
|
||||||
"@vitest/coverage-v8": "catalog:",
|
"@vitest/coverage-v8": "catalog:",
|
||||||
"@vitest/ui": "catalog:",
|
"@vitest/ui": "catalog:",
|
||||||
"conventional-changelog-conventionalcommits": "catalog:",
|
"conventional-changelog-conventionalcommits": "catalog:",
|
||||||
"cross-env": "catalog:",
|
"cross-env": "catalog:",
|
||||||
"dotenv-cli": "catalog:",
|
|
||||||
"jsdom": "catalog:",
|
"jsdom": "catalog:",
|
||||||
"json5": "catalog:",
|
"json5": "catalog:",
|
||||||
"oxfmt": "catalog:",
|
"oxfmt": "catalog:",
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,31 +47,23 @@
|
|||||||
"@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": "catalog:",
|
"@kubernetes/client-node": "catalog:",
|
||||||
"@paralleldrive/cuid2": "catalog:",
|
|
||||||
"@tanstack/react-query": "catalog:",
|
"@tanstack/react-query": "catalog:",
|
||||||
"@tiptap/react": "catalog:",
|
|
||||||
"@trpc/client": "catalog:",
|
"@trpc/client": "catalog:",
|
||||||
"@trpc/react-query": "catalog:",
|
"@trpc/react-query": "catalog:",
|
||||||
"@trpc/server": "catalog:",
|
"@trpc/server": "catalog:",
|
||||||
"@trpc/tanstack-react-query": "catalog:",
|
"@trpc/tanstack-react-query": "catalog:",
|
||||||
"drizzle-orm": "catalog:",
|
|
||||||
"jsonpath-plus": "catalog:",
|
"jsonpath-plus": "catalog:",
|
||||||
"lodash.clonedeep": "catalog:",
|
"lodash.clonedeep": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"pg": "catalog:",
|
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:",
|
||||||
"superjson": "catalog:",
|
"superjson": "catalog:",
|
||||||
"trpc-to-mcp": "catalog:",
|
"trpc-to-mcp": "catalog:",
|
||||||
"trpc-to-openapi": "catalog:",
|
"trpc-to-openapi": "catalog:",
|
||||||
"zod": "catalog:",
|
"zod": "catalog:"
|
||||||
"zod-form-data": "catalog:"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/pg": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
@@ -41,6 +42,13 @@ export const smartHomeRouter = createTRPCRouter({
|
|||||||
const client = await createIntegrationAsync(integration);
|
const client = await createIntegrationAsync(integration);
|
||||||
const success = await client.triggerToggleAsync(input.entityId);
|
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;
|
return success;
|
||||||
}),
|
}),
|
||||||
executeAutomation: protectedProcedure
|
executeAutomation: protectedProcedure
|
||||||
@@ -55,6 +63,13 @@ export const smartHomeRouter = createTRPCRouter({
|
|||||||
.input(z.object({ automationId: z.string() }))
|
.input(z.object({ automationId: z.string() }))
|
||||||
.mutation(async ({ ctx: { integration }, input }) => {
|
.mutation(async ({ ctx: { integration }, input }) => {
|
||||||
const client = await createIntegrationAsync(integration);
|
const client = await createIntegrationAsync(integration);
|
||||||
await client.triggerAutomationAsync(input.automationId);
|
const success = await client.triggerAutomationAsync(input.automationId);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "Home Assistant failed to trigger the automation",
|
||||||
|
});
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"bcrypt": "catalog:",
|
"bcrypt": "catalog:",
|
||||||
"cookies": "catalog:",
|
"cookies": "catalog:",
|
||||||
"dayjs": "catalog:",
|
|
||||||
"ldapts": "catalog:",
|
"ldapts": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"next-auth": "catalog:",
|
"next-auth": "catalog:",
|
||||||
@@ -44,8 +43,6 @@
|
|||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/bcrypt": "catalog:",
|
"@types/bcrypt": "catalog:",
|
||||||
"@types/cookies": "catalog:",
|
"@types/cookies": "catalog:",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,16 +24,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/api": "workspace:^0.1.0",
|
"@homarr/api": "workspace:^0.1.0",
|
||||||
"@mantine/hooks": "catalog:",
|
|
||||||
"next": "catalog:",
|
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:"
|
"react-dom": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"esbuild": "catalog:",
|
"esbuild": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,17 +26,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@mantine/core": "catalog:",
|
|
||||||
"@paralleldrive/cuid2": "catalog:",
|
"@paralleldrive/cuid2": "catalog:",
|
||||||
"@tanstack/query-core": "catalog:",
|
|
||||||
"@types/node-fetch": "catalog:",
|
|
||||||
"axios": "catalog:",
|
|
||||||
"cookie": "catalog:",
|
"cookie": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"node-fetch": "catalog:",
|
|
||||||
"octokit": "catalog:",
|
"octokit": "catalog:",
|
||||||
"ofetch": "catalog:",
|
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:",
|
||||||
"undici": "catalog:",
|
"undici": "catalog:",
|
||||||
@@ -45,10 +39,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/node-fetch": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@t3-oss/env-nextjs": "catalog:",
|
"@t3-oss/env-nextjs": "catalog:",
|
||||||
"axios": "catalog:",
|
|
||||||
"better-sqlite3": "catalog:",
|
"better-sqlite3": "catalog:",
|
||||||
"dns-caching": "catalog:",
|
"dns-caching": "catalog:",
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
@@ -45,15 +44,12 @@
|
|||||||
"mysql2": "catalog:",
|
"mysql2": "catalog:",
|
||||||
"pg": "catalog:",
|
"pg": "catalog:",
|
||||||
"superjson": "catalog:",
|
"superjson": "catalog:",
|
||||||
"undici": "catalog:",
|
|
||||||
"winston": "catalog:",
|
"winston": "catalog:",
|
||||||
"winston-transport": "catalog:",
|
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/better-sqlite3": "catalog:",
|
"@types/better-sqlite3": "catalog:",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/pg": "catalog:",
|
"@types/pg": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import { UndiciHttpAgent } from "@homarr/core/infrastructure/http";
|
|||||||
import type { TrustedCertificateHostname } from "../certificates/hostnames";
|
import type { TrustedCertificateHostname } from "../certificates/hostnames";
|
||||||
import { withTimeoutAsync } from "./timeout";
|
import { withTimeoutAsync } from "./timeout";
|
||||||
|
|
||||||
|
import packageJson from "../../../../../package.json";
|
||||||
|
|
||||||
|
export const getDefaultUserAgent = () => `Homarr/${packageJson.version} (+https://homarr.dev)`;
|
||||||
|
|
||||||
export const createCustomCheckServerIdentity = (
|
export const createCustomCheckServerIdentity = (
|
||||||
trustedHostnames: TrustedCertificateHostname[],
|
trustedHostnames: TrustedCertificateHostname[],
|
||||||
): typeof checkServerIdentity => {
|
): typeof checkServerIdentity => {
|
||||||
@@ -79,6 +83,10 @@ export const fetchWithTrustedCertificatesAsync = async (
|
|||||||
async (signal) =>
|
async (signal) =>
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": getDefaultUserAgent(),
|
||||||
|
...fetchOptions.headers,
|
||||||
|
},
|
||||||
signal,
|
signal,
|
||||||
dispatcher: agent,
|
dispatcher: agent,
|
||||||
}),
|
}),
|
||||||
@@ -89,6 +97,10 @@ export const fetchWithTrustedCertificatesAsync = async (
|
|||||||
const { bodyTimeout: _bodyTimeout, dispatcher: _dispatcher, ...fetchOptions } = options ?? {};
|
const { bodyTimeout: _bodyTimeout, dispatcher: _dispatcher, ...fetchOptions } = options ?? {};
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
...fetchOptions,
|
...fetchOptions,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": getDefaultUserAgent(),
|
||||||
|
...fetchOptions.headers,
|
||||||
|
},
|
||||||
dispatcher: agent,
|
dispatcher: agent,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,16 +38,11 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"croner": "catalog:",
|
|
||||||
"drizzle-orm": "catalog:",
|
|
||||||
"pg": "catalog:",
|
|
||||||
"semver-parser": "catalog:",
|
"semver-parser": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/pg": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/dates": "catalog:",
|
|
||||||
"@paralleldrive/cuid2": "catalog:",
|
"@paralleldrive/cuid2": "catalog:",
|
||||||
"@testcontainers/mysql": "catalog:",
|
"@testcontainers/mysql": "catalog:",
|
||||||
"@testcontainers/postgresql": "catalog:",
|
"@testcontainers/postgresql": "catalog:",
|
||||||
@@ -67,7 +66,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/better-sqlite3": "catalog:",
|
"@types/better-sqlite3": "catalog:",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/pg": "catalog:",
|
"@types/pg": "catalog:",
|
||||||
"dotenv-cli": "catalog:",
|
"dotenv-cli": "catalog:",
|
||||||
"esbuild": "catalog:",
|
"esbuild": "catalog:",
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"tsx": "catalog:",
|
"tsx": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@mantine/core": "catalog:",
|
"dockerode": "catalog:"
|
||||||
"dockerode": "catalog:",
|
|
||||||
"zod": "catalog:"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/dockerode": "catalog:",
|
"@types/dockerode": "catalog:",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,13 +26,10 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/form": "catalog:",
|
"@mantine/form": "catalog:",
|
||||||
"react": "catalog:",
|
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,19 +30,11 @@
|
|||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/hooks": "catalog:",
|
|
||||||
"@tabler/icons-react": "catalog:",
|
|
||||||
"@trpc/server": "catalog:",
|
|
||||||
"next": "catalog:",
|
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/bcrypt": "catalog:",
|
"@types/bcrypt": "catalog:",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,16 +38,10 @@
|
|||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@immich/sdk": "catalog:",
|
"@immich/sdk": "catalog:",
|
||||||
"@jellyfin/sdk": "catalog:",
|
"@jellyfin/sdk": "catalog:",
|
||||||
"@mantine/core": "catalog:",
|
|
||||||
"axios": "catalog:",
|
|
||||||
"dayjs": "catalog:",
|
|
||||||
"http-cookie-agent": "catalog:",
|
|
||||||
"ical.js": "catalog:",
|
"ical.js": "catalog:",
|
||||||
"maria2": "catalog:",
|
"maria2": "catalog:",
|
||||||
"node-fetch": "catalog:",
|
|
||||||
"octokit": "catalog:",
|
"octokit": "catalog:",
|
||||||
"proxmox-api": "catalog:",
|
"proxmox-api": "catalog:",
|
||||||
"superjson": "catalog:",
|
|
||||||
"timezones-ical-library": "catalog:",
|
"timezones-ical-library": "catalog:",
|
||||||
"tsdav": "catalog:",
|
"tsdav": "catalog:",
|
||||||
"undici": "catalog:",
|
"undici": "catalog:",
|
||||||
@@ -57,8 +51,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/node-fetch": "catalog:",
|
|
||||||
"@types/node-unifi": "catalog:",
|
"@types/node-unifi": "catalog:",
|
||||||
"@types/ws": "catalog:",
|
"@types/ws": "catalog:",
|
||||||
"@types/xml2js": "catalog:",
|
"@types/xml2js": "catalog:",
|
||||||
|
|||||||
@@ -134,7 +134,10 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
|
|||||||
*/
|
*/
|
||||||
private async postAsync(path: `/api/${string}`, body: Record<string, string>) {
|
private async postAsync(path: `/api/${string}`, body: Record<string, string>) {
|
||||||
return await fetchWithTrustedCertificatesAsync(this.url(path), {
|
return await fetchWithTrustedCertificatesAsync(this.url(path), {
|
||||||
headers: this.getAuthHeaders(),
|
headers: {
|
||||||
|
...this.getAuthHeaders(),
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -148,6 +148,14 @@ const happyResponder = (method: string) => {
|
|||||||
];
|
];
|
||||||
case "pool.query":
|
case "pool.query":
|
||||||
return [{ name: "tank", status: "ONLINE", healthy: true, free: 500, size: 1000, allocated: 500 }];
|
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":
|
case "interface.query":
|
||||||
return [{ id: "eth0", name: "eth0" }];
|
return [{ id: "eth0", name: "eth0" }];
|
||||||
case "reporting.netdata_get_data":
|
case "reporting.netdata_get_data":
|
||||||
@@ -191,6 +199,18 @@ describe("TrueNasIntegration", () => {
|
|||||||
// JSON-RPC does not use the legacy "connect" session handshake.
|
// JSON-RPC does not use the legacy "connect" session handshake.
|
||||||
expect(ws.sentPayloads.some((payload) => payload.msg === "connect")).toBe(false);
|
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({
|
expect(result).toMatchObject({
|
||||||
version: "TrueNAS-SCALE-24.10",
|
version: "TrueNAS-SCALE-24.10",
|
||||||
cpuModelName: "Intel Xeon",
|
cpuModelName: "Intel Xeon",
|
||||||
@@ -204,12 +224,25 @@ describe("TrueNasIntegration", () => {
|
|||||||
loadAverage: null,
|
loadAverage: null,
|
||||||
gpu: [],
|
gpu: [],
|
||||||
network: { up: 20_000, down: 10_000 },
|
network: { up: 20_000, down: 10_000 },
|
||||||
// `available` is the free space left on the pool (free = 500), not its total size.
|
// `available` and `used` come from the root dataset, not the physical pool.
|
||||||
fileSystem: [{ deviceName: "tank", available: "500", used: "500", percentage: 50 }],
|
fileSystem: [{ deviceName: "tank", available: "300", used: "200", percentage: 40 }],
|
||||||
smart: [{ deviceName: "tank", healthy: true, overallStatus: "ONLINE", temperature: null }],
|
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 () => {
|
test("authenticates with an API key when one is configured", async () => {
|
||||||
const integration = createIntegration([{ kind: "apiKey", value: "api-key-123" }]);
|
const integration = createIntegration([{ kind: "apiKey", value: "api-key-123" }]);
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,10 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
|||||||
memUsedInBytes,
|
memUsedInBytes,
|
||||||
fileSystem: datasets.map((dataset) => ({
|
fileSystem: datasets.map((dataset) => ({
|
||||||
deviceName: dataset.name,
|
deviceName: dataset.name,
|
||||||
available: `${dataset.free}`, // free space left on the pool
|
available: `${dataset.available}`,
|
||||||
used: `${dataset.allocated}`,
|
used: `${dataset.used}`,
|
||||||
percentage: (dataset.allocated / dataset.size) * 100,
|
percentage:
|
||||||
|
dataset.used + dataset.available === 0 ? 0 : (dataset.used / (dataset.used + dataset.available)) * 100,
|
||||||
})),
|
})),
|
||||||
availablePkgUpdates: 0,
|
availablePkgUpdates: 0,
|
||||||
network: {
|
network: {
|
||||||
@@ -117,12 +118,40 @@ export class TrueNasIntegration extends Integration implements ISystemHealthMoni
|
|||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.filter((pool) => pool !== 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", {
|
logger.debug("Retrieved pools", {
|
||||||
url: this.integration.url,
|
url: this.integration.url,
|
||||||
totalCount: result.length,
|
totalCount: result.length,
|
||||||
activeCount: activePools.length,
|
activeCount: poolsWithUsableSpace.length,
|
||||||
});
|
});
|
||||||
return activePools;
|
return poolsWithUsableSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,6 +292,14 @@ 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(
|
const reportingNetDataSchema = z.array(
|
||||||
z.object({
|
z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// @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,11 +33,8 @@
|
|||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/dates": "catalog:",
|
|
||||||
"@mantine/hooks": "catalog:",
|
|
||||||
"@tabler/icons-react": "catalog:",
|
"@tabler/icons-react": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"mantine-react-table": "catalog:",
|
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:",
|
||||||
@@ -45,9 +42,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,9 +49,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/adm-zip": "catalog:",
|
"@types/adm-zip": "catalog:",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,12 +22,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0"
|
||||||
"undici": "catalog:"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,19 +29,14 @@
|
|||||||
"@homarr/docker": "workspace:^0.1.0",
|
"@homarr/docker": "workspace:^0.1.0",
|
||||||
"@homarr/integrations": "workspace:^0.1.0",
|
"@homarr/integrations": "workspace:^0.1.0",
|
||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "catalog:",
|
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"dockerode": "catalog:",
|
|
||||||
"octokit": "catalog:",
|
"octokit": "catalog:",
|
||||||
"semver-parser": "catalog:",
|
|
||||||
"superjson": "catalog:",
|
"superjson": "catalog:",
|
||||||
"undici": "catalog:",
|
"undici": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/dockerode": "catalog:",
|
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,4 +18,6 @@ export const smartHomeEntityStateRequestHandler = createIntegrationRequestHandle
|
|||||||
|
|
||||||
return result.data.state;
|
return result.data.state;
|
||||||
},
|
},
|
||||||
|
// The widget already polls every 30 seconds and refetches after toggles.
|
||||||
|
cacheTtlMs: 0,
|
||||||
});
|
});
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,9 +31,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,12 +36,9 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/dates": "catalog:",
|
|
||||||
"@mantine/hooks": "catalog:",
|
"@mantine/hooks": "catalog:",
|
||||||
"@mantine/spotlight": "catalog:",
|
"@mantine/spotlight": "catalog:",
|
||||||
"@tabler/icons-react": "catalog:",
|
"@tabler/icons-react": "catalog:",
|
||||||
"@tanstack/react-query": "catalog:",
|
|
||||||
"dayjs": "catalog:",
|
|
||||||
"jotai": "catalog:",
|
"jotai": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
@@ -50,9 +47,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,21 +24,16 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gfazioli/mantine-onboarding-tour": "catalog:",
|
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/settings": "workspace:^0.1.0",
|
"@homarr/settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/charts": "catalog:",
|
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/dates": "catalog:",
|
"@mantine/dates": "catalog:",
|
||||||
"@mantine/hooks": "catalog:",
|
"@mantine/hooks": "catalog:",
|
||||||
"@tabler/icons-react": "catalog:",
|
"@tabler/icons-react": "catalog:",
|
||||||
"@tanstack/react-query": "catalog:",
|
|
||||||
"clsx": "catalog:",
|
|
||||||
"crypto-js": "catalog:",
|
"crypto-js": "catalog:",
|
||||||
"csstype": "catalog:",
|
|
||||||
"mantine-react-table": "catalog:",
|
"mantine-react-table": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
@@ -49,9 +44,6 @@
|
|||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/crypto-js": "catalog:",
|
"@types/crypto-js": "catalog:",
|
||||||
"@types/css-modules": "catalog:",
|
"@types/css-modules": "catalog:",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,13 +23,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@mantine/dates": "catalog:",
|
|
||||||
"zod": "catalog:",
|
"zod": "catalog:",
|
||||||
"zod-form-data": "catalog:"
|
"zod-form-data": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,12 +59,9 @@
|
|||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/charts": "catalog:",
|
"@mantine/charts": "catalog:",
|
||||||
"@mantine/core": "catalog:",
|
"@mantine/core": "catalog:",
|
||||||
"@mantine/dates": "catalog:",
|
|
||||||
"@mantine/form": "catalog:",
|
"@mantine/form": "catalog:",
|
||||||
"@mantine/hooks": "catalog:",
|
"@mantine/hooks": "catalog:",
|
||||||
"@mantine/tiptap": "catalog:",
|
|
||||||
"@tabler/icons-react": "catalog:",
|
"@tabler/icons-react": "catalog:",
|
||||||
"@tanstack/react-query": "catalog:",
|
|
||||||
"@tiptap/extension-color": "catalog:",
|
"@tiptap/extension-color": "catalog:",
|
||||||
"@tiptap/extension-highlight": "catalog:",
|
"@tiptap/extension-highlight": "catalog:",
|
||||||
"@tiptap/extension-image": "catalog:",
|
"@tiptap/extension-image": "catalog:",
|
||||||
@@ -81,8 +78,6 @@
|
|||||||
"@tiptap/extension-underline": "catalog:",
|
"@tiptap/extension-underline": "catalog:",
|
||||||
"@tiptap/react": "catalog:",
|
"@tiptap/react": "catalog:",
|
||||||
"@tiptap/starter-kit": "catalog:",
|
"@tiptap/starter-kit": "catalog:",
|
||||||
"@trpc/client": "catalog:",
|
|
||||||
"@trpc/server": "catalog:",
|
|
||||||
"clsx": "catalog:",
|
"clsx": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"jsonpath-plus": "catalog:",
|
"jsonpath-plus": "catalog:",
|
||||||
@@ -90,8 +85,6 @@
|
|||||||
"mantine-datatable": "catalog:",
|
"mantine-datatable": "catalog:",
|
||||||
"mantine-react-table": "catalog:",
|
"mantine-react-table": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"next-intl": "catalog:",
|
|
||||||
"prosemirror-model": "catalog:",
|
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:",
|
||||||
"react-jsx-parser": "catalog:",
|
"react-jsx-parser": "catalog:",
|
||||||
@@ -103,9 +96,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "catalog:",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"@types/video.js": "catalog:",
|
"@types/video.js": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+48
-458
File diff suppressed because it is too large.
Load diff
+1
-12
@@ -35,7 +35,6 @@ catalog:
|
|||||||
"@mantine/spotlight": ^9.4.1
|
"@mantine/spotlight": ^9.4.1
|
||||||
"@mantine/tiptap": ^9.4.1
|
"@mantine/tiptap": ^9.4.1
|
||||||
"@octokit/auth-app": ^8.2.0
|
"@octokit/auth-app": ^8.2.0
|
||||||
"@octokit/plugin-rest-endpoint-methods": ^17.0.0
|
|
||||||
"@paralleldrive/cuid2": ^3.3.0
|
"@paralleldrive/cuid2": ^3.3.0
|
||||||
"@playwright/test": ^1.61.1
|
"@playwright/test": ^1.61.1
|
||||||
"@semantic-release/changelog": ^7.0.0
|
"@semantic-release/changelog": ^7.0.0
|
||||||
@@ -46,7 +45,6 @@ catalog:
|
|||||||
"@semantic-release/release-notes-generator": ^14.1.1
|
"@semantic-release/release-notes-generator": ^14.1.1
|
||||||
"@t3-oss/env-nextjs": ^0.13.11
|
"@t3-oss/env-nextjs": ^0.13.11
|
||||||
"@tabler/icons-react": ^3.44.0
|
"@tabler/icons-react": ^3.44.0
|
||||||
"@tanstack/query-core": ^5.101.2
|
|
||||||
"@tanstack/react-devtools": ^0.10.8
|
"@tanstack/react-devtools": ^0.10.8
|
||||||
"@tanstack/react-query": ^5.101.2
|
"@tanstack/react-query": ^5.101.2
|
||||||
"@tanstack/react-query-devtools": ^5.101.2
|
"@tanstack/react-query-devtools": ^5.101.2
|
||||||
@@ -85,22 +83,15 @@ catalog:
|
|||||||
"@types/css-modules": ^1.0.5
|
"@types/css-modules": ^1.0.5
|
||||||
"@types/dockerode": ^4.0.1
|
"@types/dockerode": ^4.0.1
|
||||||
"@types/node": ^24.13.3
|
"@types/node": ^24.13.3
|
||||||
"@types/node-fetch": ^2.6.13
|
|
||||||
"@types/node-unifi": ^2.5.1
|
"@types/node-unifi": ^2.5.1
|
||||||
"@types/pg": ^8.20.0
|
"@types/pg": ^8.20.0
|
||||||
"@types/prismjs": ^1.26.6
|
"@types/prismjs": ^1.26.6
|
||||||
"@types/react": 19.2.17
|
"@types/react": 19.2.17
|
||||||
"@types/react-dom": 19.2.3
|
"@types/react-dom": 19.2.3
|
||||||
"@scalar/api-reference-react": ^0.9.54
|
"@scalar/api-reference-react": ^0.9.54
|
||||||
"@scalar/types": ^0.16.2
|
|
||||||
"@types/video.js": ^7.3.58
|
"@types/video.js": ^7.3.58
|
||||||
"@types/ws": ^8.18.1
|
"@types/ws": ^8.18.1
|
||||||
"@types/xml2js": ^0.4.14
|
"@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-js: ^1.399.1
|
||||||
posthog-node: ^5.40.0
|
posthog-node: ^5.40.0
|
||||||
"@vitejs/plugin-react": ^6.0.3
|
"@vitejs/plugin-react": ^6.0.3
|
||||||
@@ -111,7 +102,6 @@ catalog:
|
|||||||
"@xterm/xterm": ^6.0.0
|
"@xterm/xterm": ^6.0.0
|
||||||
adm-zip: 0.5.18
|
adm-zip: 0.5.18
|
||||||
jszip: ^3.10.1
|
jszip: ^3.10.1
|
||||||
prosemirror-model: ^1.25.10
|
|
||||||
sql.js: ^1.14.1
|
sql.js: ^1.14.1
|
||||||
babel-plugin-react-compiler: ^1.0.0
|
babel-plugin-react-compiler: ^1.0.0
|
||||||
bcrypt: ^6.0.0
|
bcrypt: ^6.0.0
|
||||||
@@ -164,7 +154,6 @@ catalog:
|
|||||||
oxlint: ^1.73.0
|
oxlint: ^1.73.0
|
||||||
pg: ^8.22.0
|
pg: ^8.22.0
|
||||||
postcss-preset-mantine: ^1.18.0
|
postcss-preset-mantine: ^1.18.0
|
||||||
postcss-simple-vars: ^7.0.1
|
|
||||||
prismjs: ^1.30.0
|
prismjs: ^1.30.0
|
||||||
proxmox-api: 1.1.1
|
proxmox-api: 1.1.1
|
||||||
react: 19.2.7
|
react: 19.2.7
|
||||||
@@ -195,7 +184,6 @@ catalog:
|
|||||||
vite-tsconfig-paths: ^6.1.1
|
vite-tsconfig-paths: ^6.1.1
|
||||||
vitest: ^4.1.10
|
vitest: ^4.1.10
|
||||||
winston: 3.19.0
|
winston: 3.19.0
|
||||||
winston-transport: ^4.9.0
|
|
||||||
ws: ^8.21.0
|
ws: ^8.21.0
|
||||||
xml2js: ^0.6.2
|
xml2js: ^0.6.2
|
||||||
yaml: 2.9.0
|
yaml: 2.9.0
|
||||||
@@ -204,6 +192,7 @@ catalog:
|
|||||||
zod-validation-error: ^5.0.0
|
zod-validation-error: ^5.0.0
|
||||||
|
|
||||||
catalogMode: strict
|
catalogMode: strict
|
||||||
|
nodeLinker: hoisted
|
||||||
strictPeerDependencies: false
|
strictPeerDependencies: false
|
||||||
engineStrict: true
|
engineStrict: true
|
||||||
verifyDepsBeforeRun: warn
|
verifyDepsBeforeRun: warn
|
||||||
|
|||||||
Reference in new issue
Block a user