revert: "feat(ping): ignore certificate error and show request durati… (#3680)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@homarr/log": "workspace:^",
|
||||
"@homarr/old-import": "workspace:^0.1.0",
|
||||
"@homarr/old-schema": "workspace:^0.1.0",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/request-handler": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
|
||||
53
packages/api/src/router/test/widgets/app.spec.ts
Normal file
53
packages/api/src/router/test/widgets/app.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import * as ping from "@homarr/ping";
|
||||
|
||||
import { appRouter } from "../../widgets/app";
|
||||
|
||||
// Mock the auth module to return an empty session
|
||||
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
|
||||
vi.mock("@homarr/ping", () => ({ sendPingRequestAsync: async () => await Promise.resolve(null) }));
|
||||
|
||||
describe("ping should call sendPingRequestAsync with url and return result", () => {
|
||||
test("ping with error response should return error and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ error: "error" }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
expect("error" in result).toBe(true);
|
||||
});
|
||||
|
||||
test("ping with success response should return statusCode and url", async () => {
|
||||
// Arrange
|
||||
const spy = vi.spyOn(ping, "sendPingRequestAsync");
|
||||
const url = "http://localhost";
|
||||
const db = createDb();
|
||||
const caller = appRouter.createCaller({
|
||||
db,
|
||||
deviceType: undefined,
|
||||
session: null,
|
||||
});
|
||||
spy.mockImplementation(() => Promise.resolve({ statusCode: 200, durationMs: 123 }));
|
||||
|
||||
// Act
|
||||
const result = await caller.ping({ url });
|
||||
|
||||
// Assert
|
||||
expect(result.url).toBe(url);
|
||||
expect("statusCode" in result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,20 @@
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { z } from "zod";
|
||||
|
||||
import { pingUrlChannel } from "@homarr/redis";
|
||||
import { pingRequestHandler } from "@homarr/request-handler/ping";
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
|
||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
ping: publicProcedure.input(z.object({ url: z.string() })).query(async ({ input }) => {
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
|
||||
return {
|
||||
url: input.url,
|
||||
...pingResult,
|
||||
};
|
||||
}),
|
||||
updatedPing: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
@@ -15,20 +23,16 @@ export const appRouter = createTRPCRouter({
|
||||
)
|
||||
.subscription(async ({ input }) => {
|
||||
await pingUrlChannel.addAsync(input.url);
|
||||
const innerHandler = pingRequestHandler.handler({ url: input.url });
|
||||
|
||||
const pingResult = await sendPingRequestAsync(input.url);
|
||||
|
||||
return observable<{ url: string; statusCode: number; durationMs: number } | { url: string; error: string }>(
|
||||
(emit) => {
|
||||
// Run ping request in background
|
||||
void innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: false }).then(({ data }) => {
|
||||
emit.next({ url: input.url, ...data });
|
||||
});
|
||||
|
||||
const unsubscribe = innerHandler.subscribe((pingResponse) => {
|
||||
emit.next({
|
||||
url: input.url,
|
||||
...pingResponse,
|
||||
});
|
||||
emit.next({ url: input.url, ...pingResult });
|
||||
const unsubscribe = pingChannel.subscribe((message) => {
|
||||
// Only emit if same url
|
||||
if (message.url !== input.url) return;
|
||||
emit.next(message);
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Agent as HttpsAgent } from "node:https";
|
||||
import path from "node:path";
|
||||
import { checkServerIdentity, rootCertificates } from "node:tls";
|
||||
import axios from "axios";
|
||||
import type { RequestInfo, RequestInit, Response } from "undici";
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { env } from "@homarr/common/env";
|
||||
@@ -131,8 +132,8 @@ export const createAxiosCertificateInstanceAsync = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchWithTrustedCertificatesAsync: typeof fetch = async (url, options) => {
|
||||
const agent = await createCertificateAgentAsync();
|
||||
export const fetchWithTrustedCertificatesAsync = async (url: RequestInfo, options?: RequestInit): Promise<Response> => {
|
||||
const agent = await createCertificateAgentAsync(undefined);
|
||||
return fetch(url, {
|
||||
...options,
|
||||
dispatcher: agent,
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@homarr/icons": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/ping": "workspace:^0.1.0",
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/request-handler": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
|
||||
@@ -2,8 +2,8 @@ import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
|
||||
import { db } from "@homarr/db";
|
||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||
import { logger } from "@homarr/log";
|
||||
import { pingUrlChannel } from "@homarr/redis";
|
||||
import { pingRequestHandler } from "@homarr/request-handler/ping";
|
||||
import { sendPingRequestAsync } from "@homarr/ping";
|
||||
import { pingChannel, pingUrlChannel } from "@homarr/redis";
|
||||
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
@@ -28,6 +28,16 @@ export const pingJob = createCronJob("ping", EVERY_MINUTE, {
|
||||
});
|
||||
|
||||
const pingAsync = async (url: string) => {
|
||||
const handler = pingRequestHandler.handler({ url });
|
||||
await handler.getCachedOrUpdatedDataAsync({ forceUpdate: true });
|
||||
const pingResult = await sendPingRequestAsync(url);
|
||||
|
||||
if ("statusCode" in pingResult) {
|
||||
logger.debug(`executed ping for url ${url} with status code ${pingResult.statusCode}`);
|
||||
} else {
|
||||
logger.error(`Executing ping for url ${url} failed with error: ${pingResult.error}`);
|
||||
}
|
||||
|
||||
await pingChannel.publishAsync({
|
||||
url,
|
||||
...pingResult,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,22 +39,16 @@ export type HomarrDocumentationPath =
|
||||
| "/search"
|
||||
| "/docs/tags"
|
||||
| "/docs/tags/active-directory"
|
||||
| "/docs/tags/ad-guard"
|
||||
| "/docs/tags/ad-guard-home"
|
||||
| "/docs/tags/administration"
|
||||
| "/docs/tags/advanced"
|
||||
| "/docs/tags/analytics"
|
||||
| "/docs/tags/api"
|
||||
| "/docs/tags/apps"
|
||||
| "/docs/tags/background"
|
||||
| "/docs/tags/banner"
|
||||
| "/docs/tags/blocking"
|
||||
| "/docs/tags/boards"
|
||||
| "/docs/tags/bookmark"
|
||||
| "/docs/tags/bookmarks"
|
||||
| "/docs/tags/caddy"
|
||||
| "/docs/tags/certificates"
|
||||
| "/docs/tags/checklist"
|
||||
| "/docs/tags/code"
|
||||
| "/docs/tags/community"
|
||||
| "/docs/tags/configuration"
|
||||
@@ -64,63 +58,37 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/database"
|
||||
| "/docs/tags/developer"
|
||||
| "/docs/tags/development"
|
||||
| "/docs/tags/dns"
|
||||
| "/docs/tags/docker"
|
||||
| "/docs/tags/donation"
|
||||
| "/docs/tags/edit-mode"
|
||||
| "/docs/tags/env"
|
||||
| "/docs/tags/environment-variables"
|
||||
| "/docs/tags/feeds"
|
||||
| "/docs/tags/finance"
|
||||
| "/docs/tags/getting-started"
|
||||
| "/docs/tags/google"
|
||||
| "/docs/tags/grafana"
|
||||
| "/docs/tags/groups"
|
||||
| "/docs/tags/hardware"
|
||||
| "/docs/tags/health"
|
||||
| "/docs/tags/help"
|
||||
| "/docs/tags/icon-picker"
|
||||
| "/docs/tags/icon-repositories"
|
||||
| "/docs/tags/icons"
|
||||
| "/docs/tags/iframe"
|
||||
| "/docs/tags/images"
|
||||
| "/docs/tags/installation"
|
||||
| "/docs/tags/integrade"
|
||||
| "/docs/tags/integration"
|
||||
| "/docs/tags/integrations"
|
||||
| "/docs/tags/interface"
|
||||
| "/docs/tags/jellyserr"
|
||||
| "/docs/tags/jobs"
|
||||
| "/docs/tags/layout"
|
||||
| "/docs/tags/ldap"
|
||||
| "/docs/tags/links"
|
||||
| "/docs/tags/lists"
|
||||
| "/docs/tags/management"
|
||||
| "/docs/tags/market"
|
||||
| "/docs/tags/media"
|
||||
| "/docs/tags/minecraft"
|
||||
| "/docs/tags/monitoring"
|
||||
| "/docs/tags/network"
|
||||
| "/docs/tags/news"
|
||||
| "/docs/tags/notebook"
|
||||
| "/docs/tags/notes"
|
||||
| "/docs/tags/oidc"
|
||||
| "/docs/tags/open-collective"
|
||||
| "/docs/tags/open-media-vault"
|
||||
| "/docs/tags/overseerr"
|
||||
| "/docs/tags/permissions"
|
||||
| "/docs/tags/pgid"
|
||||
| "/docs/tags/pi-hole"
|
||||
| "/docs/tags/ping"
|
||||
| "/docs/tags/programming"
|
||||
| "/docs/tags/proxmox"
|
||||
| "/docs/tags/proxy"
|
||||
| "/docs/tags/puid"
|
||||
| "/docs/tags/releases"
|
||||
| "/docs/tags/repositories"
|
||||
| "/docs/tags/responsive"
|
||||
| "/docs/tags/roles"
|
||||
| "/docs/tags/rss"
|
||||
| "/docs/tags/search"
|
||||
| "/docs/tags/search-engines"
|
||||
| "/docs/tags/security"
|
||||
@@ -128,24 +96,15 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/tags/seo"
|
||||
| "/docs/tags/server"
|
||||
| "/docs/tags/settings"
|
||||
| "/docs/tags/sinkhole"
|
||||
| "/docs/tags/sso"
|
||||
| "/docs/tags/stocks"
|
||||
| "/docs/tags/system"
|
||||
| "/docs/tags/table"
|
||||
| "/docs/tags/tasks"
|
||||
| "/docs/tags/technical-documentation"
|
||||
| "/docs/tags/text"
|
||||
| "/docs/tags/torrent"
|
||||
| "/docs/tags/traefik"
|
||||
| "/docs/tags/translations"
|
||||
| "/docs/tags/unifi-controller"
|
||||
| "/docs/tags/unraid"
|
||||
| "/docs/tags/uploads"
|
||||
| "/docs/tags/usenet"
|
||||
| "/docs/tags/users"
|
||||
| "/docs/tags/variables"
|
||||
| "/docs/tags/widgets"
|
||||
| "/docs/advanced/command-line"
|
||||
| "/docs/advanced/command-line/fix-usernames"
|
||||
| "/docs/advanced/command-line/password-recovery"
|
||||
@@ -188,17 +147,38 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/getting-started/installation/source"
|
||||
| "/docs/getting-started/installation/synology"
|
||||
| "/docs/getting-started/installation/unraid"
|
||||
| "/docs/integrations/cloud"
|
||||
| "/docs/integrations/containers"
|
||||
| "/docs/integrations/dns"
|
||||
| "/docs/integrations/hardware"
|
||||
| "/docs/integrations/adguard-home"
|
||||
| "/docs/integrations/codeberg"
|
||||
| "/docs/integrations/dash-dot"
|
||||
| "/docs/integrations/deluge"
|
||||
| "/docs/integrations/docker-hub"
|
||||
| "/docs/integrations/docker"
|
||||
| "/docs/integrations/emby"
|
||||
| "/docs/integrations/github"
|
||||
| "/docs/integrations/gitlab"
|
||||
| "/docs/integrations/home-assistant"
|
||||
| "/docs/integrations/jellyfin"
|
||||
| "/docs/integrations/jellyseerr"
|
||||
| "/docs/integrations/kubernetes"
|
||||
| "/docs/integrations/media-requester"
|
||||
| "/docs/integrations/media-server"
|
||||
| "/docs/integrations/network"
|
||||
| "/docs/integrations/servarr"
|
||||
| "/docs/integrations/torrent"
|
||||
| "/docs/integrations/usenet"
|
||||
| "/docs/integrations/lidarr"
|
||||
| "/docs/integrations/nextcloud"
|
||||
| "/docs/integrations/npm"
|
||||
| "/docs/integrations/ntfy"
|
||||
| "/docs/integrations/nzbget"
|
||||
| "/docs/integrations/open-media-vault"
|
||||
| "/docs/integrations/overseerr"
|
||||
| "/docs/integrations/pi-hole"
|
||||
| "/docs/integrations/plex"
|
||||
| "/docs/integrations/prowlarr"
|
||||
| "/docs/integrations/proxmox"
|
||||
| "/docs/integrations/q-bittorent"
|
||||
| "/docs/integrations/radarr"
|
||||
| "/docs/integrations/readarr"
|
||||
| "/docs/integrations/sabnzbd"
|
||||
| "/docs/integrations/sonarr"
|
||||
| "/docs/integrations/tdarr"
|
||||
| "/docs/integrations/transmission"
|
||||
| "/docs/integrations/unifi-controller"
|
||||
| "/docs/management/api"
|
||||
| "/docs/management/apps"
|
||||
| "/docs/management/boards"
|
||||
@@ -209,23 +189,32 @@ export type HomarrDocumentationPath =
|
||||
| "/docs/management/settings"
|
||||
| "/docs/management/tasks"
|
||||
| "/docs/management/users"
|
||||
| "/docs/widgets/app"
|
||||
| "/docs/widgets/bookmarks"
|
||||
| "/docs/widgets/calendar"
|
||||
| "/docs/widgets/clock"
|
||||
| "/docs/widgets/dns-hole"
|
||||
| "/docs/widgets/dns-hole-controls"
|
||||
| "/docs/widgets/dns-hole-summary"
|
||||
| "/docs/widgets/docker-containers"
|
||||
| "/docs/widgets/downloads"
|
||||
| "/docs/widgets/health-monitoring"
|
||||
| "/docs/widgets/home-assistant"
|
||||
| "/docs/widgets/iframe"
|
||||
| "/docs/widgets/indexer-manager"
|
||||
| "/docs/widgets/media-requests"
|
||||
| "/docs/widgets/media-releases"
|
||||
| "/docs/widgets/media-request-list"
|
||||
| "/docs/widgets/media-request-stats"
|
||||
| "/docs/widgets/media-server"
|
||||
| "/docs/widgets/media-transcoding"
|
||||
| "/docs/widgets/minecraft-server-status"
|
||||
| "/docs/widgets/network-controller"
|
||||
| "/docs/widgets/network-controller-status"
|
||||
| "/docs/widgets/network-controller-summary"
|
||||
| "/docs/widgets/notebook"
|
||||
| "/docs/widgets/notifications"
|
||||
| "/docs/widgets/releases"
|
||||
| "/docs/widgets/rss"
|
||||
| "/docs/widgets/stocks"
|
||||
| "/docs/widgets/rss-feed"
|
||||
| "/docs/widgets/smart-home-entity-state"
|
||||
| "/docs/widgets/smart-home-execute-automation"
|
||||
| "/docs/widgets/stock-price"
|
||||
| "/docs/widgets/video"
|
||||
| "/docs/widgets/weather"
|
||||
| ""
|
||||
|
||||
4
packages/ping/eslint.config.js
Normal file
4
packages/ping/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [...baseConfig];
|
||||
1
packages/ping/index.ts
Normal file
1
packages/ping/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
36
packages/ping/package.json
Normal file
36
packages/ping/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@homarr/ping",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.32.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
37
packages/ping/src/index.ts
Normal file
37
packages/ping/src/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { extractErrorMessage } from "@homarr/common";
|
||||
import { LoggingAgent } from "@homarr/common/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
export const sendPingRequestAsync = async (url: string) => {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
|
||||
// 10 seconds timeout:
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
const start = performance.now();
|
||||
|
||||
return await fetch(url, {
|
||||
dispatcher: new LoggingAgent({
|
||||
connect: {
|
||||
rejectUnauthorized: false, // Ping should always work, even with untrusted certificates
|
||||
},
|
||||
}),
|
||||
signal: controller.signal,
|
||||
})
|
||||
.finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
})
|
||||
.then((response) => {
|
||||
const end = performance.now();
|
||||
const durationMs = end - start;
|
||||
return { statusCode: response.status, durationMs };
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(new Error(`Failed to send ping request to "${url}"`, { cause: error }));
|
||||
return {
|
||||
error: extractErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
9
packages/ping/tsconfig.json
Normal file
9
packages/ping/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LogLevel } from "@homarr/log/constants";
|
||||
import type { LogLevel } from "@homarr/log/constants";
|
||||
|
||||
import { createListChannel, createQueueChannel, createSubPubChannel } from "./lib/channel";
|
||||
|
||||
@@ -14,6 +14,10 @@ export {
|
||||
createGetSetChannel,
|
||||
} from "./lib/channel";
|
||||
|
||||
export const exampleChannel = createSubPubChannel<{ message: string }>("example");
|
||||
export const pingChannel = createSubPubChannel<
|
||||
{ url: string; statusCode: number; durationMs: number } | { url: string; error: string }
|
||||
>("ping");
|
||||
export const pingUrlChannel = createListChannel<string>("ping-url");
|
||||
|
||||
export const homeAssistantEntityState = createSubPubChannel<{
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import { fetch } from "undici";
|
||||
|
||||
import { extractErrorMessage } from "@homarr/common";
|
||||
import { LoggingAgent } from "@homarr/common/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
type PingResponse =
|
||||
| {
|
||||
statusCode: number;
|
||||
durationMs: number;
|
||||
}
|
||||
| {
|
||||
error: string;
|
||||
};
|
||||
export const pingRequestHandler = createCachedWidgetRequestHandler<PingResponse, "app", { url: string }>({
|
||||
queryKey: "pingResult",
|
||||
widgetKind: "app",
|
||||
async requestAsync(input) {
|
||||
return await sendPingRequestAsync(input.url);
|
||||
},
|
||||
cacheDuration: dayjs.duration(1, "minute"),
|
||||
});
|
||||
|
||||
const sendPingRequestAsync = async (url: string) => {
|
||||
try {
|
||||
const start = performance.now();
|
||||
return await fetch(url, {
|
||||
dispatcher: new LoggingAgent({
|
||||
connect: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
}),
|
||||
}).then((response) => {
|
||||
const end = performance.now();
|
||||
logger.debug(`Ping request succeeded url="${url}" status="${response.status}" duration="${end - start}ms"`);
|
||||
return { statusCode: response.status, durationMs: end - start };
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(new Error(`Failed to send ping request to url="${url}"`, { cause: error }));
|
||||
return {
|
||||
error: extractErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,20 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { Suspense } from "react";
|
||||
import { Flex, Text, Tooltip, UnstyledButton } from "@mantine/core";
|
||||
import { IconLoader } from "@tabler/icons-react";
|
||||
import combineClasses from "clsx";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { useSettings } from "@homarr/settings";
|
||||
import { useRegisterSpotlightContextResults } from "@homarr/spotlight";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { MaskedOrNormalImage } from "@homarr/ui";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import classes from "./app.module.css";
|
||||
import { PingDot } from "./ping/ping-dot";
|
||||
import { PingIndicator } from "./ping/ping-indicator";
|
||||
|
||||
export default function AppWidget({ options, isEditMode, height, width }: WidgetComponentProps<"app">) {
|
||||
const t = useI18n();
|
||||
const settings = useSettings();
|
||||
const board = useRequiredBoard();
|
||||
const [app] = clientApi.app.byId.useSuspenseQuery(
|
||||
@@ -92,7 +97,9 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
|
||||
</Flex>
|
||||
</Tooltip.Floating>
|
||||
{options.pingEnabled && !settings.forceDisableStatus && !board.disableStatus && app.href ? (
|
||||
<PingIndicator href={app.pingUrl ?? app.href} />
|
||||
<Suspense fallback={<PingDot icon={IconLoader} color="blue" tooltip={`${t("common.action.loading")}…`} />}>
|
||||
<PingIndicator href={app.pingUrl ?? app.href} />
|
||||
</Suspense>
|
||||
) : null}
|
||||
</AppLink>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { IconCheck, IconLoader, IconX } from "@tabler/icons-react";
|
||||
import { IconCheck, IconX } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import { PingDot } from "./ping-dot";
|
||||
|
||||
@@ -12,8 +11,17 @@ interface PingIndicatorProps {
|
||||
}
|
||||
|
||||
export const PingIndicator = ({ href }: PingIndicatorProps) => {
|
||||
const t = useI18n();
|
||||
const [pingResult, setPingResult] = useState<RouterOutputs["widget"]["app"]["updatedPing"] | null>(null);
|
||||
const [ping] = clientApi.widget.app.ping.useSuspenseQuery(
|
||||
{
|
||||
url: href,
|
||||
},
|
||||
{
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
);
|
||||
|
||||
const [pingResult, setPingResult] = useState<RouterOutputs["widget"]["app"]["ping"]>(ping);
|
||||
|
||||
clientApi.widget.app.updatedPing.useSubscription(
|
||||
{ url: href },
|
||||
@@ -24,10 +32,6 @@ export const PingIndicator = ({ href }: PingIndicatorProps) => {
|
||||
},
|
||||
);
|
||||
|
||||
if (!pingResult) {
|
||||
return <PingDot icon={IconLoader} color="blue" tooltip={`${t("common.action.loading")}…`} />;
|
||||
}
|
||||
|
||||
const isError = "error" in pingResult || pingResult.statusCode >= 500;
|
||||
|
||||
return (
|
||||
|
||||
37
pnpm-lock.yaml
generated
37
pnpm-lock.yaml
generated
@@ -380,6 +380,9 @@ importers:
|
||||
'@homarr/log':
|
||||
specifier: workspace:^
|
||||
version: link:../../packages/log
|
||||
'@homarr/ping':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/ping
|
||||
'@homarr/redis':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../packages/redis
|
||||
@@ -578,6 +581,9 @@ importers:
|
||||
'@homarr/old-schema':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../old-schema
|
||||
'@homarr/ping':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../ping
|
||||
'@homarr/redis':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../redis
|
||||
@@ -1013,6 +1019,9 @@ importers:
|
||||
'@homarr/log':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../log
|
||||
'@homarr/ping':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../ping
|
||||
'@homarr/redis':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../redis
|
||||
@@ -1738,6 +1747,34 @@ importers:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
packages/ping:
|
||||
dependencies:
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../certificates
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
'@homarr/log':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../log
|
||||
devDependencies:
|
||||
'@homarr/eslint-config':
|
||||
specifier: workspace:^0.2.0
|
||||
version: link:../../tooling/eslint
|
||||
'@homarr/prettier-config':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/prettier
|
||||
'@homarr/tsconfig':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../../tooling/typescript
|
||||
eslint:
|
||||
specifier: ^9.32.0
|
||||
version: 9.32.0
|
||||
typescript:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
packages/redis:
|
||||
dependencies:
|
||||
'@homarr/common':
|
||||
|
||||
Reference in New Issue
Block a user