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`;
}