262 lines
11 KiB
JavaScript
262 lines
11 KiB
JavaScript
function createDebugContentView({
|
|
DEBUG_PANEL_ID,
|
|
MODAL_ID,
|
|
debugState,
|
|
formatDebugBody,
|
|
formatHeaderBlock,
|
|
formatMetadataBlock,
|
|
environment
|
|
}) {
|
|
const { document } = environment;
|
|
const categories = [
|
|
["all", "All"], ["authentication", "Authentication"],
|
|
["opportunities", "Opportunities"], ["products", "Products"],
|
|
["ai", "AI"], ["consumption", "Consumption"], ["installBase", "Installed Base"],
|
|
["serviceRequests", "Service Requests"], ["other", "Other"]
|
|
];
|
|
let selectedCategory = "all";
|
|
const expandedIds = new Set();
|
|
|
|
function getRequestCategory(entry) {
|
|
if (/^request(?:TokenRelay|ConsumerTokenService|CustomerTokenService)/.test(entry.name)) return "authentication";
|
|
if (entry.name === "requestOpportunities") return "opportunities";
|
|
if (entry.name === "requestOpptyProducts") return "products";
|
|
if (entry.name === "requestGenerateAiSummary") return "ai";
|
|
if (entry.name === "requestInstallBase") return "installBase";
|
|
if (entry.name === "requestCustomerServiceRequest") return "serviceRequests";
|
|
if (/^request(?:AccountSummary|AccountPlans|ConsumptionService|ConsumptionPlanDetail|RatecardByPlan)/.test(entry.name)) return "consumption";
|
|
return "other";
|
|
}
|
|
|
|
function getChronologicalRequests() {
|
|
return [...debugState.requestLog].sort((a, b) =>
|
|
(new Date(a.startedAt).getTime() || 0) - (new Date(b.startedAt).getTime() || 0) || a.id - b.id);
|
|
}
|
|
function toggleDebugPanel() {
|
|
const modal = document.getElementById(MODAL_ID);
|
|
|
|
if (!modal || modal.hidden) {
|
|
return;
|
|
}
|
|
|
|
const panel = document.getElementById(DEBUG_PANEL_ID);
|
|
|
|
if (!panel) {
|
|
return;
|
|
}
|
|
|
|
panel.hidden = !panel.hidden;
|
|
renderDebugPanel();
|
|
}
|
|
|
|
function renderDebugPanel() {
|
|
const panel = document.getElementById(DEBUG_PANEL_ID);
|
|
|
|
if (!panel || panel.hidden) {
|
|
return;
|
|
}
|
|
|
|
for (const item of panel.querySelectorAll("details[data-request-id]")) {
|
|
const id = item.getAttribute("data-request-id");
|
|
if (item.open) expandedIds.add(id);
|
|
else expandedIds.delete(id);
|
|
}
|
|
const focusedCategory = document.activeElement && document.activeElement.getAttribute("data-debug-category");
|
|
const scrollTop = panel.scrollTop;
|
|
panel.textContent = "";
|
|
|
|
const panelHeader = document.createElement("header");
|
|
panelHeader.className = "opportunities-extension-debug-panel-header";
|
|
|
|
const heading = document.createElement("div");
|
|
const title = document.createElement("h2");
|
|
title.textContent = "Debug Requests";
|
|
|
|
const summary = document.createElement("p");
|
|
summary.textContent = `${debugState.requestLog.length} request${debugState.requestLog.length === 1 ? "" : "s"} since modal opened.`;
|
|
const running = debugState.requestLog.filter(entry => entry.status === "pending").length;
|
|
if (running) summary.textContent += ` ${running} running.`;
|
|
heading.append(title, summary);
|
|
|
|
const visibilityButton = document.createElement("button");
|
|
visibilityButton.type = "button";
|
|
visibilityButton.className = "opportunities-extension-icon-button opportunities-extension-debug-visibility-button";
|
|
visibilityButton.setAttribute("aria-pressed", String(debugState.debugSecretsVisible));
|
|
visibilityButton.setAttribute("aria-label", debugState.debugSecretsVisible ? "Hide sensitive values" : "Show sensitive values");
|
|
visibilityButton.title = debugState.debugSecretsVisible ? "Hide sensitive values" : "Show sensitive values";
|
|
visibilityButton.append(createDebugVisibilityIcon(debugState.debugSecretsVisible));
|
|
visibilityButton.addEventListener("click", () => {
|
|
debugState.debugSecretsVisible = !debugState.debugSecretsVisible;
|
|
renderDebugPanel();
|
|
});
|
|
panelHeader.append(heading, visibilityButton);
|
|
|
|
const entries = getChronologicalRequests();
|
|
const availableCategories = categories.filter(([key]) => key === "all" || entries.some(entry => getRequestCategory(entry) === key));
|
|
if (!availableCategories.some(([key]) => key === selectedCategory)) selectedCategory = "all";
|
|
const tabs = document.createElement("div");
|
|
tabs.className = "opportunities-extension-debug-tabs";
|
|
tabs.setAttribute("role", "tablist");
|
|
tabs.setAttribute("aria-label", "Request categories");
|
|
const tabButtons = [];
|
|
availableCategories.forEach(([key, label], index) => {
|
|
const categoryEntries = key === "all" ? entries : entries.filter(entry => getRequestCategory(entry) === key);
|
|
const pending = categoryEntries.filter(entry => entry.status === "pending").length;
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.id = `${DEBUG_PANEL_ID}-tab-${key}`;
|
|
button.setAttribute("role", "tab");
|
|
button.setAttribute("data-debug-category", key);
|
|
button.setAttribute("aria-selected", String(key === selectedCategory));
|
|
button.setAttribute("aria-controls", `${DEBUG_PANEL_ID}-requests`);
|
|
button.tabIndex = key === selectedCategory ? 0 : -1;
|
|
button.textContent = `${label} (${categoryEntries.length})${pending ? ` / ${pending} running` : ""}`;
|
|
button.addEventListener("click", () => {
|
|
selectedCategory = key;
|
|
renderDebugPanel();
|
|
});
|
|
button.addEventListener("keydown", event => {
|
|
let nextIndex;
|
|
if (event.key === "ArrowRight") nextIndex = (index + 1) % availableCategories.length;
|
|
else if (event.key === "ArrowLeft") nextIndex = (index + availableCategories.length - 1) % availableCategories.length;
|
|
else if (event.key === "Home") nextIndex = 0;
|
|
else if (event.key === "End") nextIndex = availableCategories.length - 1;
|
|
else return;
|
|
event.preventDefault();
|
|
selectedCategory = availableCategories[nextIndex][0];
|
|
renderDebugPanel();
|
|
});
|
|
tabs.append(button);
|
|
tabButtons.push(button);
|
|
});
|
|
|
|
const list = document.createElement("div");
|
|
list.className = "opportunities-extension-debug-list";
|
|
list.id = `${DEBUG_PANEL_ID}-requests`;
|
|
list.setAttribute("role", "tabpanel");
|
|
list.setAttribute("aria-labelledby", `${DEBUG_PANEL_ID}-tab-${selectedCategory}`);
|
|
list.tabIndex = 0;
|
|
|
|
if (debugState.requestLog.length === 0) {
|
|
const empty = document.createElement("div");
|
|
empty.className = "opportunities-extension-debug-empty";
|
|
empty.textContent = "No requests recorded yet.";
|
|
list.append(empty);
|
|
}
|
|
|
|
entries.filter(entry => selectedCategory === "all" || getRequestCategory(entry) === selectedCategory).forEach((entry) => {
|
|
const item = document.createElement("details");
|
|
item.className = `opportunities-extension-debug-item opportunities-extension-debug-item-${entry.status}`;
|
|
item.setAttribute("data-request-id", String(entry.id));
|
|
item.open = expandedIds.has(String(entry.id));
|
|
|
|
const heading = document.createElement("summary");
|
|
heading.className = "opportunities-extension-debug-heading";
|
|
|
|
const name = document.createElement("strong");
|
|
name.textContent = `${entry.id}. ${entry.name}`;
|
|
if (entry.name === "requestOpptyProducts" && entry.metadata.optyNumber) {
|
|
name.textContent += ` (${entry.metadata.optyNumber}, offset ${entry.metadata.offset})`;
|
|
}
|
|
|
|
const httpStatus = document.createElement("span");
|
|
httpStatus.className = "opportunities-extension-debug-http-status";
|
|
httpStatus.textContent = entry.httpStatus ? String(entry.httpStatus) : "pending";
|
|
|
|
const status = document.createElement("span");
|
|
status.className = "opportunities-extension-debug-status";
|
|
status.textContent = entry.status === "pending" ? "running" : entry.status;
|
|
|
|
const summaryMeta = document.createElement("span");
|
|
summaryMeta.className = "opportunities-extension-debug-summary-meta";
|
|
const startedTime = document.createElement("time");
|
|
startedTime.className = "opportunities-extension-debug-time";
|
|
startedTime.textContent = formatDebugTime(entry.startedAt);
|
|
startedTime.title = `Started: ${entry.startedAt.toLocaleString()}`;
|
|
summaryMeta.append(startedTime, httpStatus, status);
|
|
|
|
heading.append(name, summaryMeta);
|
|
|
|
const lines = [
|
|
`${entry.method} ${entry.url}`,
|
|
`Started: ${formatDebugTime(entry.startedAt)}`,
|
|
entry.completedAt ? `Completed: ${formatDebugTime(entry.completedAt)} (${entry.durationMs}ms)` : "Completed: pending",
|
|
entry.httpStatus ? `HTTP: ${entry.httpStatus} ${entry.statusText}`.trim() : "",
|
|
entry.error ? `Error: ${entry.error}` : "",
|
|
`Response body truncated: ${entry.responseBodyTruncated ? "yes" : "no"}`
|
|
].filter(Boolean);
|
|
|
|
const details = document.createElement("div");
|
|
details.className = "opportunities-extension-debug-details";
|
|
appendDebugBlock(details, "Request", lines.join("\n"));
|
|
appendDebugBlock(details, "Request Metadata", formatMetadataBlock(entry.metadata));
|
|
appendDebugBlock(details, "Request Headers", formatHeaderBlock(entry.requestHeaders));
|
|
appendDebugBlock(details, "Request Body", formatDebugBody(entry.requestBody));
|
|
appendDebugBlock(details, "Response Headers", formatHeaderBlock(entry.responseHeaders));
|
|
appendDebugBlock(details, "Response Body", formatDebugBody(entry.responseBody));
|
|
|
|
item.append(heading, details);
|
|
list.append(item);
|
|
});
|
|
|
|
panel.append(panelHeader, tabs, list);
|
|
if (focusedCategory) tabButtons.find(button => button.getAttribute("data-debug-category") === selectedCategory).focus({ preventScroll: true });
|
|
panel.scrollTop = scrollTop;
|
|
}
|
|
|
|
function createDebugVisibilityIcon(secretsVisible) {
|
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
const eye = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
const pupil = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
|
|
svg.setAttribute("viewBox", "0 0 24 24");
|
|
svg.setAttribute("aria-hidden", "true");
|
|
svg.setAttribute("focusable", "false");
|
|
eye.setAttribute("d", "M2.5 12s3.5-6.5 9.5-6.5 9.5 6.5 9.5 6.5-3.5 6.5-9.5 6.5S2.5 12 2.5 12Z");
|
|
pupil.setAttribute("cx", "12");
|
|
pupil.setAttribute("cy", "12");
|
|
pupil.setAttribute("r", "2.75");
|
|
[eye, pupil].forEach((shape) => {
|
|
shape.setAttribute("fill", "none");
|
|
shape.setAttribute("stroke", "currentColor");
|
|
shape.setAttribute("stroke-linecap", "round");
|
|
shape.setAttribute("stroke-linejoin", "round");
|
|
shape.setAttribute("stroke-width", "1.7");
|
|
});
|
|
svg.append(eye, pupil);
|
|
|
|
if (secretsVisible) {
|
|
const slash = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
slash.setAttribute("d", "M4 4l16 16");
|
|
slash.setAttribute("fill", "none");
|
|
slash.setAttribute("stroke", "currentColor");
|
|
slash.setAttribute("stroke-linecap", "round");
|
|
slash.setAttribute("stroke-width", "1.9");
|
|
svg.append(slash);
|
|
}
|
|
|
|
return svg;
|
|
}
|
|
|
|
function appendDebugBlock(container, label, value) {
|
|
const block = document.createElement("section");
|
|
const heading = document.createElement("h3");
|
|
const content = document.createElement("pre");
|
|
|
|
heading.textContent = label;
|
|
content.textContent = value;
|
|
block.append(heading, content);
|
|
container.append(block);
|
|
}
|
|
|
|
function formatDebugTime(date) {
|
|
return date.toLocaleTimeString(undefined, {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit"
|
|
});
|
|
}
|
|
|
|
return { toggleDebugPanel, renderDebugPanel, createDebugVisibilityIcon, appendDebugBlock, formatDebugTime, getRequestCategory, getChronologicalRequests };
|
|
}
|