diff --git a/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs b/DebugProbe.AspNetCore/Internal/HtmlRenderer.cs index c388143..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")) @@ -80,9 +74,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 +92,59 @@ 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)) + { + return "Empty"; + } + + if (JsonUtils.IsValidJson(value)) + { + return "JSON"; + } + + return LooksLikeJson(value) ? "Invalid JSON" : "Plain Text"; + } + + private static string GetPayloadTypeClass(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return "payload-empty"; + } + + 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/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..aef0974 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,53 +152,137 @@ 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; } .code-block pre { + min-height: 18px; 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; +} + +.compare-pane-title { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 6px; +} + +/* ========================= + Badges +========================= */ + +.code-badge { + position: relative; + top: -1px; +} + +.code-badge, +.status, +.diff-badge { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + font-weight: 600; +} + +.code-badge { + padding: 4px 8px; + font-size: 12px; +} + +.payload-json { + background: #183d24; + color: #6ddf8b; +} + +.payload-invalid-json { + background: #4a1717; + color: #ff8a8a; +} + +.payload-text { + background: #4a3112; + color: #ffb84d; +} + +.payload-url { + background: #1d3557; + color: #7cc7ff; +} + +.payload-empty { + background: #444; + color: #ddd; +} + +.diff-badge { + background: #4a1717; + color: #ff8a8a; } /* ========================= Diff ========================= */ + .diff-line { min-height: 18px; padding-left: 6px; @@ -218,30 +310,22 @@ 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 { @@ -249,12 +333,7 @@ pre { top: 8px; right: 8px; padding: 4px 8px; -} - -.btn-clear { - padding: 6px 12px; - font-size: 13px; - color: #fff; + font-size: 11px; } .copy-btn:hover, @@ -262,15 +341,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; } @@ -281,60 +363,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 { @@ -347,8 +422,6 @@ pre { } .accordion-meta { - display: flex; - align-items: center; gap: 10px; } @@ -358,4 +431,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 23a0843..aacd832 100644 --- a/DebugProbe.AspNetCore/Resources/html/details.html +++ b/DebugProbe.AspNetCore/Resources/html/details.html @@ -11,8 +11,8 @@
{{requestUrl}}
{{request}}
{{response}}
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 ) {