Add network identity diagnostics
This commit is contained in:
@@ -10,6 +10,7 @@ import { Drawer } from '../../ui/Drawer.js';
|
||||
import { Tooltip } from '../../ui/Tooltip.js';
|
||||
import {
|
||||
CONNECTIVITY_IP_SOURCES,
|
||||
CONNECTIVITY_NETWORK_SOURCE,
|
||||
CONNECTIVITY_SITES,
|
||||
MAX_CUSTOM_DIAGNOSTIC_SERVICES,
|
||||
} from '../../../shared/connectivityDiagnostics.js';
|
||||
@@ -134,6 +135,32 @@ function IpCell({
|
||||
return <code aria-label={`${route}: ${value.address}`}>{value.address}</code>;
|
||||
}
|
||||
|
||||
function NetworkCell({
|
||||
path,
|
||||
pending,
|
||||
route,
|
||||
}: {
|
||||
path: DiagnosticPath | undefined;
|
||||
pending: boolean;
|
||||
route: string;
|
||||
}) {
|
||||
let status: StatusValue | null = null;
|
||||
if (path?.available === false) status = ['is-muted', '—'];
|
||||
else if (pending) status = ['is-running', 'Тестируем'];
|
||||
else if (!path?.available) status = ['is-muted', '—'];
|
||||
const identity = [path?.network?.asn, path?.network?.provider].filter(Boolean).join(' · ');
|
||||
const location = [path?.network?.city, path?.network?.country].filter(Boolean).join(', ');
|
||||
if (!status && !identity && !location) status = ['is-error', 'Нет ответа'];
|
||||
if (status) return <>
|
||||
<Status value={status} route={route} /><br />
|
||||
<span className="client-diagnostics-status is-muted" aria-hidden="true"> </span>
|
||||
</>;
|
||||
return <span aria-label={`${route}: ${[identity, location].filter(Boolean).join(', ')}`}>
|
||||
<span className="client-diagnostics-status">{identity || location}</span><br />
|
||||
<span className="client-diagnostics-status is-muted">{identity && location ? location : <> </>}</span>
|
||||
</span>;
|
||||
}
|
||||
|
||||
function mergeItems<T>(previous: T[] = [], incoming: T[] = [], key: (item: T) => string) {
|
||||
const merged = [...previous];
|
||||
for (const item of incoming) {
|
||||
@@ -161,6 +188,7 @@ function mergePath(previous: DiagnosticPath | undefined, incoming: DiagnosticPat
|
||||
ipv4: { addresses, sources },
|
||||
ipv6,
|
||||
ipv6Source,
|
||||
network: incoming.available === false ? null : incoming.network ?? previous?.network ?? null,
|
||||
sites,
|
||||
};
|
||||
}
|
||||
@@ -242,6 +270,7 @@ export function ConnectivityDiagnosticsPanel({
|
||||
try {
|
||||
let next = result;
|
||||
const targets = [
|
||||
CONNECTIVITY_NETWORK_SOURCE.id,
|
||||
...CONNECTIVITY_IP_SOURCES.map(({ id }) => `ip:${id}`),
|
||||
...sites.map(({ id }) => `site:${id}`),
|
||||
];
|
||||
@@ -378,7 +407,24 @@ export function ConnectivityDiagnosticsPanel({
|
||||
<th>Напрямую</th>
|
||||
<th>VPN{result?.vpn?.server?.label ? ` · ${result.vpn.server.label}` : ''}</th>
|
||||
</tr></thead>
|
||||
<tbody>{CONNECTIVITY_IP_SOURCES.map((source) => {
|
||||
<tbody>
|
||||
<tr
|
||||
data-diagnostic-target={CONNECTIVITY_NETWORK_SOURCE.id}
|
||||
className={activeTarget === CONNECTIVITY_NETWORK_SOURCE.id ? 'is-running' : undefined}
|
||||
>
|
||||
<th scope="row">{CONNECTIVITY_NETWORK_SOURCE.label}</th>
|
||||
<td><NetworkCell
|
||||
path={result?.direct}
|
||||
pending={activeTarget === CONNECTIVITY_NETWORK_SOURCE.id}
|
||||
route="Напрямую, сеть"
|
||||
/></td>
|
||||
<td><NetworkCell
|
||||
path={result?.vpn}
|
||||
pending={activeTarget === CONNECTIVITY_NETWORK_SOURCE.id}
|
||||
route="VPN, сеть"
|
||||
/></td>
|
||||
</tr>
|
||||
{CONNECTIVITY_IP_SOURCES.map((source) => {
|
||||
const target = `ip:${source.id}`;
|
||||
const running = activeTarget === target;
|
||||
return <tr key={source.id} data-diagnostic-target={target} className={running ? 'is-running' : undefined}>
|
||||
|
||||
@@ -12,6 +12,14 @@ export interface DiagnosticSiteResult extends Record<string, unknown> {
|
||||
latencyMs: number | null;
|
||||
}
|
||||
|
||||
export interface DiagnosticNetworkResult extends Record<string, unknown> {
|
||||
address: string | null;
|
||||
asn: string | null;
|
||||
provider: string | null;
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
}
|
||||
|
||||
export interface DiagnosticServer extends Record<string, unknown> {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -26,6 +34,7 @@ export interface DiagnosticPath extends Record<string, unknown> {
|
||||
};
|
||||
ipv6: string | null;
|
||||
ipv6Source: DiagnosticIpResult | null;
|
||||
network?: DiagnosticNetworkResult | null;
|
||||
sites: DiagnosticSiteResult[];
|
||||
server?: DiagnosticServer | null;
|
||||
}
|
||||
@@ -59,6 +68,19 @@ function validSiteResult(value: unknown): value is DiagnosticSiteResult {
|
||||
&& nullableNonnegativeNumber(value.latencyMs);
|
||||
}
|
||||
|
||||
function nullableText(value: unknown): value is string | null {
|
||||
return value === null || (typeof value === 'string' && value.length > 0);
|
||||
}
|
||||
|
||||
function validNetworkResult(value: unknown): value is DiagnosticNetworkResult {
|
||||
return record(value)
|
||||
&& nullableText(value.address)
|
||||
&& nullableText(value.asn)
|
||||
&& nullableText(value.provider)
|
||||
&& nullableText(value.city)
|
||||
&& nullableText(value.country);
|
||||
}
|
||||
|
||||
function validServer(value: unknown): value is DiagnosticServer | null {
|
||||
return value === null || (record(value)
|
||||
&& typeof value.id === 'string'
|
||||
@@ -76,6 +98,7 @@ function validPath(value: unknown): value is DiagnosticPath {
|
||||
&& value.ipv4.sources.every(validIpResult)
|
||||
&& (value.ipv6 === null || (typeof value.ipv6 === 'string' && value.ipv6.length > 0))
|
||||
&& (value.ipv6Source === null || validIpResult(value.ipv6Source))
|
||||
&& (!Object.hasOwn(value, 'network') || value.network === null || validNetworkResult(value.network))
|
||||
&& Array.isArray(value.sites)
|
||||
&& value.sites.every(validSiteResult);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user