File size: 6,152 Bytes
178e38e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { useState } from "preact/hooks";
import { useT } from "@shared/i18n/context";
import { useTestConnection } from "@shared/hooks/use-test-connection";
import type { DiagnosticCheck, DiagnosticStatus } from "@shared/types";

const STATUS_COLORS: Record<DiagnosticStatus, string> = {
  pass: "text-green-600 dark:text-green-400",
  fail: "text-red-500 dark:text-red-400",
  skip: "text-slate-400 dark:text-text-dim",
};

const STATUS_BG: Record<DiagnosticStatus, string> = {
  pass: "bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",
  fail: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",
  skip: "bg-slate-50 dark:bg-[#161b22] border-slate-200 dark:border-border-dark",
};

const CHECK_NAME_KEYS: Record<string, string> = {
  server: "checkServer",
  accounts: "checkAccounts",
  transport: "checkTransport",
  upstream: "checkUpstream",
};

const STATUS_KEYS: Record<DiagnosticStatus, string> = {
  pass: "statusPass",
  fail: "statusFail",
  skip: "statusSkip",
};

function StatusIcon({ status }: { status: DiagnosticStatus }) {
  if (status === "pass") {
    return (
      <svg class="size-5 text-green-600 dark:text-green-400 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
      </svg>
    );
  }
  if (status === "fail") {
    return (
      <svg class="size-5 text-red-500 dark:text-red-400 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
      </svg>
    );
  }
  return (
    <svg class="size-5 text-slate-400 dark:text-text-dim shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
    </svg>
  );
}

function CheckRow({ check }: { check: DiagnosticCheck }) {
  const t = useT();
  const nameKey = CHECK_NAME_KEYS[check.name] ?? check.name;
  const statusKey = STATUS_KEYS[check.status];

  return (
    <div class={`flex items-start gap-3 p-3 rounded-lg border ${STATUS_BG[check.status]}`}>
      <StatusIcon status={check.status} />
      <div class="flex-1 min-w-0">
        <div class="flex items-center gap-2">
          <span class="text-sm font-semibold text-slate-700 dark:text-text-main">
            {t(nameKey as Parameters<typeof t>[0])}
          </span>
          <span class={`text-xs font-medium ${STATUS_COLORS[check.status]}`}>
            {t(statusKey as Parameters<typeof t>[0])}
          </span>
          {check.latencyMs > 0 && (
            <span class="text-xs text-slate-400 dark:text-text-dim">{check.latencyMs}ms</span>
          )}
        </div>
        {check.detail && (
          <p class="text-xs text-slate-500 dark:text-text-dim mt-0.5 break-all">{check.detail}</p>
        )}
        {check.error && (
          <p class="text-xs text-red-500 dark:text-red-400 mt-0.5 break-all">{check.error}</p>
        )}
      </div>
    </div>
  );
}

export function TestConnection() {
  const t = useT();
  const { testing, result, error, runTest } = useTestConnection();
  const [collapsed, setCollapsed] = useState(true);

  return (
    <section class="bg-white dark:bg-card-dark border border-gray-200 dark:border-border-dark rounded-xl shadow-sm transition-colors">
      {/* Header */}
      <button
        onClick={() => setCollapsed(!collapsed)}
        class="w-full flex items-center justify-between p-5 cursor-pointer select-none"
      >
        <div class="flex items-center gap-2">
          <svg class="size-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
            <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
          </svg>
          <h2 class="text-[0.95rem] font-bold">{t("testConnection")}</h2>
          {result && !collapsed && (
            <span class={`text-xs font-medium ml-1 ${result.overall === "pass" ? "text-green-600 dark:text-green-400" : "text-red-500 dark:text-red-400"}`}>
              {result.overall === "pass" ? t("testPassed") : t("testFailed")}
            </span>
          )}
        </div>
        <svg class={`size-5 text-slate-400 dark:text-text-dim transition-transform ${collapsed ? "" : "rotate-180"}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
        </svg>
      </button>

      {/* Content */}
      {!collapsed && (
        <div class="px-5 pb-5 border-t border-slate-100 dark:border-border-dark pt-4">
          {/* Run test button */}
          <button
            onClick={runTest}
            disabled={testing}
            class={`w-full py-2.5 text-sm font-medium rounded-lg transition-colors ${
              testing
                ? "bg-slate-100 dark:bg-[#21262d] text-slate-400 dark:text-text-dim cursor-not-allowed"
                : "bg-primary text-white hover:bg-primary/90 cursor-pointer"
            }`}
          >
            {testing ? t("testing") : t("testConnection")}
          </button>

          {/* Error */}
          {error && (
            <p class="mt-3 text-sm text-red-500">{error}</p>
          )}

          {/* Results */}
          {result && (
            <div class="mt-4 space-y-2">
              {result.checks.map((check) => (
                <CheckRow key={check.name} check={check} />
              ))}
            </div>
          )}
        </div>
      )}
    </section>
  );
}