aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components/admin/ServiceConnections.tsx
blob: 8d79d8bb89a746641dbf332ba2786e9a9196559d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"use client";

import { AdminCard } from "@/components/admin/AdminCard";
import { useTranslation } from "@/lib/i18n/client";
import { api } from "@/lib/trpc";

function ConnectionStatus({
  label,
  configured,
  connected,
  pluginName,
  error,
}: {
  label: string;
  configured: boolean;
  connected: boolean;
  pluginName?: string;
  error?: string;
}) {
  const { t } = useTranslation();

  let statusText = t("admin.service_connections.status.not_configured");
  let badgeColor =
    "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400";
  let iconColor = "text-gray-400";
  let borderColor = "border-gray-200 dark:border-gray-700";

  if (configured) {
    if (connected) {
      statusText = t("admin.service_connections.status.connected");
      badgeColor =
        "bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-400";
      iconColor = "text-green-500";
      borderColor = "border-green-200 dark:border-green-800";
    } else {
      statusText = t("admin.service_connections.status.disconnected");
      badgeColor =
        "bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400";
      iconColor = "text-red-500";
      borderColor = "border-red-200 dark:border-red-800";
    }
  }

  return (
    <div
      className={`rounded-lg border ${borderColor} bg-background p-5 shadow-sm transition-all sm:w-1/3`}
    >
      <div className="mb-3 flex items-center justify-between">
        <div>
          <div className="text-base font-semibold">{label}</div>
          {pluginName && (
            <div className="mt-1 text-xs text-muted-foreground">
              {pluginName}
            </div>
          )}
        </div>
        <div
          className={`flex h-2 w-2 items-center justify-center rounded-full ${iconColor}`}
        >
          <div
            className={`h-2 w-2 rounded-full ${connected && configured ? "animate-pulse" : ""} bg-current`}
          ></div>
        </div>
      </div>
      <div className="mb-2">
        <span
          className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${badgeColor}`}
        >
          {statusText}
        </span>
      </div>
      {error && (
        <div className="mt-3 rounded-md bg-red-50 p-2 dark:bg-red-900/10">
          <p className="text-xs text-red-600 dark:text-red-400" title={error}>
            {error.length > 60 ? `${error.substring(0, 60)}...` : error}
          </p>
        </div>
      )}
    </div>
  );
}

function ConnectionsSkeleton() {
  return (
    <AdminCard>
      <div className="mb-4 h-7 w-40 animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
      <div className="flex flex-col gap-4 sm:flex-row">
        {[1, 2, 3].map((i) => (
          <div
            key={i}
            className="rounded-lg border border-gray-200 bg-background p-5 shadow-sm dark:border-gray-700 sm:w-1/3"
          >
            <div className="mb-3 flex items-center justify-between">
              <div className="h-5 w-28 animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
              <div className="h-2 w-2 animate-pulse rounded-full bg-gray-300 dark:bg-gray-600"></div>
            </div>
            <div className="mb-2">
              <div className="h-5 w-20 animate-pulse rounded-full bg-gray-200 dark:bg-gray-700"></div>
            </div>
          </div>
        ))}
      </div>
    </AdminCard>
  );
}

export default function ServiceConnections() {
  const { t } = useTranslation();
  const { data: connections } = api.admin.checkConnections.useQuery(undefined, {
    refetchInterval: 10000,
  });

  if (!connections) {
    return <ConnectionsSkeleton />;
  }

  return (
    <AdminCard>
      <div className="mb-2 text-xl font-medium">
        {t("admin.service_connections.title")}
      </div>
      <p className="mb-4 text-sm text-muted-foreground">
        {t("admin.service_connections.description")}
      </p>
      <div className="flex flex-col gap-4 sm:flex-row">
        <ConnectionStatus
          label={t("admin.service_connections.search_engine")}
          configured={connections.searchEngine.configured}
          connected={connections.searchEngine.connected}
          pluginName={connections.searchEngine.pluginName}
          error={connections.searchEngine.error}
        />
        <ConnectionStatus
          label={t("admin.service_connections.browser")}
          configured={connections.browser.configured}
          connected={connections.browser.connected}
          pluginName={connections.browser.pluginName}
          error={connections.browser.error}
        />
        <ConnectionStatus
          label={t("admin.service_connections.queue_system")}
          configured={connections.queue.configured}
          connected={connections.queue.connected}
          pluginName={connections.queue.pluginName}
          error={connections.queue.error}
        />
      </div>
    </AdminCard>
  );
}