import type { CSSProperties } from "react"; import type { Finding } from "@/lib/inspect/types"; export type Tab = "overview" | "dns" | "tls" | "http" | "raw"; export type FindingFilter = "all" | Finding["severity"]; export const tabs: Tab[] = ["overview", "dns", "tls", "http", "raw"]; export const headerNames = [ "strict-transport-security", "content-security-policy", "x-content-type-options", "referrer-policy", "x-frame-options", ]; export function downloadText(filename: string, content: string, type: string) { const blob = new Blob([content], { type }); const href = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = href; anchor.download = filename; document.body.appendChild(anchor); anchor.click(); anchor.remove(); window.setTimeout(() => URL.revokeObjectURL(href), 0); } export function CopyIcon() { return ; } export function CloseIcon() { return ; } export function Gauge({ value, tone }: { value: number; tone: "green" | "amber" | "red" | "muted" }) { const degrees = Math.max(0, Math.min(100, value)) * 3.6; return
; } export function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } export function finiteNumber(value: unknown): value is number { return typeof value === "number" && Number.isFinite(value); } export function hasValue(value: unknown) { return value !== null && value !== undefined && value !== ""; } export function compact(value: unknown) { if (Array.isArray(value)) return value.length ? value.map((item) => item && typeof item === "object" ? JSON.stringify(item) : String(item)).join("\n") : "—"; if (value && typeof value === "object") return JSON.stringify(value); return value == null || value === "" ? "—" : String(value); } export function formatMxRecords(value: unknown) { if (!Array.isArray(value)) return compact(value); return value.length ? value.map((record) => { if (!isRecord(record) || typeof record.exchange !== "string" || !finiteNumber(record.priority)) return compact(record); return `${record.priority} · ${record.exchange}`; }).join("\n") : "—"; } export function formatCertificateName(value: unknown) { if (!isRecord(value)) return compact(value); const labels: Record = { O: "Organization", CN: "Common name", C: "Country" }; return Object.entries(value).map(([key, entry]) => `${labels[key] ?? key}: ${compact(entry)}`).join("\n") || "—"; } export function severityLabel(severity: Finding["severity"]) { return severity === "critical" ? "Critical" : severity === "warning" ? "Warning" : severity === "good" ? "Good" : "Info"; } export function findingIcon(severity: Finding["severity"]) { return severity === "good" ? "✓" : severity === "info" ? "i" : "!"; } export function toneForScore(value: number) { return value >= 90 ? "green" as const : value >= 75 ? "amber" as const : "red" as const; } export function terminalHttpsResult(http: Record) { const chain = Array.isArray(http.httpsChain) ? http.httpsChain.filter(isRecord) : []; const last = chain.at(-1); return last && !("error" in last) ? last : null; } export function collectorMeta(value: Record) { const meta = isRecord(value._meta) ? value._meta : {}; const status = meta.status === "ok" || meta.status === "partial" || meta.status === "failed" ? meta.status : "failed"; const errors = Array.isArray(meta.errors) ? meta.errors : []; return { status, errors }; } export function collectorLabel(status: string) { return status === "ok" ? "Complete" : status === "partial" ? "Partial" : "Failed"; } export function triState(value: unknown, observed: boolean) { return value === true ? "Yes" : value === false && observed ? "No" : "Unavailable"; } export function formatInspectedAt(value: string) { const date = new Date(value); if (!Number.isFinite(date.getTime())) return value; return `${date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "medium" })} local`; }