194 lines
6.8 KiB
TypeScript
194 lines
6.8 KiB
TypeScript
import type { WorkbenchTraceTimelinePolicy } from "@/components/workbench/trace-lifecycle";
|
|
|
|
export interface DisplayTimeConfig {
|
|
timeZone: string;
|
|
locale: string;
|
|
label: string;
|
|
}
|
|
|
|
export interface OpenCodeFrameConfig {
|
|
url: string;
|
|
}
|
|
|
|
export interface WorkbenchDebugCapabilities {
|
|
isolatedKafka: boolean;
|
|
rawHwlabEventWindow: WorkbenchRawHwlabEventWindowCapability;
|
|
}
|
|
|
|
export interface WorkbenchRealtimeCapabilities {
|
|
liveKafkaSse: boolean;
|
|
kafkaRefreshReplay: boolean;
|
|
projectionRealtime: boolean;
|
|
}
|
|
|
|
export interface WorkbenchRawHwlabEventWindowCapability {
|
|
enabled: boolean;
|
|
maxEntries: number;
|
|
maxRetainedBytes: number;
|
|
}
|
|
|
|
const DEFAULT_DISPLAY_DATE_TIME_OPTIONS: Intl.DateTimeFormatOptions = {
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit",
|
|
hour12: false
|
|
};
|
|
const TRACE_ID_PATTERN = /^[A-Za-z0-9_.:-]{3,160}$/u;
|
|
const TRACE_EXPLORER_TEMPLATE_TOKEN = "{trace_id}";
|
|
const TRACE_EXPLORER_TEMPLATE_PLACEHOLDER_PATTERN = /\{([A-Za-z0-9_:-]+)\}/gu;
|
|
|
|
export function displayTimeConfig(): DisplayTimeConfig {
|
|
const config = window.HWLAB_CLOUD_WEB_CONFIG?.displayTime;
|
|
const timeZone = requiredDisplayTimeField(config?.timeZone, "timeZone");
|
|
const locale = requiredDisplayTimeField(config?.locale, "locale");
|
|
const label = requiredDisplayTimeField(config?.label, "label");
|
|
validateDisplayTimeZone(timeZone);
|
|
validateDisplayLocale(locale);
|
|
return { timeZone, locale, label };
|
|
}
|
|
|
|
export function displayTimeLabel(): string {
|
|
return displayTimeConfig().label;
|
|
}
|
|
|
|
export function formatDisplayDateTime(value: string | Date, options: Intl.DateTimeFormatOptions = {}): string {
|
|
const date = value instanceof Date ? value : new Date(value);
|
|
if (!Number.isFinite(date.getTime())) return String(value);
|
|
const config = displayTimeConfig();
|
|
const dateTimeOptions = Object.keys(options).length > 0 ? options : DEFAULT_DISPLAY_DATE_TIME_OPTIONS;
|
|
return new Intl.DateTimeFormat(config.locale, { hour12: false, ...dateTimeOptions, timeZone: config.timeZone }).format(date);
|
|
}
|
|
|
|
export function formatDisplayClock(value: string | Date): string {
|
|
return formatDisplayDateTime(value, { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
}
|
|
|
|
function requiredDisplayTimeField(value: unknown, field: string): string {
|
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
throw new Error(`HWLAB Cloud Web displayTime.${field} is required`);
|
|
}
|
|
return value.trim();
|
|
}
|
|
|
|
function validateDisplayTimeZone(timeZone: string): void {
|
|
try {
|
|
new Intl.DateTimeFormat("en-US", { timeZone }).format(new Date(0));
|
|
} catch {
|
|
throw new Error(`HWLAB Cloud Web displayTime.timeZone is invalid: ${timeZone}`);
|
|
}
|
|
}
|
|
|
|
function validateDisplayLocale(locale: string): void {
|
|
try {
|
|
new Intl.DateTimeFormat(locale).format(new Date(0));
|
|
} catch {
|
|
throw new Error(`HWLAB Cloud Web displayTime.locale is invalid: ${locale}`);
|
|
}
|
|
}
|
|
|
|
export function workbenchTraceTimelinePolicy(): WorkbenchTraceTimelinePolicy {
|
|
const config = window.HWLAB_CLOUD_WEB_CONFIG?.workbench?.traceTimeline;
|
|
return {
|
|
autoExpandRunning: config?.autoExpandRunning === true,
|
|
autoCollapseTerminal: config?.autoCollapseTerminal === true
|
|
};
|
|
}
|
|
|
|
export function workbenchDebugCapabilities(): WorkbenchDebugCapabilities {
|
|
const features = window.HWLAB_CLOUD_WEB_CONFIG?.workbench?.debugCapabilities;
|
|
const rawWindow = features?.rawHwlabEventWindow;
|
|
if (
|
|
typeof features?.isolatedKafka !== "boolean" ||
|
|
typeof rawWindow?.enabled !== "boolean" ||
|
|
!Number.isInteger(rawWindow.maxEntries) || Number(rawWindow.maxEntries) <= 0 ||
|
|
!Number.isInteger(rawWindow.maxRetainedBytes) || Number(rawWindow.maxRetainedBytes) <= 0
|
|
) {
|
|
throw new Error("HWLAB Cloud Web workbench.debugCapabilities and rawHwlabEventWindow policy are required");
|
|
}
|
|
return {
|
|
isolatedKafka: features.isolatedKafka,
|
|
rawHwlabEventWindow: {
|
|
enabled: rawWindow.enabled,
|
|
maxEntries: Number(rawWindow.maxEntries),
|
|
maxRetainedBytes: Number(rawWindow.maxRetainedBytes)
|
|
}
|
|
};
|
|
}
|
|
|
|
export function workbenchRealtimeCapabilities(): WorkbenchRealtimeCapabilities {
|
|
const features = window.HWLAB_CLOUD_WEB_CONFIG?.workbench?.realtimeFeatures;
|
|
if (
|
|
typeof features?.liveKafkaSse !== "boolean" ||
|
|
typeof features?.kafkaRefreshReplay !== "boolean" ||
|
|
typeof features?.projectionRealtime !== "boolean"
|
|
) {
|
|
throw new Error("HWLAB Cloud Web workbench.realtimeFeatures are required");
|
|
}
|
|
return {
|
|
liveKafkaSse: features.liveKafkaSse,
|
|
kafkaRefreshReplay: features.kafkaRefreshReplay,
|
|
projectionRealtime: features.projectionRealtime
|
|
};
|
|
}
|
|
|
|
export function traceExplorerHref(traceId: string | null | undefined): string | null {
|
|
const safeTraceId = normalizedTraceId(traceId);
|
|
if (!safeTraceId) return null;
|
|
const template = window.HWLAB_CLOUD_WEB_CONFIG?.workbench?.traceExplorerUrlTemplate;
|
|
if (typeof template !== "string" || !isSafeTraceExplorerUrlTemplate(template)) return null;
|
|
const rendered = template.trim().replaceAll(TRACE_EXPLORER_TEMPLATE_TOKEN, encodeURIComponent(safeTraceId));
|
|
try {
|
|
const url = new URL(rendered, runtimeOrigin());
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
return rendered;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function opencodeFrameUrl(): string | null {
|
|
const value = window.HWLAB_CLOUD_WEB_CONFIG?.opencode?.url;
|
|
if (typeof value !== "string") return null;
|
|
const trimmed = value.trim();
|
|
if (!isSafeOpencodeFrameUrl(trimmed)) return null;
|
|
return trimmed;
|
|
}
|
|
|
|
function normalizedTraceId(value: string | null | undefined): string | null {
|
|
const traceId = typeof value === "string" ? value.trim() : "";
|
|
return TRACE_ID_PATTERN.test(traceId) ? traceId : null;
|
|
}
|
|
|
|
function isSafeTraceExplorerUrlTemplate(template: string): boolean {
|
|
const trimmed = template.trim();
|
|
if (!trimmed.includes(TRACE_EXPLORER_TEMPLATE_TOKEN) || trimmed.startsWith("//")) return false;
|
|
const placeholders = Array.from(trimmed.matchAll(TRACE_EXPLORER_TEMPLATE_PLACEHOLDER_PATTERN), (match) => match[0]);
|
|
if (placeholders.length === 0 || placeholders.some((placeholder) => placeholder !== TRACE_EXPLORER_TEMPLATE_TOKEN)) return false;
|
|
try {
|
|
const rendered = trimmed.replaceAll(TRACE_EXPLORER_TEMPLATE_TOKEN, "trc_template_probe");
|
|
const url = new URL(rendered, runtimeOrigin());
|
|
return url.protocol === "http:" || url.protocol === "https:";
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isSafeOpencodeFrameUrl(value: string): boolean {
|
|
if (!value || value.startsWith("//")) return false;
|
|
try {
|
|
const url = new URL(value, runtimeOrigin());
|
|
return url.protocol === "http:" || url.protocol === "https:";
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function runtimeOrigin(): string {
|
|
return typeof window.location?.origin === "string" && window.location.origin.length > 0
|
|
? window.location.origin
|
|
: "http://hwlab-cloud-web.local";
|
|
}
|