Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76ea2d3df6 | ||
|
|
6c122c70e4 | ||
|
|
aca0f56dfe | ||
|
|
07ad643f4d | ||
|
|
f5b4625c8e | ||
|
|
ae8ad9785e | ||
|
|
b72b70d2a3 | ||
|
|
8d22df5123 | ||
|
|
2f7bdbfe1c | ||
|
|
1136b3a99b | ||
|
|
bf26f17fcd | ||
|
|
83b93e17da | ||
|
|
597b5fbc8d | ||
|
|
5412577519 |
No files matched your search
@@ -163,65 +163,3 @@ jobs:
|
||||
run: cp .env.example .env
|
||||
- name: Build
|
||||
run: pnpm turbo build --affected
|
||||
|
||||
memory-regression:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
context: .
|
||||
push: false
|
||||
load: true
|
||||
tags: homarr-memory-test
|
||||
network: host
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
- name: Start container and measure memory
|
||||
run: |
|
||||
docker run -d --name homarr-mem-test -p 17575:7575 homarr-memory-test
|
||||
# Wait for container to be healthy (up to 60s)
|
||||
healthy=false
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:17575/api/health 2>/dev/null; then
|
||||
echo "Container healthy after $((i * 2))s"
|
||||
healthy=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$healthy" != "true" ]; then
|
||||
echo "ERROR: Container never became healthy"
|
||||
docker logs homarr-mem-test --tail 50
|
||||
exit 1
|
||||
fi
|
||||
# Warm up: hit the dashboard page a few times
|
||||
for i in $(seq 1 5); do
|
||||
curl -sf http://localhost:17575/ > /dev/null 2>&1 || true
|
||||
sleep 2
|
||||
done
|
||||
# Measure RSS after warmup
|
||||
MEM_KB=$(docker stats --no-stream --format '{{.MemUsage}}' homarr-mem-test | sed 's| /.*||')
|
||||
echo "Raw memory: $MEM_KB"
|
||||
# Convert to numeric MB (handles GiB and MiB suffixes)
|
||||
MEM_NUM=$(echo "$MEM_KB" | awk '{
|
||||
if ($0 ~ /GiB/) { gsub(/GiB/,""); print $1 * 1024 }
|
||||
else if ($0 ~ /MiB/) { gsub(/MiB/,""); print $1 }
|
||||
else if ($0 ~ /KiB/) { gsub(/KiB/,""); print $1 / 1024 }
|
||||
else { print $1 / 1048576 }
|
||||
}')
|
||||
echo "Memory usage: ${MEM_NUM} MB"
|
||||
MAX_MB=700
|
||||
if awk "BEGIN {exit !($MEM_NUM > $MAX_MB)}"; then
|
||||
echo "ERROR: Memory usage ${MEM_NUM} MB exceeds ${MAX_MB} MB threshold"
|
||||
docker logs homarr-mem-test --tail 50
|
||||
exit 1
|
||||
fi
|
||||
echo "Memory check passed: ${MEM_NUM} MB <= ${MAX_MB} MB"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- uses: actions/setup-node@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
node-version: 24.17.0
|
||||
package-manager-cache: false
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
[tools]
|
||||
node = "24.18.0"
|
||||
node = "24.17.0"
|
||||
@@ -1,10 +1,3 @@
|
||||
## [1.68.1](https://github.com/homarr-labs/homarr/compare/v1.68.0...v1.68.1) (2026-06-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make source maps opt-in to restore v1.67.0 memory baseline ([57ef0af](https://github.com/homarr-labs/homarr/commit/57ef0af4a252ea39727caeba7e13587dabc6254e)), closes [#6123](https://github.com/homarr-labs/homarr/issues/6123)
|
||||
* revert production source maps to restore v1.67.0 memory baseline ([60a69e3](https://github.com/homarr-labs/homarr/commit/60a69e31efc7168b8f4309daf75954d7730d883b)), closes [#6123](https://github.com/homarr-labs/homarr/issues/6123)
|
||||
|
||||
## [1.68.0](https://github.com/homarr-labs/homarr/compare/v1.67.0...v1.68.0) (2026-06-26)
|
||||
|
||||
### Features
|
||||
|
||||
+15
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:24.18.0-alpine AS base
|
||||
FROM node:24.17.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
@@ -20,6 +20,17 @@ ARG DISABLE_REDIS_LOGS='true'
|
||||
|
||||
RUN corepack enable pnpm && pnpm build
|
||||
|
||||
# The standalone output tracing omits server chunk source maps (.next/server/**/*.map), which is
|
||||
# where production Node stack frames point. Copy the .map files into the standalone tree (preserving
|
||||
# directory structure) so the existing standalone COPY ships them, keeping traces readable.
|
||||
# See https://github.com/homarr-labs/homarr/issues/5891
|
||||
RUN cd apps/nextjs/.next/server && \
|
||||
find . -name '*.map' | while IFS= read -r map; do \
|
||||
dest="../standalone/apps/nextjs/.next/server/$map"; \
|
||||
mkdir -p "$(dirname "$dest")"; \
|
||||
cp "$map" "$dest"; \
|
||||
done
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
@@ -64,6 +75,9 @@ ENV DB_DRIVER='better-sqlite3'
|
||||
ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
# Tell Node to symbolicate stack traces using the shipped .map files so production logs
|
||||
# show original source locations instead of minified frames, see https://github.com/homarr-labs/homarr/issues/5891
|
||||
ENV NODE_OPTIONS='--enable-source-maps'
|
||||
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
@@ -31,6 +31,8 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
// Ship browser source maps so production client stack traces are readable, see https://github.com/homarr-labs/homarr/issues/5891
|
||||
productionBrowserSourceMaps: true,
|
||||
// react compiler breaks mantine-react-table, so disabled for now
|
||||
//reactCompiler: true,
|
||||
/** We already do typechecking as separate tasks in CI */
|
||||
@@ -41,6 +43,8 @@ const nextConfig: NextConfig = {
|
||||
*/
|
||||
serverExternalPackages: ["dockerode", "isomorphic-dompurify", "jsdom", "better-sqlite3"],
|
||||
experimental: {
|
||||
// Ship server source maps so production Node stack traces in logs reference original source, see https://github.com/homarr-labs/homarr/issues/5891
|
||||
serverSourceMaps: true,
|
||||
optimizePackageImports: ["@mantine/core", "@mantine/hooks", "@tabler/icons-react"],
|
||||
turbopackFileSystemCacheForDev: true,
|
||||
preloadEntriesOnStart: false,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24.18.0-trixie AS base
|
||||
FROM node:24.17.0-trixie AS base
|
||||
WORKDIR /app
|
||||
COPY package.json .
|
||||
COPY pnpm-lock.yaml .
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"better-sqlite3": "^12.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.18.0",
|
||||
"node": ">=24.17.0",
|
||||
"pnpm": ">=11.9.0"
|
||||
},
|
||||
"packageManager": "pnpm@11.9.0"
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.68.1",
|
||||
"version": "1.68.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
@@ -61,7 +61,7 @@
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.18.0",
|
||||
"node": ">=24.17.0",
|
||||
"pnpm": ">=11.9.0"
|
||||
},
|
||||
"packageManager": "pnpm@11.9.0",
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const dawarichRouter = createTRPCRouter({
|
||||
getStatistics: publicProcedure.concat(createOneIntegrationMiddleware("query", "dawarich")).query(async ({ ctx }) => {
|
||||
const { dawarichStatisticsRequestHandler } = await import("@homarr/request-handler/dawarich");
|
||||
const innerHandler = dawarichStatisticsRequestHandler.handler(ctx.integration, {});
|
||||
const data = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
return data.data;
|
||||
}),
|
||||
|
||||
getPlaces: publicProcedure
|
||||
.concat(createOneIntegrationMiddleware("query", "dawarich"))
|
||||
.input(
|
||||
z.object({
|
||||
limit: z.number().min(1).max(50).default(10),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { dawarichPlacesRequestHandler } = await import("@homarr/request-handler/dawarich");
|
||||
const innerHandler = dawarichPlacesRequestHandler.handler(ctx.integration, {});
|
||||
const { data } = await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false });
|
||||
return data.slice(0, input.limit);
|
||||
}),
|
||||
});
|
||||
@@ -39,5 +39,4 @@ export const widgetRouter = createTRPCRouter({
|
||||
vpn: lazy(() => import("./vpn").then((mod) => mod.vpnRouter)),
|
||||
ups: lazy(() => import("./ups").then((mod) => mod.upsRouter)),
|
||||
customApi: lazy(() => import("./custom-api").then((mod) => mod.customApiRouter)),
|
||||
dawarich: lazy(() => import("./dawarich").then((mod) => mod.dawarichRouter)),
|
||||
});
|
||||
@@ -343,7 +343,6 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/integrations/gluetun"
|
||||
| "/docs/integrations/peanut"
|
||||
| "/docs/integrations/beszel"
|
||||
| "/docs/integrations/dawarich"
|
||||
| "/docs/widgets/clock"
|
||||
| "/docs/widgets/weather"
|
||||
| "/docs/widgets/app"
|
||||
|
||||
@@ -59,6 +59,5 @@ export const integrationDocSlugs: Record<IntegrationKind, string | null> = {
|
||||
peaNut: "peanut",
|
||||
beszel: "beszel",
|
||||
archiveTeamWarrior: null,
|
||||
dawarich: "dawarich",
|
||||
mock: null,
|
||||
};
|
||||
@@ -51,6 +51,4 @@ export const widgetDocSlugs: Record<WidgetKind, string | null> = {
|
||||
beszelAlerts: "beszel-alerts",
|
||||
beszelSystemStats: "beszel-system-stats",
|
||||
customApi: "custom-api",
|
||||
dawarichStatistics: null,
|
||||
dawarichPlaces: null,
|
||||
};
|
||||
@@ -504,13 +504,6 @@ export const integrationDefs = {
|
||||
defaultUrl: "http://localhost:8001",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/archiveteam-warrior"),
|
||||
},
|
||||
dawarich: {
|
||||
name: "Dawarch",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/dawarich.png",
|
||||
category: ["geolocationTracking"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/dawarich"),
|
||||
},
|
||||
// This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page)
|
||||
mock: {
|
||||
name: "Mock",
|
||||
@@ -624,7 +617,6 @@ export const integrationCategories = [
|
||||
"documents",
|
||||
"mediaLibrary",
|
||||
"uptimeMonitoring",
|
||||
"geolocationTracking",
|
||||
] as const;
|
||||
|
||||
export type IntegrationCategory = (typeof integrationCategories)[number];
|
||||
@@ -40,8 +40,6 @@ export const widgetIntegrationSupport: Partial<Record<WidgetKind, readonly Integ
|
||||
audioStats: ["navidrome", "audiobookshelf"],
|
||||
vpn: getIntegrationKindsByCategory("vpn"),
|
||||
anchorNote: ["anchor"],
|
||||
dawarichStatistics: ["dawarich"],
|
||||
dawarichPlaces: ["dawarich"],
|
||||
};
|
||||
|
||||
export const getWidgetKindsForIntegration = (integrationKind: IntegrationKind): WidgetKind[] => {
|
||||
|
||||
@@ -49,8 +49,6 @@ export const widgetKinds = [
|
||||
"beszelAlerts",
|
||||
"beszelSystemStats",
|
||||
"customApi",
|
||||
"dawarichStatistics",
|
||||
"dawarichPlaces",
|
||||
] as const;
|
||||
export type WidgetKind = (typeof widgetKinds)[number];
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ import { UptimeKumaIntegration } from "../uptime-kuma/uptime-kuma-integration";
|
||||
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
|
||||
import { UnraidIntegration } from "../unraid/unraid-integration";
|
||||
import type { Integration, IntegrationInput } from "./integration";
|
||||
import { DawarichIntegration } from "../dawarich/dawarich-integration";
|
||||
|
||||
export const createIntegrationAsync = async <TKind extends keyof typeof integrationCreators>(
|
||||
integration: IntegrationInput & { kind: TKind },
|
||||
@@ -144,7 +143,6 @@ export const integrationCreators = {
|
||||
uptimeKuma: UptimeKumaIntegration,
|
||||
peaNut: PeaNutIntegration,
|
||||
beszel: BeszelIntegration,
|
||||
dawarich: DawarichIntegration,
|
||||
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
|
||||
|
||||
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import type { IntegrationInput, IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { dawarichPlacesSchema, dawarichStatisticsSchema } from "./dawarich-schemas";
|
||||
import type { z } from "zod/v4";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
|
||||
export class DawarichIntegration extends Integration {
|
||||
constructor(integration: IntegrationInput) {
|
||||
super(integration);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url(`/api/v1/users/me`), {
|
||||
headers: { Authorization: `Bearer ${this.getSecretValue("apiKey")}` },
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
async getStatisticsAsync(): Promise<{
|
||||
totalDistanceKm: number;
|
||||
totalPointsTracked: number;
|
||||
totalReverseGeocodedPoints: number;
|
||||
totalCountriesVisited: number;
|
||||
totalCitiesVisited: number;
|
||||
}> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/stats"), {
|
||||
headers: { Authorization: `Bearer ${this.getSecretValue("apiKey")}` },
|
||||
});
|
||||
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
|
||||
return dawarichStatisticsSchema.parseAsync(await response.json());
|
||||
}
|
||||
|
||||
async getPlacesAsync(): Promise<z.infer<typeof dawarichPlacesSchema>> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/places"), {
|
||||
headers: { Authorization: `Bearer ${this.getSecretValue("apiKey")}` },
|
||||
});
|
||||
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
|
||||
return dawarichPlacesSchema.parseAsync(await response.json());
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const dawarichStatisticsSchema = z.object({
|
||||
totalDistanceKm: z.number(),
|
||||
totalPointsTracked: z.number(),
|
||||
totalReverseGeocodedPoints: z.number(),
|
||||
totalCountriesVisited: z.number(),
|
||||
totalCitiesVisited: z.number(),
|
||||
});
|
||||
|
||||
export const dawarichPlaceTagSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
icon: z.string(),
|
||||
color: z.string(),
|
||||
});
|
||||
|
||||
export const dawarichPlaceSchema = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
latitude: z.number(),
|
||||
longitude: z.number(),
|
||||
source: z.string().optional(),
|
||||
icon: z.string().nullable(),
|
||||
color: z.string().nullable(),
|
||||
visits_count: z.number(),
|
||||
created_at: z.string(),
|
||||
tags: z.array(dawarichPlaceTagSchema),
|
||||
});
|
||||
|
||||
export const dawarichPlacesSchema = z.array(dawarichPlaceSchema);
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { z } from "zod/v4";
|
||||
import type { dawarichPlacesSchema, dawarichStatisticsSchema } from "./dawarich-schemas";
|
||||
|
||||
export type DawarichStatistics = z.infer<typeof dawarichStatisticsSchema>;
|
||||
export type DawarichPlace = z.infer<typeof dawarichPlacesSchema>[number];
|
||||
@@ -41,7 +41,6 @@ export { NavidromeIntegration } from "./navidrome/navidrome-integration";
|
||||
export { UptimeKumaIntegration } from "./uptime-kuma/uptime-kuma-integration";
|
||||
export { UmamiIntegration } from "./umami/umami-integration";
|
||||
export { PeaNutIntegration } from "./peanut/peanut-integration";
|
||||
export { DawarichIntegration } from "./dawarich/dawarich-integration";
|
||||
|
||||
// Types
|
||||
export type { IntegrationInput } from "./base/integration";
|
||||
@@ -87,7 +86,6 @@ export type { AudiobookshelfDashboardData } from "./audiobookshelf/audiobookshel
|
||||
export type { NavidromeDashboardData } from "./navidrome/navidrome-types";
|
||||
export type { UptimeKumaDashboardData } from "./uptime-kuma/uptime-kuma-types";
|
||||
export type { UmamiVisitorStats } from "./umami/umami-types";
|
||||
export type { DawarichStatistics, DawarichPlace } from "./dawarich/dawarich-types";
|
||||
|
||||
// Schemas
|
||||
export { anchorNotesListInputSchema } from "./anchor/anchor-types";
|
||||
|
||||
@@ -25,4 +25,3 @@ export * from "./paperless-ngx/paperless-ngx-types";
|
||||
export * from "./umami/umami-types";
|
||||
export * from "./gluetun/gluetun-types";
|
||||
export * from "./beszel/beszel-types";
|
||||
export * from "./dawarich/dawarich-types";
|
||||
@@ -1,32 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
import type { DawarichPlace, DawarichStatistics } from "@homarr/integrations";
|
||||
|
||||
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
|
||||
|
||||
export const dawarichStatisticsRequestHandler = createCachedIntegrationRequestHandler<
|
||||
DawarichStatistics,
|
||||
"dawarich",
|
||||
Record<string, never>
|
||||
>({
|
||||
async requestAsync(integration) {
|
||||
const instance = await createIntegrationAsync(integration);
|
||||
return await instance.getStatisticsAsync();
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
queryKey: "dawarichStatistics",
|
||||
});
|
||||
|
||||
export const dawarichPlacesRequestHandler = createCachedIntegrationRequestHandler<
|
||||
DawarichPlace[],
|
||||
"dawarich",
|
||||
Record<string, never>
|
||||
>({
|
||||
async requestAsync(integration) {
|
||||
const instance = await createIntegrationAsync(integration);
|
||||
return await instance.getPlacesAsync();
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
queryKey: "dawarichPlaces",
|
||||
});
|
||||
@@ -3545,42 +3545,6 @@
|
||||
"executeSuccess": "Action executed successfully",
|
||||
"executeFailed": "Action failed. Check the widget configuration."
|
||||
},
|
||||
"dawarichStatistics": {
|
||||
"name": "Dawarich Statistics",
|
||||
"description": "Display travel statistics from Dawarich",
|
||||
"option": {
|
||||
"showTotalDistance": {
|
||||
"label": "Show total distance"
|
||||
},
|
||||
"showTotalPoints": {
|
||||
"label": "Show total points tracked"
|
||||
},
|
||||
"showReverseGeocoded": {
|
||||
"label": "Show reverse geocoded points"
|
||||
},
|
||||
"showCountries": {
|
||||
"label": "Show countries visited"
|
||||
},
|
||||
"showCities": {
|
||||
"label": "Show cities visited"
|
||||
}
|
||||
},
|
||||
"totalDistance": "Total distance",
|
||||
"totalPoints": "Points tracked",
|
||||
"reverseGeocoded": "Reverse geocoded",
|
||||
"countries": "Countries",
|
||||
"cities": "Cities"
|
||||
},
|
||||
"dawarichPlaces": {
|
||||
"name": "Dawarich Places",
|
||||
"description": "Display recently visited places from Dawarich",
|
||||
"option": {
|
||||
"maxPlaces": {
|
||||
"label": "Max places to show"
|
||||
}
|
||||
},
|
||||
"visits": "visits"
|
||||
},
|
||||
"beszelSystemTable": {
|
||||
"name": "Beszel Systems (Table)",
|
||||
"description": "Table view of all Beszel-monitored systems with sortable columns and status indicators",
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
.placeItem {
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--mantine-radius-sm);
|
||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Anchor, Group, Stack, Text } from "@mantine/core";
|
||||
import { IconMapPin } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetEmptyState } from "../common/empty-state";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import classes from "./component.module.css";
|
||||
|
||||
export default function DawarichPlacesWidget({ integrationIds, options }: WidgetComponentProps<"dawarichPlaces">) {
|
||||
const t = useI18n();
|
||||
const { data: places } = clientApi.widget.dawarich.getPlaces.useQuery(
|
||||
{
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
limit: options.maxPlaces,
|
||||
},
|
||||
{ staleTime: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
if (!places || places.length === 0) return <WidgetEmptyState />;
|
||||
|
||||
return (
|
||||
<Stack gap="xs" h="100%" p="md">
|
||||
{places.map((place) => (
|
||||
<Group key={place.id} gap="sm" align="center" className={classes.placeItem}>
|
||||
<IconMapPin size={16} />
|
||||
<Text size="sm" fw={500} style={{ flex: 1 }}>
|
||||
{place.name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{place.visits_count} {t("widget.dawarichPlaces.visits")}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { IconMapPin } from "@tabler/icons-react";
|
||||
import z from "zod";
|
||||
|
||||
import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("dawarichPlaces", {
|
||||
icon: IconMapPin,
|
||||
supportedIntegrations: ["dawarich"],
|
||||
integrationsRequired: true,
|
||||
createOptions() {
|
||||
return optionsBuilder.from((factory) => ({
|
||||
maxPlaces: factory.number({ defaultValue: 10, validate: z.number().min(1).max(50) }),
|
||||
}));
|
||||
},
|
||||
}).withDynamicImport(() => import("./component"));
|
||||
@@ -1,10 +0,0 @@
|
||||
.statItem {
|
||||
padding: 12px;
|
||||
border-radius: var(--mantine-radius-md);
|
||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Group, Stack, Text } from "@mantine/core";
|
||||
import { IconMap, IconMapPin, IconRoute, IconWorld, IconBuildingSkyscraper } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { WidgetEmptyState } from "../common/empty-state";
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import classes from "./component.module.css";
|
||||
|
||||
export default function DawarichStatisticsWidget({
|
||||
integrationIds,
|
||||
options,
|
||||
}: WidgetComponentProps<"dawarichStatistics">) {
|
||||
const t = useI18n();
|
||||
const { data: stats } = clientApi.widget.dawarich.getStatistics.useQuery(
|
||||
{
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
},
|
||||
{ staleTime: 5 * 60 * 1000 },
|
||||
);
|
||||
|
||||
if (!stats) return <WidgetEmptyState />;
|
||||
|
||||
return (
|
||||
<Stack gap="md" h="100%" p="md">
|
||||
{options.showTotalDistance && (
|
||||
<StatItem
|
||||
icon={<IconRoute size={20} />}
|
||||
label={t("widget.dawarichStatistics.totalDistance")}
|
||||
value={`${stats.totalDistanceKm.toLocaleString(undefined, { maximumFractionDigits: 1 })} km`}
|
||||
/>
|
||||
)}
|
||||
{options.showTotalPoints && (
|
||||
<StatItem
|
||||
icon={<IconMap size={20} />}
|
||||
label={t("widget.dawarichStatistics.totalPoints")}
|
||||
value={stats.totalPointsTracked.toLocaleString()}
|
||||
/>
|
||||
)}
|
||||
{options.showReverseGeocoded && (
|
||||
<StatItem
|
||||
icon={<IconMapPin size={20} />}
|
||||
label={t("widget.dawarichStatistics.reverseGeocoded")}
|
||||
value={stats.totalReverseGeocodedPoints.toLocaleString()}
|
||||
/>
|
||||
)}
|
||||
{options.showCountries && (
|
||||
<StatItem
|
||||
icon={<IconWorld size={20} />}
|
||||
label={t("widget.dawarichStatistics.countries")}
|
||||
value={stats.totalCountriesVisited.toLocaleString()}
|
||||
/>
|
||||
)}
|
||||
{options.showCities && (
|
||||
<StatItem
|
||||
icon={<IconBuildingSkyscraper size={20} />}
|
||||
label={t("widget.dawarichStatistics.cities")}
|
||||
value={stats.totalCitiesVisited.toLocaleString()}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
interface StatItemProps {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
function StatItem({ icon, label, value }: StatItemProps) {
|
||||
return (
|
||||
<Group justify="space-between" align="center" className={classes.statItem}>
|
||||
<Group gap="sm" align="center">
|
||||
{icon}
|
||||
<Text size="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" fw={700} c="var(--mantine-primary-color)">
|
||||
{value}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { IconChartBar } from "@tabler/icons-react";
|
||||
|
||||
import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("dawarichStatistics", {
|
||||
icon: IconChartBar,
|
||||
supportedIntegrations: ["dawarich"],
|
||||
integrationsRequired: true,
|
||||
createOptions() {
|
||||
return optionsBuilder.from((factory) => ({
|
||||
showTotalDistance: factory.switch({ defaultValue: true }),
|
||||
showTotalPoints: factory.switch({ defaultValue: true }),
|
||||
showReverseGeocoded: factory.switch({ defaultValue: true }),
|
||||
showCountries: factory.switch({ defaultValue: true }),
|
||||
showCities: factory.switch({ defaultValue: true }),
|
||||
}));
|
||||
},
|
||||
}).withDynamicImport(() => import("./component"));
|
||||
@@ -60,8 +60,6 @@ import * as beszelSystemStats from "./beszel-system-stats";
|
||||
import * as video from "./video";
|
||||
import * as weather from "./weather";
|
||||
import * as customApi from "./custom-api";
|
||||
import * as dawarichStatistics from "./dawarich-statistics";
|
||||
import * as dawarichPlaces from "./dawarich-places";
|
||||
|
||||
export type {
|
||||
WidgetDefinition,
|
||||
@@ -123,8 +121,6 @@ export const widgetImports = {
|
||||
beszelAlerts,
|
||||
beszelSystemStats,
|
||||
customApi,
|
||||
dawarichStatistics,
|
||||
dawarichPlaces,
|
||||
} satisfies WidgetImportRecord;
|
||||
|
||||
export type WidgetImports = typeof widgetImports;
|
||||
|
||||
Generated
+14
-29
@@ -337,8 +337,8 @@ catalogs:
|
||||
specifier: ^0.2.9
|
||||
version: 0.2.9
|
||||
dockerode:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
dotenv:
|
||||
specifier: ^17.4.2
|
||||
version: 17.4.2
|
||||
@@ -445,8 +445,8 @@ catalogs:
|
||||
specifier: ^1.18.0
|
||||
version: 1.18.0
|
||||
posthog-js:
|
||||
specifier: ^1.393.5
|
||||
version: 1.395.0
|
||||
specifier: ^1.393.4
|
||||
version: 1.393.4
|
||||
posthog-node:
|
||||
specifier: ^4.18.0
|
||||
version: 4.18.0
|
||||
@@ -1012,7 +1012,7 @@ importers:
|
||||
version: 1.18.0(postcss@8.5.15)
|
||||
posthog-js:
|
||||
specifier: 'catalog:'
|
||||
version: 1.395.0
|
||||
version: 1.393.4
|
||||
prismjs:
|
||||
specifier: 'catalog:'
|
||||
version: 1.30.0
|
||||
@@ -1771,7 +1771,7 @@ importers:
|
||||
version: link:../core
|
||||
dockerode:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.1
|
||||
version: 5.0.0
|
||||
devDependencies:
|
||||
'@homarr/tsconfig':
|
||||
specifier: workspace:^0.1.0
|
||||
@@ -6418,8 +6418,8 @@ packages:
|
||||
'@polka/url@1.0.0-next.25':
|
||||
resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==}
|
||||
|
||||
'@posthog/core@1.38.0':
|
||||
resolution: {integrity: sha512-QLrJh0hMVpEJXHNyiJR9YhvIO5tzLDedw4UHdvB+ub4fXHMtHCA8H44qgl11HWR3ajpjxtJ8hs3HyOGVF3Zc1w==}
|
||||
'@posthog/core@1.37.3':
|
||||
resolution: {integrity: sha512-Dvw4CTlRVH4K5ag/B8YIHgG4E27w43MCUsXpn1iKQHIggIMN3Shq9whG4Omm3OvXPF+VikBTmpyMKUjckPxw+g==}
|
||||
|
||||
'@posthog/types@1.391.1':
|
||||
resolution: {integrity: sha512-ASwd7Nf4pViqdYRYaNRyPYRVKWa1CcHUAUWR0XeQJLGdNnsWACBwe0sSieb/cHnKsRXjRwO/23KIY83lm/Ccpw==}
|
||||
@@ -9678,10 +9678,6 @@ packages:
|
||||
resolution: {integrity: sha512-C52mvJ+7lcyhWNfrzVfFsbTrBfy/ezE9FGEYLpu17FUeBcCkxERk9nN7uDl/478ynDiQ4U+5DbQC2vENHkVEtQ==}
|
||||
engines: {node: '>= 14.17'}
|
||||
|
||||
dockerode@5.0.1:
|
||||
resolution: {integrity: sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA==}
|
||||
engines: {node: '>= 14.17'}
|
||||
|
||||
docusaurus-plugin-image-zoom@3.0.1:
|
||||
resolution: {integrity: sha512-mQrqA99VpoMQJNbi02qkWAMVNC4+kwc6zLLMNzraHAJlwn+HrlUmZSEDcTwgn+H4herYNxHKxveE2WsYy73eGw==}
|
||||
peerDependencies:
|
||||
@@ -13120,8 +13116,8 @@ packages:
|
||||
resolution: {integrity: sha512-Ray65LYEJrMMqDtsBUBXunEVP/g4wtATvq/xz9rchUoLy/9mSkkFgUko/8DVtGxgiP3vivpFMgfb9HpCuDrBHg==}
|
||||
engines: {node: '>=10.15.1'}
|
||||
|
||||
posthog-js@1.395.0:
|
||||
resolution: {integrity: sha512-5iTb00CGt2eQUUiBQysQiX89RAbCN6wK2sDNzvs9zv0alaY8mJ0ZySrUD3LQ+XyLhgM5pCpacBuUwChqiYDLDw==}
|
||||
posthog-js@1.393.4:
|
||||
resolution: {integrity: sha512-mOBSVQczEfyLnW3aFRbLXZEuE0Rfbmhqm9UCnuLeMFUUz0uYTkbnyhl5+Uc+BLSGfyiXHLf3nm2BqxYC+G/PXA==}
|
||||
|
||||
posthog-node@4.18.0:
|
||||
resolution: {integrity: sha512-XROs1h+DNatgKh/AlIlCtDxWzwrKdYDb2mOs58n4yN8BkGN9ewqeQwG5ApS4/IzwCb7HPttUkOVulkYatd2PIw==}
|
||||
@@ -17955,7 +17951,7 @@ snapshots:
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
dependencies:
|
||||
esbuild: 0.28.1
|
||||
esbuild: 0.28.0
|
||||
source-map-support: 0.5.21
|
||||
|
||||
'@esbuild-kit/esm-loader@2.6.5':
|
||||
@@ -19901,7 +19897,7 @@ snapshots:
|
||||
|
||||
'@polka/url@1.0.0-next.25': {}
|
||||
|
||||
'@posthog/core@1.38.0':
|
||||
'@posthog/core@1.37.3':
|
||||
dependencies:
|
||||
'@posthog/types': 1.391.1
|
||||
|
||||
@@ -23483,17 +23479,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
dockerode@5.0.1:
|
||||
dependencies:
|
||||
'@balena/dockerignore': 1.0.2
|
||||
'@grpc/grpc-js': 1.12.5
|
||||
'@grpc/proto-loader': 0.7.13
|
||||
docker-modem: 5.0.7
|
||||
protobufjs: 7.4.0
|
||||
tar-fs: 2.1.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
docusaurus-plugin-image-zoom@3.0.1(@docusaurus/theme-classic@3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@docusaurus/theme-classic': 3.10.1(@docusaurus/faster@3.10.1(@docusaurus/types@3.10.1(@swc/core@1.15.3(@swc/helpers@0.5.15))(esbuild@0.28.1)(postcss@8.5.15)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@swc/helpers@0.5.15)(esbuild@0.28.1)(postcss@8.5.15))(@rspack/core@1.7.11(@swc/helpers@0.5.15))(@swc/core@1.15.3(@swc/helpers@0.5.15))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
@@ -27420,9 +27405,9 @@ snapshots:
|
||||
|
||||
posthog-docusaurus@2.0.5: {}
|
||||
|
||||
posthog-js@1.395.0:
|
||||
posthog-js@1.393.4:
|
||||
dependencies:
|
||||
'@posthog/core': 1.38.0
|
||||
'@posthog/core': 1.37.3
|
||||
'@posthog/types': 1.391.1
|
||||
core-js: 3.49.0
|
||||
dompurify: 3.4.11
|
||||
|
||||
+2
-2
@@ -92,7 +92,7 @@ catalog:
|
||||
"@types/video.js": ^7.3.58
|
||||
"@types/ws": ^8.18.1
|
||||
"@types/xml2js": ^0.4.14
|
||||
posthog-js: ^1.393.5
|
||||
posthog-js: ^1.393.4
|
||||
posthog-node: ^4.18.0
|
||||
"@vitejs/plugin-react": ^6.0.3
|
||||
"@vitest/coverage-v8": ^4.1.9
|
||||
@@ -118,7 +118,7 @@ catalog:
|
||||
dayjs: ^1.11.21
|
||||
deepmerge: 4.3.1
|
||||
dns-caching: ^0.2.9
|
||||
dockerode: ^5.0.1
|
||||
dockerode: ^5.0.0
|
||||
dotenv: ^17.4.2
|
||||
dotenv-cli: ^11.0.0
|
||||
drizzle-kit: ^0.31.10
|
||||
|
||||
@@ -7,7 +7,7 @@ runs:
|
||||
- uses: pnpm/action-setup@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
node-version: 24.17.0
|
||||
cache: "pnpm"
|
||||
|
||||
- shell: bash
|
||||
|
||||
Reference in new issue
Block a user