aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-10-12 13:42:24 +0000
committerMohamed Bassem <me@mbassem.com>2025-10-12 18:29:40 +0000
commitfda1c851cf507ca7e309e80ff068444dfaab93c3 (patch)
treed349fdae4fa1a3a1e32152f0df41948fd56b7f89 /packages
parent7ee9416e8f1689b6390ea51c7a8484936c12026d (diff)
downloadkarakeep-fda1c851cf507ca7e309e80ff068444dfaab93c3.tar.zst
feat: Add service dependency checks in the server overview page
Diffstat (limited to 'packages')
-rw-r--r--packages/shared/plugins.ts8
-rw-r--r--packages/trpc/routers/admin.ts116
2 files changed, 124 insertions, 0 deletions
diff --git a/packages/shared/plugins.ts b/packages/shared/plugins.ts
index 2aa7df4a..e04fd91e 100644
--- a/packages/shared/plugins.ts
+++ b/packages/shared/plugins.ts
@@ -51,6 +51,14 @@ export class PluginManager {
return PluginManager.providers[type].length > 0;
}
+ static getPluginName<T extends PluginType>(type: T): string | null {
+ const providers: TPlugin<T>[] = PluginManager.providers[type];
+ if (providers.length === 0) {
+ return null;
+ }
+ return providers[providers.length - 1]!.name;
+ }
+
static logAllPlugins() {
logger.info("Plugins (Last one wins):");
for (const type of Object.values(PluginType)) {
diff --git a/packages/trpc/routers/admin.ts b/packages/trpc/routers/admin.ts
index 25425eaf..a40dfa6f 100644
--- a/packages/trpc/routers/admin.ts
+++ b/packages/trpc/routers/admin.ts
@@ -14,6 +14,8 @@ import {
VideoWorkerQueue,
WebhookQueue,
} from "@karakeep/shared-server";
+import serverConfig from "@karakeep/shared/config";
+import { PluginManager, PluginType } from "@karakeep/shared/plugins";
import { getSearchClient } from "@karakeep/shared/search";
import {
resetPasswordSchema,
@@ -417,4 +419,118 @@ export const adminAppRouter = router({
// Unused for now
};
}),
+ checkConnections: adminProcedure
+ .output(
+ z.object({
+ searchEngine: z.object({
+ configured: z.boolean(),
+ connected: z.boolean(),
+ pluginName: z.string().optional(),
+ error: z.string().optional(),
+ }),
+ browser: z.object({
+ configured: z.boolean(),
+ connected: z.boolean(),
+ pluginName: z.string().optional(),
+ error: z.string().optional(),
+ }),
+ queue: z.object({
+ configured: z.boolean(),
+ connected: z.boolean(),
+ pluginName: z.string().optional(),
+ error: z.string().optional(),
+ }),
+ }),
+ )
+ .query(async () => {
+ const searchEngineStatus: {
+ configured: boolean;
+ connected: boolean;
+ pluginName?: string;
+ error?: string;
+ } = { configured: false, connected: false };
+ const browserStatus: {
+ configured: boolean;
+ connected: boolean;
+ pluginName?: string;
+ error?: string;
+ } = { configured: false, connected: false };
+ const queueStatus: {
+ configured: boolean;
+ connected: boolean;
+ pluginName?: string;
+ error?: string;
+ } = { configured: true, connected: false };
+
+ const searchClient = await getSearchClient();
+ searchEngineStatus.configured = searchClient !== null;
+
+ if (searchClient) {
+ const pluginName = PluginManager.getPluginName(PluginType.Search);
+ if (pluginName) {
+ searchEngineStatus.pluginName = pluginName;
+ }
+ try {
+ await searchClient.search({ query: "", limit: 1 });
+ searchEngineStatus.connected = true;
+ } catch (error) {
+ searchEngineStatus.error =
+ error instanceof Error ? error.message : "Unknown error";
+ }
+ }
+
+ browserStatus.configured =
+ !!serverConfig.crawler.browserWebUrl ||
+ !!serverConfig.crawler.browserWebSocketUrl;
+
+ if (browserStatus.configured) {
+ if (serverConfig.crawler.browserWebUrl) {
+ browserStatus.pluginName = "Browserless/Chrome";
+ } else if (serverConfig.crawler.browserWebSocketUrl) {
+ browserStatus.pluginName = "WebSocket Browser";
+ }
+
+ try {
+ if (serverConfig.crawler.browserWebUrl) {
+ const response = await fetch(
+ `${serverConfig.crawler.browserWebUrl}/json/version`,
+ {
+ signal: AbortSignal.timeout(5000),
+ },
+ );
+ if (response.ok) {
+ browserStatus.connected = true;
+ } else {
+ browserStatus.error = `HTTP ${response.status}: ${response.statusText}`;
+ }
+ } else if (serverConfig.crawler.browserWebSocketUrl) {
+ browserStatus.connected = true;
+ browserStatus.error =
+ "WebSocket URL configured (connection check not supported)";
+ }
+ } catch (error) {
+ browserStatus.error =
+ error instanceof Error ? error.message : "Unknown error";
+ }
+ }
+
+ const queuePluginName = PluginManager.getPluginName(PluginType.Queue);
+ if (queuePluginName) {
+ queueStatus.pluginName = queuePluginName;
+ }
+
+ try {
+ await LinkCrawlerQueue.stats();
+ queueStatus.connected = true;
+ } catch (error) {
+ queueStatus.error =
+ error instanceof Error ? error.message : "Unknown error";
+ }
+
+ return {
+ searchEngine: searchEngineStatus,
+ browser: browserStatus,
+ queue: queueStatus,
+ };
+ }),
});