From dc8f887b300025023f39879025ac139b990f8628 Mon Sep 17 00:00:00 2001 From: Georgi Hristov Date: Tue, 12 May 2026 22:47:53 +0300 Subject: [PATCH 1/3] feat(ui): add payload type badges --- .../Internal/HtmlRenderer.cs | 32 ++++++++++++++-- DebugProbe.AspNetCore/Internal/JsonUtils.cs | 18 +++++++++ .../Resources/css/debugprobe.css | 38 +++++++++++++++++-- .../Resources/html/details.html | 35 +++++++++++++++-- .../Resources/js/debugprobe_ui.js | 5 ++- 5 files changed, 117 insertions(+), 11 deletions(-) diff --git a/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs b/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs index c388143..83ea8c3 100644 --- a/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs +++ b/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs @@ -80,9 +80,15 @@ public static string RenderDetailsPage(DebugEntry x, DebugEnvironment e, string .Replace("{{dateFormat}}", e.DateFormat ?? "") .Replace("{{assemblyVersion}}", Encode(e.AssemblyVersion)) - .Replace("{{requestUrl}}", Encode(string.IsNullOrEmpty(x.RequestUrl) ? "(empty)" : x.RequestUrl)) - .Replace("{{request}}", Encode(string.IsNullOrEmpty(req) ? "(empty)" : req)) - .Replace("{{response}}", Encode(string.IsNullOrEmpty(res) ? "(empty)" : res)) + .Replace("{{requestUrl}}", Encode(string.IsNullOrEmpty(x.RequestUrl) ? "" : x.RequestUrl)) + .Replace("{{requestType}}", GetPayloadType(req)) + .Replace("{{requestTypeClass}}", GetPayloadTypeClass(req)) + .Replace("{{request}}", Encode(string.IsNullOrEmpty(req) ? "" : req)) + + .Replace("{{responseType}}", GetPayloadType(res)) + .Replace("{{responseTypeClass}}", GetPayloadTypeClass(res)) + .Replace("{{response}}", Encode(string.IsNullOrEmpty(res) ? "" : res)) + .Replace("{{headers}}", headers); return BuildLayout(content); @@ -92,4 +98,24 @@ private static string Encode(string? value) { return WebUtility.HtmlEncode(value ?? ""); } + + private static string GetPayloadType(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return "Empty"; + } + + return JsonUtils.IsValidJson(value) ? "JSON" : "Plain Text"; + } + + private static string GetPayloadTypeClass(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return "payload-empty"; + } + + return JsonUtils.IsValidJson(value) ? "payload-json" : "payload-text"; + } } diff --git a/DebugProbe.AspNetCore/Internal/JsonUtils.cs b/DebugProbe.AspNetCore/Internal/JsonUtils.cs index db6e92a..17c2f1d 100644 --- a/DebugProbe.AspNetCore/Internal/JsonUtils.cs +++ b/DebugProbe.AspNetCore/Internal/JsonUtils.cs @@ -27,4 +27,22 @@ public static string Format(string json) return json; } } + + public static bool IsValidJson(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + + try + { + JsonDocument.Parse(value); + return true; + } + catch + { + return false; + } + } } \ No newline at end of file diff --git a/DebugProbe.AspNetCore/Resources/css/debugprobe.css b/DebugProbe.AspNetCore/Resources/css/debugprobe.css index b777aed..eb60fc8 100644 --- a/DebugProbe.AspNetCore/Resources/css/debugprobe.css +++ b/DebugProbe.AspNetCore/Resources/css/debugprobe.css @@ -161,6 +161,7 @@ pre { } .code-block pre { + min-height: 18px; margin: 0; } @@ -188,6 +189,40 @@ pre { border: 1px solid rgba(231, 76, 60, 0.25); } +.code-block-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.code-badge { + font-size: 12px; + font-weight: 600; + padding: 4px 8px; + border-radius: 999px; +} + +.payload-json { + background: #183d24; + color: #6ddf8b; +} + +.payload-text { + background: #4a3112; + color: #ffb84d; +} + +.payload-url { + background: #1d3557; + color: #7cc7ff; +} + +.payload-empty { + background: #444; + color: #ddd; +} + /* ========================= Diff ========================= */ @@ -245,9 +280,6 @@ pre { } .copy-btn { - position: absolute; - top: 8px; - right: 8px; padding: 4px 8px; } diff --git a/DebugProbe.AspNetCore/Resources/html/details.html b/DebugProbe.AspNetCore/Resources/html/details.html index 23a0843..dccd22e 100644 --- a/DebugProbe.AspNetCore/Resources/html/details.html +++ b/DebugProbe.AspNetCore/Resources/html/details.html @@ -20,7 +20,7 @@

{{method}} {{path}}

TraceId @@ -92,19 +92,46 @@

{{method}} {{path}}

Request URL

- +
+ + URL + + + +
+
{{requestUrl}}

Request Body

- +
+ + {{requestType}} + + + +
+
{{request}}

Response Body

- +
+ + {{responseType}} + + + +
+
{{response}}
diff --git a/DebugProbe.AspNetCore/Resources/js/debugprobe_ui.js b/DebugProbe.AspNetCore/Resources/js/debugprobe_ui.js index a32dbf1..8eab5ce 100644 --- a/DebugProbe.AspNetCore/Resources/js/debugprobe_ui.js +++ b/DebugProbe.AspNetCore/Resources/js/debugprobe_ui.js @@ -1,9 +1,12 @@ function copyText(btn) { - const pre = btn.parentElement.querySelector("pre"); + const pre = btn.closest(".code-block").querySelector("pre"); + const text = pre.dataset.copy ?? pre.innerText; + navigator.clipboard.writeText(text); btn.innerText = "Copied"; + setTimeout(() => btn.innerText = "Copy", 1500); } From 30c6c71782752f3f725253504cc1f1df0c19458f Mon Sep 17 00:00:00 2001 From: Georgi Hristov Date: Tue, 12 May 2026 23:55:58 +0300 Subject: [PATCH 2/3] feat: improve details page payload badges and status visualization --- .../Internal/HtmlRenderer.cs | 51 +++- .../Resources/css/debugprobe.css | 252 ++++++++++-------- .../Resources/html/details.html | 53 ++-- 3 files changed, 213 insertions(+), 143 deletions(-) diff --git a/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs b/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs index 83ea8c3..6f847a0 100644 --- a/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs +++ b/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs @@ -48,20 +48,14 @@ public static string RenderDetailsPage(DebugEntry x, DebugEnvironment e, string ? x.Path : $"{x.Path}{x.Query}"; - var statusClass = x.StatusCode switch - { - >= 200 and < 300 => "status-200", - >= 300 and < 400 => "status-300", - >= 400 and < 500 => "status-400", - >= 500 => "status-500", - _ => "" - }; + var statusClass = GetStatusClass(x.StatusCode); var content = EmbeddedResources.Details .Replace("{{method}}", Encode(x.Method)) .Replace("{{path}}", Encode(pathWithQuery)) - .Replace("{{status}}", string.Format($"{x.StatusCode} {((HttpStatusCode)x.StatusCode)}")) + .Replace("{{status}}", GetStatusText(x.StatusCode)) .Replace("{{statusClass}}", statusClass) + .Replace("{{responseStatusCode}}", x.StatusCode.ToString()) .Replace("{{traceId}}", x.Id.ToString()) .Replace("{{time}}", x.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff")) @@ -99,6 +93,23 @@ private static string Encode(string? value) return WebUtility.HtmlEncode(value ?? ""); } + private static string GetStatusText(int statusCode) + { + return $"{statusCode} {((HttpStatusCode)statusCode)}"; + } + + private static string GetStatusClass(int statusCode) + { + return statusCode switch + { + >= 200 and < 300 => "status-200", + >= 300 and < 400 => "status-300", + >= 400 and < 500 => "status-400", + >= 500 => "status-500", + _ => "" + }; + } + private static string GetPayloadType(string value) { if (string.IsNullOrWhiteSpace(value)) @@ -106,7 +117,12 @@ private static string GetPayloadType(string value) return "Empty"; } - return JsonUtils.IsValidJson(value) ? "JSON" : "Plain Text"; + if (JsonUtils.IsValidJson(value)) + { + return "JSON"; + } + + return LooksLikeJson(value) ? "Invalid JSON" : "Plain Text"; } private static string GetPayloadTypeClass(string value) @@ -116,6 +132,19 @@ private static string GetPayloadTypeClass(string value) return "payload-empty"; } - return JsonUtils.IsValidJson(value) ? "payload-json" : "payload-text"; + if (JsonUtils.IsValidJson(value)) + { + return "payload-json"; + } + + return LooksLikeJson(value) ? "payload-invalid-json" : "payload-text"; } + + private static bool LooksLikeJson(string value) + { + var trimmed = value.TrimStart(); + + return trimmed.StartsWith('{') || trimmed.StartsWith('['); + } + } diff --git a/DebugProbe.AspNetCore/Resources/css/debugprobe.css b/DebugProbe.AspNetCore/Resources/css/debugprobe.css index eb60fc8..00d1969 100644 --- a/DebugProbe.AspNetCore/Resources/css/debugprobe.css +++ b/DebugProbe.AspNetCore/Resources/css/debugprobe.css @@ -1,18 +1,11 @@ /* ========================= Base ========================= */ + body { margin: 0; - font-family: Arial, sans-serif; background: #f7f7f7; -} - -h2 { - margin-bottom: 10px; -} - -h3 { - margin-top: 25px; + font-family: Arial, sans-serif; } a { @@ -26,29 +19,46 @@ a { text-decoration: underline; } +h2 { + margin-bottom: 10px; +} + +h3 { + margin-top: 25px; + margin-bottom: 10px; +} + /* ========================= Layout ========================= */ + .container { padding: 20px; } -.toolbar { +.toolbar, +.topbar, +.accordion-header, +.accordion-meta, +.details-item, +.json-compare { display: flex; - justify-content: space-between; align-items: center; - margin-bottom: 10px; } -.topbar { - display: flex; +.toolbar, +.topbar, +.accordion-header, +.details-item { justify-content: space-between; - align-items: center; +} + +.topbar { padding: 18px; - font-family: 'Roboto', sans-serif; - font-size: 20px; background: #1b1b1b; color: #fff; + font-family: 'Roboto', sans-serif; + font-size: 20px; } .logo { @@ -58,15 +68,15 @@ a { } .logo img { - height: 24px; width: auto; + height: 24px; } .env { - font-size: 13px; padding: 4px 10px; - border-radius: 4px; background: #2d2d2d; + border-radius: 4px; + font-size: 13px; opacity: 0.9; } @@ -75,33 +85,30 @@ a { ========================= */ .details-grid { - align-items: start; display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + align-items: start; gap: 16px; margin-bottom: 24px; } .details-card { + overflow: hidden; background: #fff; border: 1px solid #e9e9e9; border-radius: 8px; - overflow: hidden; } .details-card-title { padding: 14px 16px; + background: #fafafa; + border-bottom: 1px solid #eee; font-size: 16px; font-weight: 600; - border-bottom: 1px solid #eee; - background: #fafafa; } .details-item { min-height: 32px; - display: flex; - justify-content: space-between; - align-items: center; padding: 10px 16px; border-bottom: 1px solid #f3f3f3; } @@ -115,17 +122,18 @@ a { } .details-item strong { - font-weight: 600; font-size: 15px; + font-weight: 600; } /* ========================= Table ========================= */ + table { width: 100%; - border-collapse: collapse; background: #fff; + border-collapse: collapse; } th, @@ -144,18 +152,25 @@ tr:hover { } /* ========================= - Code / JSON + Section Titles ========================= */ -pre { - background: #1e1e1e; - color: #dcdcdc; - padding: 12px; - border-radius: 6px; - overflow: auto; - font-size: 13px; - line-height: 1.4; + +.section-title { + display: flex; + align-items: center; + gap: 10px; + margin-top: 25px; + margin-bottom: 10px; } +.section-title h3 { + margin: 0; +} + +/* ========================= + Code Blocks +========================= */ + .code-block { position: relative; } @@ -165,42 +180,66 @@ pre { margin: 0; } +pre { + overflow: auto; + margin: 0; + padding: 12px; + background: #1e1e1e; + border-radius: 6px; + color: #dcdcdc; + font-size: 13px; + line-height: 1.4; +} + +/* ========================= + JSON Compare +========================= */ + .json-compare { - display: flex; - gap: 20px; align-items: flex-start; + gap: 20px; } .json-compare > div { - flex: 1; min-width: 0; + flex: 1; padding: 10px; border-bottom: 1px solid #eee; text-align: left; } .json-error { - font-size: 12px; - padding: 4px 8px; margin-bottom: 4px; - border-radius: 6px; + padding: 4px 8px; background: rgba(231, 76, 60, 0.12); - color: #ff8a8a; border: 1px solid rgba(231, 76, 60, 0.25); + border-radius: 6px; + color: #ff8a8a; + font-size: 12px; } -.code-block-header { - display: flex; - justify-content: space-between; +/* ========================= + Badges +========================= */ + +.code-badge { + position: relative; + top: -1px; +} + +.code-badge, +.status, +.diff-badge { + display: inline-flex; align-items: center; - margin-bottom: 8px; + justify-content: center; + border-radius: 999px; + font-weight: 600; } .code-badge { - font-size: 12px; - font-weight: 600; padding: 4px 8px; - border-radius: 999px; + font-size: 12px; } .payload-json { @@ -208,6 +247,11 @@ pre { color: #6ddf8b; } +.payload-invalid-json { + background: #4a1717; + color: #ff8a8a; +} + .payload-text { background: #4a3112; color: #ffb84d; @@ -223,9 +267,19 @@ pre { color: #ddd; } +.diff-badge { + min-width: 22px; + height: 22px; + background: #e74c3c; + color: #fff; + font-size: 12px; + font-weight: bold; +} + /* ========================= Diff ========================= */ + .diff-line { min-height: 18px; padding-left: 6px; @@ -253,40 +307,30 @@ pre { border-left: 3px solid #e74c3c; } -.diff-badge { - min-width: 22px; - height: 22px; - border-radius: 999px; - background: #e74c3c; - color: #fff; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - font-weight: bold; -} - /* ========================= Buttons ========================= */ + +.copy-btn, +.btn-clear, +.trace-id-button { + border-radius: 4px; + cursor: pointer; +} + .copy-btn, .btn-clear { - font-size: 11px; background: #2d2d2d; - color: #ccc; border: 1px solid #444; - border-radius: 4px; - cursor: pointer; + color: #ccc; } .copy-btn { + position: absolute; + top: 8px; + right: 8px; padding: 4px 8px; -} - -.btn-clear { - padding: 6px 12px; - font-size: 13px; - color: #fff; + font-size: 11px; } .copy-btn:hover, @@ -294,15 +338,18 @@ pre { background: #3a3a3a; } +.btn-clear { + padding: 6px 12px; + color: #fff; + font-size: 13px; +} .trace-id-button { + padding: 6px 10px; background: #f3f4f6; border: none; - border-radius: 6px; - font-size: 13px; - padding: 6px 10px; - cursor: pointer; font-family: monospace; + font-size: 13px; font-weight: 600; } @@ -313,60 +360,53 @@ pre { /* ========================= Status ========================= */ + +.status { + padding: 4px 8px; + font-size: 12px; +} + .status-ok { - color: #2ecc71; + color: #2ecc71 !important; font-weight: bold; } .status-error { - color: #e74c3c; + color: #e74c3c !important; font-weight: bold; } -.status { - display: inline-flex; - align-items: center; - padding: 4px 10px; - border-radius: 999px; - font-size: 13px; - font-weight: 600; -} - .status-200 { - background: #dcfce7; - color: #166534; + background: #183d24; + color: #6ddf8b !important; } .status-300 { - background: #dbeafe; - color: #1d4ed8; + background: #1d3557; + color: #7cc7ff !important; } .status-400, .status-500 { - background: #fecaca; - color: #991b1b; + background: #4a1717; + color: #ff8a8a !important; } - /* ========================= Accordion ========================= */ .accordion-section { - background: #fff; - border-radius: 8px; - margin-bottom: 14px; overflow: hidden; + margin-bottom: 14px; + background: #fff; border: 1px solid #e9e9e9; + border-radius: 8px; } .accordion-header { - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; padding: 18px; + cursor: pointer; } .accordion-header:hover { @@ -379,8 +419,6 @@ pre { } .accordion-meta { - display: flex; - align-items: center; gap: 10px; } @@ -390,4 +428,4 @@ pre { .accordion-body.open { display: block; - } \ No newline at end of file + } diff --git a/DebugProbe.AspNetCore/Resources/html/details.html b/DebugProbe.AspNetCore/Resources/html/details.html index dccd22e..aacd832 100644 --- a/DebugProbe.AspNetCore/Resources/html/details.html +++ b/DebugProbe.AspNetCore/Resources/html/details.html @@ -11,8 +11,8 @@

{{method}} {{path}}

Status - - {{status}} + + {{status}}
@@ -90,48 +90,51 @@

{{method}} {{path}}

-

Request URL

-
-
+
+

Request URL

+ +
URL - -
- +
+
+
{{requestUrl}}
-

Request Body

-
-
+ +
+

Request Body

+ +
{{requestType}} - -
- +
+
+
{{request}}
-

Response Body

-
-
+ +
+

Response Body

+ +
{{responseType}} - + + {{responseStatusCode}} +
- +
+
+
{{response}}
From 3f55c00332670b7598a39909e7567a134334ea63 Mon Sep 17 00:00:00 2001 From: Georgi Hristov Date: Wed, 13 May 2026 00:10:04 +0300 Subject: [PATCH 3/3] feat(compare): add payload type badges to compare request and response sections --- .../Resources/css/debugprobe.css | 15 +++-- .../js/debugprobe_compare_renderer.js | 61 ++++++++++++++++--- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/DebugProbe.AspNetCore/Resources/css/debugprobe.css b/DebugProbe.AspNetCore/Resources/css/debugprobe.css index 00d1969..aef0974 100644 --- a/DebugProbe.AspNetCore/Resources/css/debugprobe.css +++ b/DebugProbe.AspNetCore/Resources/css/debugprobe.css @@ -218,6 +218,13 @@ pre { font-size: 12px; } +.compare-pane-title { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 6px; +} + /* ========================= Badges ========================= */ @@ -268,12 +275,8 @@ pre { } .diff-badge { - min-width: 22px; - height: 22px; - background: #e74c3c; - color: #fff; - font-size: 12px; - font-weight: bold; + background: #4a1717; + color: #ff8a8a; } /* ========================= diff --git a/DebugProbe.AspNetCore/Resources/js/debugprobe_compare_renderer.js b/DebugProbe.AspNetCore/Resources/js/debugprobe_compare_renderer.js index 7989b65..25be611 100644 --- a/DebugProbe.AspNetCore/Resources/js/debugprobe_compare_renderer.js +++ b/DebugProbe.AspNetCore/Resources/js/debugprobe_compare_renderer.js @@ -180,17 +180,19 @@ function renderSideBySideJson(comparison, localJson, remoteJson ) {
- Local - - ${comparison.localError ? `
${comparison.localError}
` : ''} +
+ Local + ${renderPayloadBadge(localJson)} +
${renderAlignedJson(comparison.local,localJson)}
- Remote - - ${comparison.remoteError ? `
${comparison.remoteError}
` : ''} +
+ Remote + ${renderPayloadBadge(remoteJson)} +
${renderAlignedJson(comparison.remote, remoteJson )}
@@ -199,6 +201,49 @@ function renderSideBySideJson(comparison, localJson, remoteJson ) { `; } +function renderPayloadBadge(value) { + + const payloadType = getPayloadType(value); + + return `${payloadType.label}`; +} + +function getPayloadType(value) { + + if (!value || !value.trim()) { + return { + label: 'Empty', + className: 'payload-empty' + }; + } + + try { + JSON.parse(value); + + return { + label: 'JSON', + className: 'payload-json' + }; + } catch { + return looksLikeJson(value) + ? { + label: 'Invalid JSON', + className: 'payload-invalid-json' + } + : { + label: 'Plain Text', + className: 'payload-text' + }; + } +} + +function looksLikeJson(value) { + + const trimmed = value.trimStart(); + + return trimmed.startsWith('{') || trimmed.startsWith('['); +} + function renderAccordionSection(title, content, expanded = false, changes = 0) { return `
@@ -211,7 +256,7 @@ function renderAccordionSection(title, content, expanded = false, changes = 0) {
- ${changes > 0 ? `${changes}` : ''} + ${changes > 0 ? `${changes}` : ''} ${expanded ? '-' : '+'} @@ -288,4 +333,4 @@ function escapeHtml(value) { .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); -} \ No newline at end of file +}