sentiero 1.0.0.alpha1 → 1.0.0.beta3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +15 -13
- data/lib/sentiero/analytics/analyzer.rb +16 -4
- data/lib/sentiero/analytics/browser_event_discovery.rb +2 -2
- data/lib/sentiero/analytics/conversion_analyzer.rb +1 -1
- data/lib/sentiero/analytics/engagement_analyzer.rb +1 -1
- data/lib/sentiero/analytics/error_discovery.rb +9 -2
- data/lib/sentiero/analytics/exporter.rb +1 -1
- data/lib/sentiero/analytics/frustration_analyzer.rb +1 -1
- data/lib/sentiero/analytics/funnel_analyzer.rb +2 -2
- data/lib/sentiero/analytics/heatmap_analyzer.rb +3 -3
- data/lib/sentiero/analytics/page_report_analyzer.rb +2 -2
- data/lib/sentiero/analytics/scroll_depth_analyzer.rb +1 -1
- data/lib/sentiero/analytics/segmenter.rb +16 -2
- data/lib/sentiero/analytics/stats_aggregator/result_builder.rb +2 -0
- data/lib/sentiero/analytics/stats_aggregator.rb +45 -6
- data/lib/sentiero/analytics/web_vitals_analyzer.rb +2 -2
- data/lib/sentiero/configuration.rb +29 -0
- data/lib/sentiero/fingerprint/config.rb +40 -0
- data/lib/sentiero/fingerprint.rb +45 -11
- data/lib/sentiero/geo.rb +86 -0
- data/lib/sentiero/reporter.rb +2 -1
- data/lib/sentiero/store/session_store.rb +62 -3
- data/lib/sentiero/store.rb +7 -0
- data/lib/sentiero/stores/file.rb +5 -1
- data/lib/sentiero/stores/memory.rb +15 -3
- data/lib/sentiero/stores/redis.rb +17 -5
- data/lib/sentiero/stores/sqlite/payload_codec.rb +42 -0
- data/lib/sentiero/stores/sqlite/schema.rb +72 -6
- data/lib/sentiero/stores/sqlite.rb +216 -101
- data/lib/sentiero/version.rb +1 -1
- data/lib/sentiero/web/analytics_app.rb +10 -0
- data/lib/sentiero/web/assets/dashboard-YINQE5ZH.js +3 -0
- data/lib/sentiero/web/assets/manifest.json +4 -4
- data/lib/sentiero/web/assets/recorder-W2RCU5VM.js +78 -0
- data/lib/sentiero/web/assets/rrweb-player-d522d74c.js +201 -0
- data/lib/sentiero/web/assets/{style-d71e72fd.css → style-80e536e6.css} +1 -1
- data/lib/sentiero/web/body_reader.rb +21 -10
- data/lib/sentiero/web/dashboard_app.rb +77 -5
- data/lib/sentiero/web/errors_app.rb +14 -1
- data/lib/sentiero/web/events_app.rb +6 -1
- data/lib/sentiero/web/ingest_app.rb +1 -1
- data/lib/sentiero/web/templates/client_error_show.html.erb +6 -1
- data/lib/sentiero/web/templates/dashboard.html.erb +7 -0
- data/lib/sentiero/web/templates/maintenance.html.erb +50 -0
- data/lib/sentiero/web/templates/segments.html.erb +9 -0
- data/lib/sentiero/web/templates/sessions_index.html.erb +12 -4
- data/lib/sentiero/web/views/analytics_index_view.rb +7 -1
- data/lib/sentiero/web/views/client_error_show_view.rb +1 -1
- data/lib/sentiero/web/views/maintenance_view.rb +27 -0
- data/lib/sentiero/web/views/segments_view.rb +3 -2
- data/lib/sentiero/web/views/sessions_index_view.rb +6 -2
- data/lib/sentiero/web/views.rb +1 -0
- data/lib/sentiero.rb +1 -0
- metadata +10 -5
- data/lib/sentiero/web/assets/dashboard-JFYNHZZV.js +0 -3
- data/lib/sentiero/web/assets/recorder-SLLXSUUX.js +0 -71
- data/lib/sentiero/web/assets/rrweb-player-cd435a95.js +0 -126
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-leading:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono:ui-monospace, "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", Menlo, monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-blue-50:oklch(97% .014 254.604);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-900:oklch(35.9% .144 278.697);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wider:.05em;--leading-normal:1.5;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-surface-subtle:#f8f9fb;--color-border:#e2e5ea;--color-border-light:#eef0f3;--color-primary:#2563eb;--color-primary-hover:#1d4ed8;--color-primary-light:#eff6ff;--color-success:#059669;--color-success-bg:#ecfdf5;--color-warning:#d97706;--color-warning-bg:#fffbeb;--color-danger:#dc2626;--color-danger-bg:#fef2f2;--color-danger-hover:#b91c1c;--color-info:#0891b2;--color-info-bg:#ecfeff;--sidebar-w:220px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{--tw-leading:var(--leading-normal);font-size:13px;line-height:var(--leading-normal);color:var(--color-gray-900);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:var(--font-sans);background:var(--color-surface-subtle);margin:0}*,:before,:after{border-color:var(--color-border)}a{color:var(--color-primary);text-decoration-line:none}a:hover{color:var(--color-primary-hover)}h1,h2,h3,h4,h5,h6{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}}@layer components{.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1;--tw-font-weight:var(--font-weight-medium);line-height:1;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);border-width:1px;border-color:#0000;text-decoration-line:none;transition:all .12s;display:inline-flex;box-shadow:0 1px 2px #0000000d}.btn:focus-visible{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);outline-style:var(--tw-outline-style);outline-offset:1px;outline-width:2px;outline-color:var(--color-primary)}.btn-sm{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:11px}.btn-xs{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:11px}.btn-primary{color:var(--color-white);background:var(--color-gray-900);border-color:var(--color-gray-900)}.btn-primary:hover{color:var(--color-white);background:var(--color-gray-800);border-color:var(--color-gray-800)}.btn-secondary{background-color:var(--color-white);color:var(--color-gray-600);border-color:var(--color-border);box-shadow:0 1px 2px #0000000a}.btn-secondary:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.btn-danger{color:var(--color-white);background:var(--color-danger);border-color:var(--color-danger)}.btn-danger:hover{color:var(--color-white);background:var(--color-danger-hover);border-color:var(--color-danger-hover)}.btn-ghost-danger{color:var(--color-gray-400);border-color:var(--color-border);box-shadow:none;background-color:#0000}.btn-ghost-danger:hover{color:var(--color-danger);background:var(--color-danger-bg);border-color:var(--color-danger)}.btn-active{color:var(--color-white);background:var(--color-primary);border-color:var(--color-primary)}.btn-active:hover{color:var(--color-white);background:var(--color-primary-hover);border-color:var(--color-primary-hover)}.input,.select{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-900);font-family:var(--font-sans);border:1px solid var(--color-border);transition:border-color .12s,box-shadow .12s;box-shadow:0 1px 2px #0000000a}.select{appearance:none;padding-right:calc(var(--spacing) * 7);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");background-position:right .4rem center;background-repeat:no-repeat;background-size:.65rem}.input:focus,.select:focus{--tw-outline-style:none;border-color:var(--color-primary);outline-style:none;box-shadow:0 0 0 3px #2563eb14}.input::placeholder{color:var(--color-gray-400)}.label{margin-bottom:calc(var(--spacing) * 1);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-400);text-transform:uppercase;display:block}.checkbox{height:calc(var(--spacing) * 3.5);width:calc(var(--spacing) * 3.5);cursor:pointer;border:1px solid var(--color-gray-300);accent-color:var(--color-primary);border-radius:.25rem}.badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-medium);font-size:10px;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);letter-spacing:.01em;border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-neutral{background-color:var(--color-gray-100);color:var(--color-gray-500)}.badge-primary{background:var(--color-primary-light);color:var(--color-primary)}.badge-warning{background:var(--color-warning-bg);color:var(--color-warning)}.badge-danger{background:var(--color-danger-bg);color:var(--color-danger)}.badge-success{background:var(--color-success-bg);color:var(--color-success)}.badge-info{background:var(--color-info-bg);color:var(--color-info)}.card{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.card-body{padding:calc(var(--spacing) * 5)}.data-table{text-align:left;width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-collapse:separate;border-spacing:0}.data-table th{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase;background:var(--color-surface-subtle);border-bottom:1px solid var(--color-border)}.data-table td{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);vertical-align:middle;color:var(--color-gray-600);border-bottom:1px solid var(--color-border-light)}.data-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.data-table tbody tr:hover td{background-color:#eff6ff66}@supports (color:color-mix(in lab, red, red)){.data-table tbody tr:hover td{background-color:color-mix(in oklab, var(--color-blue-50) 40%, transparent)}}.table-wrap{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.pagination{margin:calc(var(--spacing) * 0);gap:calc(var(--spacing) * 1);padding:calc(var(--spacing) * 0);list-style-type:none;display:flex}.page-link{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border:1px solid var(--color-border);text-decoration-line:none;display:block}.page-link:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.page-disabled .page-link{pointer-events:none;background-color:var(--color-gray-50);color:var(--color-gray-300)}.alert-error{margin-bottom:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-danger-bg);color:var(--color-danger);border:1px solid #fecaca}.tabs{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 6);border-bottom:1px solid var(--color-border);display:flex}.tab{align-items:center;gap:calc(var(--spacing) * 1.5);padding-bottom:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--color-gray-500);border-bottom:2px solid #0000;margin-bottom:-1px;text-decoration-line:none;transition:color .12s,border-color .12s;display:inline-flex}.tab:hover{color:var(--color-gray-800);border-bottom-color:var(--color-gray-300)}.tab-active,.tab-active:hover{color:var(--color-primary);border-bottom-color:var(--color-primary)}.tab-count{padding-inline:calc(var(--spacing) * 1.5);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);background:var(--color-gray-100);color:var(--color-gray-500);border-radius:3.40282e38px}.tab-active .tab-count{background:var(--color-primary-light);color:var(--color-primary)}.banner-warning{margin-bottom:calc(var(--spacing) * 4);align-items:flex-start;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-warning-bg);color:var(--color-warning);border:1px solid #fde68a;border-left:3px solid var(--color-warning);display:flex}.banner-warning strong{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}}@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline\!{display:inline!important}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.w-3{width:calc(var(--spacing) * 3)}.w-8{width:calc(var(--spacing) * 8)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.w-px{width:1px}.max-w-0{max-width:calc(var(--spacing) * 0)}.max-w-full{max-width:100%}.max-w-xs{max-width:var(--container-xs)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[100px_1fr\]{grid-template-columns:100px 1fr}.grid-cols-\[140px_1fr\]{grid-template-columns:140px 1fr}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-red-400{border-color:var(--color-red-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-red-50{background-color:var(--color-red-50)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-700{color:var(--color-indigo-700)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-indigo-900:hover{color:var(--color-indigo-900)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-\[1fr_280px\]{grid-template-columns:1fr 280px}}}.s-layout{min-height:100vh;display:flex}.s-sidebar{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);z-index:50;background-color:var(--color-white);padding-block:calc(var(--spacing) * 4);width:var(--sidebar-w);border-right:1px solid var(--color-border);flex-direction:column;display:flex;position:fixed}.s-sidebar-brand{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900);text-decoration-line:none;display:flex}.s-sidebar-brand:hover{color:var(--color-gray-900)}.s-sidebar-brand-logo{height:calc(var(--spacing) * 7);width:calc(var(--spacing) * 7);border-radius:var(--radius-md);flex-shrink:0}.s-sidebar-nav{margin:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 2.5);list-style-type:none}.s-nav-item{margin-bottom:calc(var(--spacing) * .5);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);padding-block:7px;text-decoration-line:none;transition:all .12s;display:flex}.s-nav-item:hover{background-color:var(--color-gray-100);color:var(--color-gray-800)}.s-nav-item.active{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background:var(--color-primary-light);color:var(--color-primary)}.s-nav-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.6;flex-shrink:0}.s-nav-item.active .s-nav-icon{opacity:1}.s-main{padding-inline:calc(var(--spacing) * 8);padding-block:calc(var(--spacing) * 6);margin-left:var(--sidebar-w);max-width:calc(1400px + var(--sidebar-w));flex:1}.s-mobile-header{background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;display:none}.page-title{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}.session-id{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);font-family:var(--font-mono)}.metadata-url{text-overflow:ellipsis;white-space:nowrap;max-width:200px;overflow:hidden}.window-switcher{align-items:center;gap:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 2);display:flex}.window-switcher-label{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase}.window-overflow{display:inline-block;position:relative}.window-overflow summary{cursor:pointer;list-style-type:none}.window-overflow summary::-webkit-details-marker{display:none}.window-overflow-menu{top:100%;left:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 1);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);border:1px solid var(--color-border);position:absolute;box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.window-overflow-menu a{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-600);text-decoration-line:none;display:block}.window-overflow-menu a:hover{background-color:var(--color-gray-50)}.window-overflow-menu a.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light);color:var(--color-primary)}.player-frame{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.player-frame .rr-player{--tw-shadow:0 0 #0000;max-width:100%;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:0}.player-frame .rr-controller{background:var(--color-surface-subtle);border-top:1px solid var(--color-border-light);border-radius:0}#replayer{justify-content:center;display:flex;overflow:hidden}.click-overlay-container{pointer-events:none;inset:calc(var(--spacing) * 0);z-index:20;position:absolute}.click-dot{pointer-events:none;background:#dc35458c;border:1px solid #dc3545;border-radius:3.40282e38px;width:12px;height:12px;transition:transform .12s,background .12s;position:absolute;box-shadow:0 0 0 2px #dc354533}.click-dot:hover,.click-dot.active{background:#dc3545d9;transform:scale(1.4)}#event-markers{padding-inline:calc(var(--spacing) * 3);padding-top:calc(var(--spacing) * 2)}.marker-legend{margin-bottom:calc(var(--spacing) * 1);gap:calc(var(--spacing) * 3);color:var(--color-gray-400);font-size:10px;display:flex}.marker-legend-item{align-items:center;gap:calc(var(--spacing) * 1);display:flex}.marker-legend-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.marker-bar{margin-bottom:calc(var(--spacing) * 2);height:calc(var(--spacing) * 8);cursor:pointer;border-radius:var(--radius-md);background:var(--color-surface-subtle);border:1px solid var(--color-border);box-sizing:border-box;max-width:100%;position:relative;overflow:visible}.marker-track{pointer-events:none;right:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2);background:var(--color-border);border-radius:3.40282e38px;height:2px;position:absolute;top:50%;transform:translateY(-50%)}.marker-playhead{pointer-events:none;top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:10;background:var(--color-primary);border-radius:1px;width:2px;transition:left .15s linear;position:absolute;box-shadow:0 0 4px #2563eb4d}.marker-playhead:before{content:"";background:var(--color-primary);border-radius:50%;width:8px;height:8px;display:block;position:absolute;top:-3px;left:50%;transform:translate(-50%);box-shadow:0 0 0 2px #fff,0 1px 3px #00000026}.event-marker{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);cursor:pointer;opacity:.7;border-radius:3.40282e38px;transition:opacity .12s,transform .12s;position:absolute;top:50%;transform:translate(-50%,-50%)}.event-marker:hover{z-index:1;opacity:1;transform:translate(-50%,-50%)scale(1.6)}.event-marker.error-marker{z-index:2;height:calc(var(--spacing) * 2.5);width:calc(var(--spacing) * 2.5);opacity:1;box-shadow:0 0 4px #dc262666}.marker-gap{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:0;cursor:help;border-left:1px solid var(--color-gray-300);border-right:1px solid var(--color-gray-300);background:repeating-linear-gradient(-45deg, transparent, transparent 3px, var(--color-border) 3px, var(--color-border) 5px);position:absolute}.marker-group{z-index:3;cursor:pointer;position:absolute;top:50%;transform:translate(-50%,-50%)}.marker-group .event-marker{display:block;position:static;transform:none}.marker-group:hover .event-marker{transform:scale(1.4)}.marker-group-dot{opacity:.9;width:14px!important;height:14px!important}.marker-group-count{pointer-events:none;background-color:var(--color-gray-700);text-align:center;--tw-font-weight:var(--font-weight-bold);font-size:9px;font-weight:var(--font-weight-bold);color:var(--color-white);border-radius:3.40282e38px;min-width:14px;padding-inline:3px;line-height:14px;position:absolute;top:-8px;left:50%;transform:translate(-50%)}.marker-dropdown{z-index:50;max-width:var(--container-xs);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);white-space:nowrap;border:1px solid var(--color-border);position:absolute;top:22px;left:50%;transform:translate(-50%);box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.marker-dropdown-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);color:var(--color-gray-600);font-size:11px;display:flex}.marker-dropdown-item:hover{background-color:var(--color-gray-50)}.marker-dropdown-item.error-entry{background:var(--color-danger-bg)}.marker-dropdown-item.error-entry:hover{background:#fee2e2}#activity-sidebar{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);max-height:600px;padding:calc(var(--spacing) * 3);border:1px solid var(--color-border);overflow-y:auto;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}#activity-sidebar h6{margin-bottom:calc(var(--spacing) * 2);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.activity-entry{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-bottom:1px solid var(--color-border-light);transition:background 80ms;display:flex}.activity-entry:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.activity-entry:hover{background-color:var(--color-gray-50)}.activity-entry.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light)}.activity-entry.error-entry{background:var(--color-danger-bg);border-left:3px solid var(--color-danger)}.activity-entry.navigation-entry{background:var(--color-success-bg);border-left:3px solid var(--color-success)}.activity-entry.frustration-entry{background:#ff6b6b1a;border-left:3px solid #ff6b6b}.activity-entry.server-exception-entry{background:#b91c1c14;border-left:3px solid #b91c1c}.activity-entry.server-event-entry{background:#7c3aed14;border-left:3px solid #7c3aed}.activity-label-link{color:var(--color-blue-600);text-decoration-line:none}@media (hover:hover){.activity-label-link:hover{text-decoration-line:underline}}.activity-time{min-width:calc(var(--spacing) * 10);white-space:nowrap;color:var(--color-gray-400);font-size:10px;font-family:var(--font-mono)}.activity-dot{height:calc(var(--spacing) * 1.5);width:calc(var(--spacing) * 1.5);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.activity-label{text-overflow:ellipsis;white-space:nowrap;color:var(--color-gray-600);overflow:hidden}.activity-gap{margin-block:calc(var(--spacing) * .5);padding-block:calc(var(--spacing) * .5);text-align:center;color:var(--color-gray-400);border-top:1px dashed var(--color-border-light);border-bottom:1px dashed var(--color-border-light);background:repeating-linear-gradient(-45deg, transparent, transparent 4px, var(--color-surface-subtle) 4px, var(--color-surface-subtle) 6px);font-size:10px}.activity-wrapper{position:relative}.activity-detail{margin-left:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);overflow-wrap:break-word;color:var(--color-gray-500);background:var(--color-surface-subtle);border-left:2px solid var(--color-border);border-radius:0 6px 6px 0;font-size:11px;display:none}.activity-wrapper:hover .activity-detail{display:block}.activity-detail-line{margin-bottom:calc(var(--spacing) * .5)}.activity-detail-line:last-child{margin-bottom:calc(var(--spacing) * 0)}.activity-detail-pre{margin-top:calc(var(--spacing) * .5);background-color:var(--color-gray-100);max-height:120px;padding:calc(var(--spacing) * 1.5);word-break:break-all;white-space:pre-wrap;font-size:10px;font-family:var(--font-mono);border-radius:.25rem;overflow-y:auto}.stats-chart{margin-top:calc(var(--spacing) * 3);align-items:flex-end;gap:calc(var(--spacing) * 1);height:180px;padding-top:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 2);border-bottom:1px solid var(--color-border-light);display:flex}.stats-chart-col{min-width:calc(var(--spacing) * 0);flex-direction:column;flex:1;align-items:center;display:flex}.stats-chart-bar-wrapper{flex:1;justify-content:center;align-items:flex-end;width:100%;height:140px;display:flex}.stats-chart-bar{background:var(--color-primary);border-radius:4px 4px 0 0;width:60%;max-width:36px;min-height:2px;transition:height .3s,background .12s;position:relative}.stats-chart-bar:hover{background:var(--color-primary-hover)}.stats-chart-value{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);white-space:nowrap;color:var(--color-gray-500);position:absolute;top:-16px;left:50%;transform:translate(-50%)}.stats-chart-label{margin-top:calc(var(--spacing) * 1);white-space:nowrap;color:var(--color-gray-400);font-size:10px}@media (max-width:768px){.s-sidebar{display:none}.s-mobile-header{display:flex}.s-main{margin-left:calc(var(--spacing) * 0);padding:calc(var(--spacing) * 4)}}details[open]>summary .group-open\:rotate-90{transform:rotate(90deg)}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-leading:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono:ui-monospace, "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", Menlo, monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-blue-50:oklch(97% .014 254.604);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-900:oklch(35.9% .144 278.697);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wider:.05em;--leading-normal:1.5;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-surface-subtle:#f8f9fb;--color-border:#e2e5ea;--color-border-light:#eef0f3;--color-primary:#2563eb;--color-primary-hover:#1d4ed8;--color-primary-light:#eff6ff;--color-success:#059669;--color-success-bg:#ecfdf5;--color-warning:#d97706;--color-warning-bg:#fffbeb;--color-danger:#dc2626;--color-danger-bg:#fef2f2;--color-danger-hover:#b91c1c;--color-info:#0891b2;--color-info-bg:#ecfeff;--sidebar-w:220px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{--tw-leading:var(--leading-normal);font-size:13px;line-height:var(--leading-normal);color:var(--color-gray-900);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:var(--font-sans);background:var(--color-surface-subtle);margin:0}*,:before,:after{border-color:var(--color-border)}a{color:var(--color-primary);text-decoration-line:none}a:hover{color:var(--color-primary-hover)}h1,h2,h3,h4,h5,h6{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}}@layer components{.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1;--tw-font-weight:var(--font-weight-medium);line-height:1;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);border-width:1px;border-color:#0000;text-decoration-line:none;transition:all .12s;display:inline-flex;box-shadow:0 1px 2px #0000000d}.btn:focus-visible{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);outline-style:var(--tw-outline-style);outline-offset:1px;outline-width:2px;outline-color:var(--color-primary)}.btn-sm{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:11px}.btn-xs{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:11px}.btn-primary{color:var(--color-white);background:var(--color-gray-900);border-color:var(--color-gray-900)}.btn-primary:hover{color:var(--color-white);background:var(--color-gray-800);border-color:var(--color-gray-800)}.btn-secondary{background-color:var(--color-white);color:var(--color-gray-600);border-color:var(--color-border);box-shadow:0 1px 2px #0000000a}.btn-secondary:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.btn-danger{color:var(--color-white);background:var(--color-danger);border-color:var(--color-danger)}.btn-danger:hover{color:var(--color-white);background:var(--color-danger-hover);border-color:var(--color-danger-hover)}.btn-ghost-danger{color:var(--color-gray-400);border-color:var(--color-border);box-shadow:none;background-color:#0000}.btn-ghost-danger:hover{color:var(--color-danger);background:var(--color-danger-bg);border-color:var(--color-danger)}.btn-active{color:var(--color-white);background:var(--color-primary);border-color:var(--color-primary)}.btn-active:hover{color:var(--color-white);background:var(--color-primary-hover);border-color:var(--color-primary-hover)}.input,.select{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-900);font-family:var(--font-sans);border:1px solid var(--color-border);transition:border-color .12s,box-shadow .12s;box-shadow:0 1px 2px #0000000a}.select{appearance:none;padding-right:calc(var(--spacing) * 7);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");background-position:right .4rem center;background-repeat:no-repeat;background-size:.65rem}.input:focus,.select:focus{--tw-outline-style:none;border-color:var(--color-primary);outline-style:none;box-shadow:0 0 0 3px #2563eb14}.input::placeholder{color:var(--color-gray-400)}.label{margin-bottom:calc(var(--spacing) * 1);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-400);text-transform:uppercase;display:block}.checkbox{height:calc(var(--spacing) * 3.5);width:calc(var(--spacing) * 3.5);cursor:pointer;border:1px solid var(--color-gray-300);accent-color:var(--color-primary);border-radius:.25rem}.badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-medium);font-size:10px;font-weight:var(--font-weight-medium);white-space:nowrap;font-family:var(--font-sans);letter-spacing:.01em;border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-neutral{background-color:var(--color-gray-100);color:var(--color-gray-500)}.badge-primary{background:var(--color-primary-light);color:var(--color-primary)}.badge-warning{background:var(--color-warning-bg);color:var(--color-warning)}.badge-danger{background:var(--color-danger-bg);color:var(--color-danger)}.badge-success{background:var(--color-success-bg);color:var(--color-success)}.badge-info{background:var(--color-info-bg);color:var(--color-info)}.card{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.card-body{padding:calc(var(--spacing) * 5)}.data-table{text-align:left;width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-collapse:separate;border-spacing:0}.data-table th{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase;background:var(--color-surface-subtle);border-bottom:1px solid var(--color-border)}.data-table td{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);vertical-align:middle;color:var(--color-gray-600);border-bottom:1px solid var(--color-border-light)}.data-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.data-table tbody tr:hover td{background-color:#eff6ff66}@supports (color:color-mix(in lab, red, red)){.data-table tbody tr:hover td{background-color:color-mix(in oklab, var(--color-blue-50) 40%, transparent)}}.table-wrap{border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.pagination{margin:calc(var(--spacing) * 0);gap:calc(var(--spacing) * 1);padding:calc(var(--spacing) * 0);list-style-type:none;display:flex}.page-link{border-radius:var(--radius-md);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border:1px solid var(--color-border);text-decoration-line:none;display:block}.page-link:hover{background-color:var(--color-gray-50);color:var(--color-gray-800);border-color:var(--color-gray-300)}.page-disabled .page-link{pointer-events:none;background-color:var(--color-gray-50);color:var(--color-gray-300)}.alert-error{margin-bottom:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-danger-bg);color:var(--color-danger);border:1px solid #fecaca}.tabs{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 6);border-bottom:1px solid var(--color-border);display:flex}.tab{align-items:center;gap:calc(var(--spacing) * 1.5);padding-bottom:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--color-gray-500);border-bottom:2px solid #0000;margin-bottom:-1px;text-decoration-line:none;transition:color .12s,border-color .12s;display:inline-flex}.tab:hover{color:var(--color-gray-800);border-bottom-color:var(--color-gray-300)}.tab-active,.tab-active:hover{color:var(--color-primary);border-bottom-color:var(--color-primary)}.tab-count{padding-inline:calc(var(--spacing) * 1.5);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);background:var(--color-gray-100);color:var(--color-gray-500);border-radius:3.40282e38px}.tab-active .tab-count{background:var(--color-primary-light);color:var(--color-primary)}.banner-warning{margin-bottom:calc(var(--spacing) * 4);align-items:flex-start;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));background:var(--color-warning-bg);color:var(--color-warning);border:1px solid #fde68a;border-left:3px solid var(--color-warning);display:flex}.banner-warning strong{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}}@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline\!{display:inline!important}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.w-3{width:calc(var(--spacing) * 3)}.w-8{width:calc(var(--spacing) * 8)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.w-px{width:1px}.max-w-0{max-width:calc(var(--spacing) * 0)}.max-w-full{max-width:100%}.max-w-xs{max-width:var(--container-xs)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[100px_1fr\]{grid-template-columns:100px 1fr}.grid-cols-\[140px_1fr\]{grid-template-columns:140px 1fr}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-end{align-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-400{border-color:var(--color-blue-400)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-red-400{border-color:var(--color-red-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-red-50{background-color:var(--color-red-50)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-700{color:var(--color-indigo-700)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media (hover:hover){.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-indigo-900:hover{color:var(--color-indigo-900)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-\[1fr_280px\]{grid-template-columns:1fr 280px}}}.s-layout{min-height:100vh;display:flex}.s-sidebar{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);z-index:50;background-color:var(--color-white);padding-block:calc(var(--spacing) * 4);width:var(--sidebar-w);border-right:1px solid var(--color-border);flex-direction:column;display:flex;position:fixed}.s-sidebar-brand{margin-bottom:calc(var(--spacing) * 5);align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900);text-decoration-line:none;display:flex}.s-sidebar-brand:hover{color:var(--color-gray-900)}.s-sidebar-brand-logo{height:calc(var(--spacing) * 7);width:calc(var(--spacing) * 7);border-radius:var(--radius-md);flex-shrink:0}.s-sidebar-nav{margin:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 2.5);list-style-type:none}.s-nav-item{margin-bottom:calc(var(--spacing) * .5);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);padding-block:7px;text-decoration-line:none;transition:all .12s;display:flex}.s-nav-item:hover{background-color:var(--color-gray-100);color:var(--color-gray-800)}.s-nav-item.active{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background:var(--color-primary-light);color:var(--color-primary)}.s-nav-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.6;flex-shrink:0}.s-nav-item.active .s-nav-icon{opacity:1}.s-main{padding-inline:calc(var(--spacing) * 8);padding-block:calc(var(--spacing) * 6);margin-left:var(--sidebar-w);max-width:calc(1400px + var(--sidebar-w));flex:1}.s-mobile-header{background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-bottom:1px solid var(--color-border);justify-content:space-between;align-items:center;display:none}.page-title{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--color-gray-900)}.session-id{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);font-family:var(--font-mono)}.metadata-url{text-overflow:ellipsis;white-space:nowrap;max-width:200px;overflow:hidden}.window-switcher{align-items:center;gap:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 2);display:flex}.window-switcher-label{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);white-space:nowrap;color:var(--color-gray-400);text-transform:uppercase}.window-overflow{display:inline-block;position:relative}.window-overflow summary{cursor:pointer;list-style-type:none}.window-overflow summary::-webkit-details-marker{display:none}.window-overflow-menu{top:100%;left:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 1);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);border:1px solid var(--color-border);position:absolute;box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.window-overflow-menu a{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-600);text-decoration-line:none;display:block}.window-overflow-menu a:hover{background-color:var(--color-gray-50)}.window-overflow-menu a.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light);color:var(--color-primary)}.player-frame{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);border:1px solid var(--color-border);overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}.player-frame .rr-player{--tw-shadow:0 0 #0000;max-width:100%;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:0}.player-frame .rr-controller{background:var(--color-surface-subtle);border-top:1px solid var(--color-border-light);border-radius:0}#replayer{justify-content:center;display:flex;overflow:hidden}.click-overlay-container{pointer-events:none;inset:calc(var(--spacing) * 0);z-index:20;position:absolute}.click-dot{pointer-events:none;background:#dc35458c;border:1px solid #dc3545;border-radius:3.40282e38px;width:12px;height:12px;transition:transform .12s,background .12s;position:absolute;box-shadow:0 0 0 2px #dc354533}.click-dot:hover,.click-dot.active{background:#dc3545d9;transform:scale(1.4)}#event-markers{padding-inline:calc(var(--spacing) * 3);padding-top:calc(var(--spacing) * 2)}.marker-legend{margin-bottom:calc(var(--spacing) * 1);gap:calc(var(--spacing) * 3);color:var(--color-gray-400);font-size:10px;display:flex}.marker-legend-item{align-items:center;gap:calc(var(--spacing) * 1);display:flex}.marker-legend-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.marker-bar{margin-bottom:calc(var(--spacing) * 2);height:calc(var(--spacing) * 8);cursor:pointer;border-radius:var(--radius-md);background:var(--color-surface-subtle);border:1px solid var(--color-border);box-sizing:border-box;max-width:100%;position:relative;overflow:visible}.marker-track{pointer-events:none;right:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2);background:var(--color-border);border-radius:3.40282e38px;height:2px;position:absolute;top:50%;transform:translateY(-50%)}.marker-playhead{pointer-events:none;top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:10;background:var(--color-primary);border-radius:1px;width:2px;transition:left .15s linear;position:absolute;box-shadow:0 0 4px #2563eb4d}.marker-playhead:before{content:"";background:var(--color-primary);border-radius:50%;width:8px;height:8px;display:block;position:absolute;top:-3px;left:50%;transform:translate(-50%);box-shadow:0 0 0 2px #fff,0 1px 3px #00000026}.event-marker{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);cursor:pointer;opacity:.7;border-radius:3.40282e38px;transition:opacity .12s,transform .12s;position:absolute;top:50%;transform:translate(-50%,-50%)}.event-marker:hover{z-index:1;opacity:1;transform:translate(-50%,-50%)scale(1.6)}.event-marker.error-marker{z-index:2;height:calc(var(--spacing) * 2.5);width:calc(var(--spacing) * 2.5);opacity:1;box-shadow:0 0 4px #dc262666}.marker-gap{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:0;cursor:help;border-left:1px solid var(--color-gray-300);border-right:1px solid var(--color-gray-300);background:repeating-linear-gradient(-45deg, transparent, transparent 3px, var(--color-border) 3px, var(--color-border) 5px);position:absolute}.marker-group{z-index:3;cursor:pointer;position:absolute;top:50%;transform:translate(-50%,-50%)}.marker-group .event-marker{display:block;position:static;transform:none}.marker-group:hover .event-marker{transform:scale(1.4)}.marker-group-dot{opacity:.9;width:14px!important;height:14px!important}.marker-group-count{pointer-events:none;background-color:var(--color-gray-700);text-align:center;--tw-font-weight:var(--font-weight-bold);font-size:9px;font-weight:var(--font-weight-bold);color:var(--color-white);border-radius:3.40282e38px;min-width:14px;padding-inline:3px;line-height:14px;position:absolute;top:-8px;left:50%;transform:translate(-50%)}.marker-dropdown{z-index:50;max-width:var(--container-xs);border-radius:var(--radius-lg);background-color:var(--color-white);min-width:200px;padding:calc(var(--spacing) * 1);white-space:nowrap;border:1px solid var(--color-border);position:absolute;top:22px;left:50%;transform:translate(-50%);box-shadow:0 8px 24px #0000001f,0 2px 6px #0000000a}.marker-dropdown-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);color:var(--color-gray-600);font-size:11px;display:flex}.marker-dropdown-item:hover{background-color:var(--color-gray-50)}.marker-dropdown-item.error-entry{background:var(--color-danger-bg)}.marker-dropdown-item.error-entry:hover{background:#fee2e2}#activity-sidebar{margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-lg);background-color:var(--color-white);max-height:600px;padding:calc(var(--spacing) * 3);border:1px solid var(--color-border);overflow-y:auto;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005}#activity-sidebar h6{margin-bottom:calc(var(--spacing) * 2);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.activity-entry{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));border-bottom:1px solid var(--color-border-light);transition:background 80ms;display:flex}.activity-entry:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.activity-entry:hover{background-color:var(--color-gray-50)}.activity-entry.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background:var(--color-primary-light)}.activity-entry.error-entry{background:var(--color-danger-bg);border-left:3px solid var(--color-danger)}.activity-entry.navigation-entry{background:var(--color-success-bg);border-left:3px solid var(--color-success)}.activity-entry.frustration-entry{background:#ff6b6b1a;border-left:3px solid #ff6b6b}.activity-entry.server-exception-entry{background:#b91c1c14;border-left:3px solid #b91c1c}.activity-entry.server-event-entry{background:#7c3aed14;border-left:3px solid #7c3aed}.activity-label-link{color:var(--color-blue-600);text-decoration-line:none}@media (hover:hover){.activity-label-link:hover{text-decoration-line:underline}}.activity-time{min-width:calc(var(--spacing) * 10);white-space:nowrap;color:var(--color-gray-400);font-size:10px;font-family:var(--font-mono)}.activity-dot{height:calc(var(--spacing) * 1.5);width:calc(var(--spacing) * 1.5);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.activity-label{text-overflow:ellipsis;white-space:nowrap;color:var(--color-gray-600);overflow:hidden}.activity-gap{margin-block:calc(var(--spacing) * .5);padding-block:calc(var(--spacing) * .5);text-align:center;color:var(--color-gray-400);border-top:1px dashed var(--color-border-light);border-bottom:1px dashed var(--color-border-light);background:repeating-linear-gradient(-45deg, transparent, transparent 4px, var(--color-surface-subtle) 4px, var(--color-surface-subtle) 6px);font-size:10px}.activity-wrapper{position:relative}.activity-detail{margin-left:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1.5);overflow-wrap:break-word;color:var(--color-gray-500);background:var(--color-surface-subtle);border-left:2px solid var(--color-border);border-radius:0 6px 6px 0;font-size:11px;display:none}.activity-wrapper:hover .activity-detail{display:block}.activity-detail-line{margin-bottom:calc(var(--spacing) * .5)}.activity-detail-line:last-child{margin-bottom:calc(var(--spacing) * 0)}.activity-detail-pre{margin-top:calc(var(--spacing) * .5);background-color:var(--color-gray-100);max-height:120px;padding:calc(var(--spacing) * 1.5);word-break:break-all;white-space:pre-wrap;font-size:10px;font-family:var(--font-mono);border-radius:.25rem;overflow-y:auto}.stats-chart{margin-top:calc(var(--spacing) * 3);align-items:flex-end;gap:calc(var(--spacing) * 1);height:180px;padding-top:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 2);border-bottom:1px solid var(--color-border-light);display:flex}.stats-chart-col{min-width:calc(var(--spacing) * 0);flex-direction:column;flex:1;align-items:center;display:flex}.stats-chart-bar-wrapper{justify-content:center;align-items:flex-end;width:100%;height:140px;display:flex}.stats-chart-bar{background:var(--color-primary);border-radius:4px 4px 0 0;width:60%;max-width:36px;min-height:2px;transition:height .3s,background .12s;position:relative}.stats-chart-bar:hover{background:var(--color-primary-hover)}.stats-chart-value{--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);white-space:nowrap;color:var(--color-gray-500);position:absolute;top:-16px;left:50%;transform:translate(-50%)}.stats-chart-label{margin-top:calc(var(--spacing) * 1);white-space:nowrap;color:var(--color-gray-400);font-size:10px}@media (max-width:768px){.s-layout{flex-direction:column}.s-sidebar{display:none}.s-mobile-header{gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.s-mobile-header nav{max-width:100%;overflow-x:auto}.s-main{margin-left:calc(var(--spacing) * 0);min-width:calc(var(--spacing) * 0);padding:calc(var(--spacing) * 4)}.tabs{overflow-x:auto}.s-main table{white-space:nowrap;max-width:100%;display:block;overflow-x:auto}.s-main form,.s-main .flex:has(>.shrink-0){flex-wrap:wrap}.s-main form .shrink-0{width:100%}.s-main .input[style],.s-main .select[style]{width:100%!important}}details[open]>summary .group-open\:rotate-90{transform:rotate(90deg)}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}
|
|
@@ -5,10 +5,11 @@ require "zlib"
|
|
|
5
5
|
|
|
6
6
|
module Sentiero
|
|
7
7
|
module Web
|
|
8
|
-
# Reads a request body (optionally gzip-encoded) with a hard
|
|
9
|
-
# BOTH the compressed and decompressed
|
|
10
|
-
# it. Shared by the two
|
|
11
|
-
#
|
|
8
|
+
# Reads a request body (optionally gzip-encoded) with a hard cap (default
|
|
9
|
+
# 512KB, config.max_body_size) on BOTH the compressed and decompressed
|
|
10
|
+
# size, so a gzip bomb can't blow past it. Shared by the two
|
|
11
|
+
# untrusted-input lanes (EventsApp, IngestApp), which differ only in the
|
|
12
|
+
# response they build from the error symbol.
|
|
12
13
|
module BodyReader
|
|
13
14
|
MAX_BODY_SIZE = 524_288 # 512 KB
|
|
14
15
|
|
|
@@ -21,24 +22,34 @@ module Sentiero
|
|
|
21
22
|
module_function
|
|
22
23
|
|
|
23
24
|
# [body, nil] on success, or [nil, :too_large | :bad_gzip] on failure.
|
|
24
|
-
def read(env)
|
|
25
|
-
raw = env["rack.input"].read(
|
|
25
|
+
def read(env, max_bytes: MAX_BODY_SIZE)
|
|
26
|
+
raw = env["rack.input"].read(max_bytes + 1) || ""
|
|
26
27
|
env["rack.input"].rewind if env["rack.input"].respond_to?(:rewind)
|
|
27
|
-
return [nil, :too_large] if raw.bytesize >
|
|
28
|
+
return [nil, :too_large] if raw.bytesize > max_bytes
|
|
28
29
|
|
|
29
|
-
if env
|
|
30
|
+
if gzip?(env, raw)
|
|
30
31
|
begin
|
|
31
32
|
gz = Zlib::GzipReader.new(StringIO.new(raw))
|
|
32
|
-
raw = gz.read(
|
|
33
|
+
raw = gz.read(max_bytes + 1) || ""
|
|
33
34
|
gz.close
|
|
34
35
|
rescue Zlib::GzipFile::Error
|
|
35
36
|
return [nil, :bad_gzip]
|
|
36
37
|
end
|
|
37
|
-
return [nil, :too_large] if raw.bytesize >
|
|
38
|
+
return [nil, :too_large] if raw.bytesize > max_bytes
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
[raw, nil]
|
|
41
42
|
end
|
|
43
|
+
|
|
44
|
+
# Decompress when the client declared gzip or the body starts with the
|
|
45
|
+
# gzip magic number. The magic-byte path lets unload beacons ship
|
|
46
|
+
# compressed as text/plain without a Content-Encoding header, which would
|
|
47
|
+
# otherwise force a CORS preflight sendBeacon can't perform. JSON never
|
|
48
|
+
# begins with these bytes, so detection is unambiguous.
|
|
49
|
+
def gzip?(env, raw)
|
|
50
|
+
env["HTTP_CONTENT_ENCODING"]&.downcase == "gzip" ||
|
|
51
|
+
(raw.getbyte(0) == 0x1f && raw.getbyte(1) == 0x8b)
|
|
52
|
+
end
|
|
42
53
|
end
|
|
43
54
|
end
|
|
44
55
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "date"
|
|
3
4
|
require_relative "base_app"
|
|
4
5
|
require_relative "analytics_app"
|
|
5
6
|
require_relative "monitoring_app"
|
|
@@ -57,6 +58,12 @@ module Sentiero
|
|
|
57
58
|
else
|
|
58
59
|
not_found
|
|
59
60
|
end
|
|
61
|
+
when "/maintenance"
|
|
62
|
+
get_only(method) || handle_maintenance(env)
|
|
63
|
+
when "/maintenance/purge-before"
|
|
64
|
+
post_only(method) || handle_purge_before(env)
|
|
65
|
+
when "/maintenance/purge-expired"
|
|
66
|
+
post_only(method) || handle_purge_expired(env)
|
|
60
67
|
when %r{\A/custom-events(?:/.*)?\z}
|
|
61
68
|
Sentiero::Web::MonitoringApp.new.call(env)
|
|
62
69
|
when %r{\A/issues(?:/.*)?\z}
|
|
@@ -95,6 +102,8 @@ module Sentiero
|
|
|
95
102
|
search = params["search"]&.strip
|
|
96
103
|
search = nil if search&.empty?
|
|
97
104
|
has_errors_filter = params["has_errors"] == "true"
|
|
105
|
+
min_duration_s = positive_int_param(params["min_duration"])
|
|
106
|
+
min_events = positive_int_param(params["min_events"])
|
|
98
107
|
|
|
99
108
|
sessions = fetch_sessions(
|
|
100
109
|
has_errors_filter: has_errors_filter,
|
|
@@ -103,7 +112,9 @@ module Sentiero
|
|
|
103
112
|
since: since,
|
|
104
113
|
until_time: until_time,
|
|
105
114
|
sort_by: sort_by,
|
|
106
|
-
search: search
|
|
115
|
+
search: search,
|
|
116
|
+
min_duration_ms: min_duration_s && min_duration_s * 1000,
|
|
117
|
+
min_events: min_events
|
|
107
118
|
)
|
|
108
119
|
|
|
109
120
|
sessions, has_next = take_page(sessions, per_page)
|
|
@@ -119,11 +130,14 @@ module Sentiero
|
|
|
119
130
|
sort_by: sort_by || "updated_at",
|
|
120
131
|
since: params["since"] || "",
|
|
121
132
|
until_param: params["until"] || "",
|
|
122
|
-
has_errors: has_errors_filter
|
|
133
|
+
has_errors: has_errors_filter,
|
|
134
|
+
min_duration: min_duration_s&.to_s || "",
|
|
135
|
+
min_events: min_events&.to_s || ""
|
|
123
136
|
))
|
|
124
137
|
end
|
|
125
138
|
|
|
126
|
-
def fetch_sessions(has_errors_filter:, per_page:, offset:, since:, until_time:, sort_by:, search
|
|
139
|
+
def fetch_sessions(has_errors_filter:, per_page:, offset:, since:, until_time:, sort_by:, search:,
|
|
140
|
+
min_duration_ms: nil, min_events: nil)
|
|
127
141
|
unless has_errors_filter
|
|
128
142
|
return Sentiero.store.list_sessions(
|
|
129
143
|
limit: per_page + 1,
|
|
@@ -131,7 +145,9 @@ module Sentiero
|
|
|
131
145
|
since: since,
|
|
132
146
|
until_time: until_time,
|
|
133
147
|
sort_by: sort_by,
|
|
134
|
-
search: search
|
|
148
|
+
search: search,
|
|
149
|
+
min_duration_ms: min_duration_ms,
|
|
150
|
+
min_events: min_events
|
|
135
151
|
)
|
|
136
152
|
end
|
|
137
153
|
|
|
@@ -145,12 +161,21 @@ module Sentiero
|
|
|
145
161
|
since: since,
|
|
146
162
|
until_time: until_time,
|
|
147
163
|
sort_by: sort_by,
|
|
148
|
-
search: search
|
|
164
|
+
search: search,
|
|
165
|
+
min_duration_ms: min_duration_ms,
|
|
166
|
+
min_events: min_events
|
|
149
167
|
).select { |s| s[:metadata] && s[:metadata]["has_errors"] }
|
|
150
168
|
|
|
151
169
|
all_matching.slice(offset, per_page + 1) || []
|
|
152
170
|
end
|
|
153
171
|
|
|
172
|
+
def positive_int_param(value)
|
|
173
|
+
return nil unless value.is_a?(String) && value.match?(/\A\d+\z/)
|
|
174
|
+
|
|
175
|
+
n = value.to_i
|
|
176
|
+
(n > 0) ? n : nil
|
|
177
|
+
end
|
|
178
|
+
|
|
154
179
|
def handle_session_redirect(env, session_id)
|
|
155
180
|
session = Sentiero.store.get_session(session_id)
|
|
156
181
|
return [404, {"content-type" => "text/plain"}, ["Session not found"]] unless session
|
|
@@ -231,6 +256,53 @@ module Sentiero
|
|
|
231
256
|
redirect("#{base_path(env)}/")
|
|
232
257
|
end
|
|
233
258
|
end
|
|
259
|
+
|
|
260
|
+
def handle_maintenance(env)
|
|
261
|
+
params = query_params(env)
|
|
262
|
+
purged = params["purged"]&.then { |v| v.match?(/\A\d+\z/) ? v.to_i : nil }
|
|
263
|
+
render_page(env, Views::MaintenanceView.new(
|
|
264
|
+
retention_period: Sentiero.configuration.retention_period,
|
|
265
|
+
purged: purged,
|
|
266
|
+
error: params["error"]
|
|
267
|
+
), request_path: "/maintenance")
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def handle_purge_before(env)
|
|
271
|
+
verify_csrf(env) || begin
|
|
272
|
+
post = ::Rack::Request.new(env).POST
|
|
273
|
+
return redirect(maintenance_path(env, error: "Tick the confirmation checkbox to purge.")) unless post["confirm"] == "1"
|
|
274
|
+
|
|
275
|
+
begin
|
|
276
|
+
date = Date.iso8601(post["date"].to_s)
|
|
277
|
+
rescue ArgumentError
|
|
278
|
+
return redirect(maintenance_path(env, error: "Enter a valid date."))
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Inclusive end of the chosen day, UTC.
|
|
282
|
+
until_time = Time.utc(date.year, date.month, date.day) + 86_400 - 0.001
|
|
283
|
+
audit!(env, action: :erase_where, dataset: date.iso8601)
|
|
284
|
+
count = Sentiero.erase_where(until_time: until_time)
|
|
285
|
+
redirect(maintenance_path(env, purged: count))
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def handle_purge_expired(env)
|
|
290
|
+
verify_csrf(env) || begin
|
|
291
|
+
audit!(env, action: :purge)
|
|
292
|
+
count = Sentiero.purge_expired!
|
|
293
|
+
if count.nil?
|
|
294
|
+
redirect(maintenance_path(env, error: "retention_period is not set; nothing to purge."))
|
|
295
|
+
else
|
|
296
|
+
redirect(maintenance_path(env, purged: count))
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def maintenance_path(env, purged: nil, error: nil)
|
|
302
|
+
qs = ::Rack::Utils.build_query({"purged" => purged, "error" => error}.compact)
|
|
303
|
+
path = "#{base_path(env)}/maintenance"
|
|
304
|
+
qs.empty? ? path : "#{path}?#{qs}"
|
|
305
|
+
end
|
|
234
306
|
end
|
|
235
307
|
end
|
|
236
308
|
end
|
|
@@ -13,6 +13,7 @@ module Sentiero
|
|
|
13
13
|
MAX_BACKTRACE_FRAMES = 100
|
|
14
14
|
MAX_MESSAGE_LENGTH = 4000
|
|
15
15
|
MAX_CONTEXT_BYTES = 16_384
|
|
16
|
+
PLATFORM_PATTERN = /\A[a-z0-9_-]{1,32}\z/i
|
|
16
17
|
|
|
17
18
|
private
|
|
18
19
|
|
|
@@ -46,11 +47,14 @@ module Sentiero
|
|
|
46
47
|
backtrace &&= backtrace.map { |frame| Redaction.redact_text(frame, redaction) }
|
|
47
48
|
|
|
48
49
|
timestamp = numeric_timestamp(data["timestamp"])
|
|
50
|
+
platform = valid_platform(data["platform"])
|
|
51
|
+
normalizer = Sentiero.configuration.fingerprint.resolve(platform)
|
|
49
52
|
|
|
50
53
|
fingerprint = Fingerprint.compute(
|
|
51
54
|
exception_class: exception_class,
|
|
52
55
|
backtrace: backtrace,
|
|
53
|
-
project: project
|
|
56
|
+
project: project,
|
|
57
|
+
normalizer: normalizer
|
|
54
58
|
)
|
|
55
59
|
|
|
56
60
|
occurrence = {
|
|
@@ -67,6 +71,7 @@ module Sentiero
|
|
|
67
71
|
end
|
|
68
72
|
occurrence["session_id"] = session_id if session_id
|
|
69
73
|
occurrence["window_id"] = window_id if window_id
|
|
74
|
+
occurrence["platform"] = platform if platform
|
|
70
75
|
|
|
71
76
|
begin
|
|
72
77
|
Sentiero.store.save_occurrence(occurrence)
|
|
@@ -77,6 +82,14 @@ module Sentiero
|
|
|
77
82
|
json_response(200, {status: "ok", fingerprint: fingerprint})
|
|
78
83
|
end
|
|
79
84
|
|
|
85
|
+
# Anything not matching PLATFORM_PATTERN is treated as absent (tier 1:
|
|
86
|
+
# default_platform's normalizer), not rejected — an unrecognized tag is a
|
|
87
|
+
# reporter quirk, not a malformed request. Downcased so storage/resolve
|
|
88
|
+
# use one canonical form.
|
|
89
|
+
def valid_platform(raw)
|
|
90
|
+
(raw.is_a?(String) && PLATFORM_PATTERN.match?(raw)) ? raw.downcase : nil
|
|
91
|
+
end
|
|
92
|
+
|
|
80
93
|
def capped_context(context)
|
|
81
94
|
(JSON.generate(context).bytesize <= MAX_CONTEXT_BYTES) ? context : {"_truncated" => true}
|
|
82
95
|
end
|
|
@@ -6,6 +6,7 @@ require_relative "body_reader"
|
|
|
6
6
|
require_relative "../analytics/events"
|
|
7
7
|
require_relative "../redaction"
|
|
8
8
|
require_relative "../store"
|
|
9
|
+
require_relative "../geo"
|
|
9
10
|
|
|
10
11
|
module Sentiero
|
|
11
12
|
module Web
|
|
@@ -34,7 +35,7 @@ module Sentiero
|
|
|
34
35
|
# could still POST here.
|
|
35
36
|
return dropped(env) if opted_out?(env) || gpc_signaled?(env)
|
|
36
37
|
|
|
37
|
-
raw_body, error = BodyReader.read(env)
|
|
38
|
+
raw_body, error = BodyReader.read(env, max_bytes: Sentiero.configuration.max_body_size)
|
|
38
39
|
if error
|
|
39
40
|
status, message = BodyReader::ERRORS[error]
|
|
40
41
|
return cors_error(env, status, message)
|
|
@@ -83,6 +84,10 @@ module Sentiero
|
|
|
83
84
|
metadata = metadata.merge("has_errors" => true)
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
# Server geo wins so a page can't forge its location via geo_* keys.
|
|
88
|
+
resolved_geo = Sentiero::Geo.resolve(env, Sentiero.configuration.geo_source, metadata)
|
|
89
|
+
metadata = resolved_geo.merge(metadata.except(*Sentiero::Geo::TARGET_KEYS))
|
|
90
|
+
|
|
86
91
|
unless metadata.empty?
|
|
87
92
|
metadata = Sentiero::Redaction.redact_metadata(metadata, Sentiero.configuration.redaction)
|
|
88
93
|
Sentiero.store.save_metadata(session_id, metadata)
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
<div class="text-xs text-gray-600">
|
|
24
24
|
<%= view.group[:last_seen_at] ? Time.at(view.group[:last_seen_at].to_f / 1000).utc.strftime("%b %d, %Y %H:%M:%S UTC") : "N/A" %>
|
|
25
25
|
</div>
|
|
26
|
+
|
|
27
|
+
<% if view.group[:stack] -%>
|
|
28
|
+
<div class="text-[10px] font-medium text-gray-400 uppercase tracking-wider pt-0.5">Stack</div>
|
|
29
|
+
<pre class="text-[10px] text-gray-500 bg-gray-50 rounded p-2 overflow-x-auto whitespace-pre-wrap font-mono" data-client-error-stack><%= view.h(view.group[:stack]) %></pre>
|
|
30
|
+
<% end -%>
|
|
26
31
|
</div>
|
|
27
32
|
</div>
|
|
28
33
|
</div>
|
|
@@ -36,7 +41,7 @@
|
|
|
36
41
|
<% view.facet_chips.each do |title, counts| -%>
|
|
37
42
|
<% next if counts.empty? -%>
|
|
38
43
|
<div class="flex items-center gap-2 mb-1.5 flex-wrap">
|
|
39
|
-
<span class="text-[10px] font-medium text-gray-400 uppercase tracking-wider w-
|
|
44
|
+
<span class="text-[10px] font-medium text-gray-400 uppercase tracking-wider w-20 shrink-0"><%= view.h(title) %></span>
|
|
40
45
|
<% counts.sort_by { |_value, count| -count }.first(8).each do |value, count| -%>
|
|
41
46
|
<span class="badge badge-neutral"><%= view.h(value) %> <span class="tabular-nums">×<%= count %></span></span>
|
|
42
47
|
<% end -%>
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
Analytics
|
|
36
36
|
</a>
|
|
37
37
|
</li>
|
|
38
|
+
<li>
|
|
39
|
+
<a class="s-nav-item <%= request_path.start_with?('/maintenance') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/maintenance">
|
|
40
|
+
<svg class="s-nav-icon" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17 17.25 21A2.652 2.652 0 0 0 21 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 1 1-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 0 0 4.486-6.336l-3.276 3.277a3.004 3.004 0 0 1-2.25-2.25l3.276-3.276a4.5 4.5 0 0 0-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437 1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008Z" /></svg>
|
|
41
|
+
Maintenance
|
|
42
|
+
</a>
|
|
43
|
+
</li>
|
|
38
44
|
</ul>
|
|
39
45
|
</aside>
|
|
40
46
|
|
|
@@ -45,6 +51,7 @@
|
|
|
45
51
|
<a class="s-nav-item <%= request_path.start_with?('/issues') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/issues">Errors</a>
|
|
46
52
|
<a class="s-nav-item <%= request_path.start_with?('/custom-events') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/custom-events">Events</a>
|
|
47
53
|
<a class="s-nav-item <%= request_path.start_with?('/analytics') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/analytics">Analytics</a>
|
|
54
|
+
<a class="s-nav-item <%= request_path.start_with?('/maintenance') ? 'active' : '' %>" href="<%= view.h(view.base_path) %>/maintenance">Maintenance</a>
|
|
48
55
|
</nav>
|
|
49
56
|
</header>
|
|
50
57
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<h1 class="page-title">Maintenance</h1>
|
|
2
|
+
|
|
3
|
+
<% if view.purged %>
|
|
4
|
+
<div class="card mb-4 px-4 py-3 text-sm">Purged <%= view.purged %> session<%= view.purged == 1 ? "" : "s" %>.</div>
|
|
5
|
+
<% end %>
|
|
6
|
+
<% if view.error %>
|
|
7
|
+
<div class="card mb-4 px-4 py-3 text-sm text-red-600"><%= view.h(view.error) %></div>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<div class="card mb-4 px-4 py-3">
|
|
11
|
+
<h2 class="text-sm font-semibold mb-2">Purge recordings before a date</h2>
|
|
12
|
+
<p class="text-xs text-gray-500 mb-3">
|
|
13
|
+
Permanently deletes every session (and its error occurrences and tracked
|
|
14
|
+
events) last updated on or before the chosen date, UTC. This cannot be undone.
|
|
15
|
+
</p>
|
|
16
|
+
<form method="post" action="<%= view.h(view.base_path) %>/maintenance/purge-before">
|
|
17
|
+
<input type="hidden" name="csrf_token" value="<%= view.h(view.csrf_token) %>">
|
|
18
|
+
<div class="flex items-end gap-3">
|
|
19
|
+
<div>
|
|
20
|
+
<label for="date" class="label">Delete on/before</label>
|
|
21
|
+
<input type="date" name="date" id="date" class="input" required>
|
|
22
|
+
</div>
|
|
23
|
+
<label class="text-xs flex items-center gap-1.5 pb-1.5">
|
|
24
|
+
<input type="checkbox" name="confirm" value="1">
|
|
25
|
+
I understand this permanently deletes recordings
|
|
26
|
+
</label>
|
|
27
|
+
<button type="submit" class="btn btn-danger">Purge</button>
|
|
28
|
+
</div>
|
|
29
|
+
</form>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="card mb-4 px-4 py-3">
|
|
33
|
+
<h2 class="text-sm font-semibold mb-2">Retention</h2>
|
|
34
|
+
<% if view.retention_period %>
|
|
35
|
+
<p class="text-xs text-gray-500 mb-3">
|
|
36
|
+
<code>retention_period</code> is <%= view.retention_period %> seconds
|
|
37
|
+
(<%= view.retention_days %> days). Scheduled purges should run from cron;
|
|
38
|
+
this button runs one now.
|
|
39
|
+
</p>
|
|
40
|
+
<form method="post" action="<%= view.h(view.base_path) %>/maintenance/purge-expired">
|
|
41
|
+
<input type="hidden" name="csrf_token" value="<%= view.h(view.csrf_token) %>">
|
|
42
|
+
<button type="submit" class="btn btn-danger">Run retention purge now</button>
|
|
43
|
+
</form>
|
|
44
|
+
<% else %>
|
|
45
|
+
<p class="text-xs text-gray-500">
|
|
46
|
+
<code>retention_period</code> is not set — recordings are kept forever.
|
|
47
|
+
See <a class="underline" href="https://www.sentiero.app/guide/privacy/#data-retention--purge">data retention</a>.
|
|
48
|
+
</p>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
@@ -23,6 +23,15 @@
|
|
|
23
23
|
<% end -%>
|
|
24
24
|
</select>
|
|
25
25
|
</div>
|
|
26
|
+
<div class="shrink-0">
|
|
27
|
+
<label for="country" class="label">Country</label>
|
|
28
|
+
<select name="country" id="country" class="select" style="width:8rem">
|
|
29
|
+
<option value="">Any</option>
|
|
30
|
+
<% (view.country_options | [view.filters[:country]].compact).each do |c| -%>
|
|
31
|
+
<option value="<%= view.h(c) %>" <%= view.filters[:country] == c ? "selected" : "" %>><%= view.h(c) %></option>
|
|
32
|
+
<% end -%>
|
|
33
|
+
</select>
|
|
34
|
+
</div>
|
|
26
35
|
<div class="shrink-0">
|
|
27
36
|
<label for="url_pattern" class="label">URL pattern</label>
|
|
28
37
|
<input type="text" name="url_pattern" id="url_pattern" class="input" style="width:12rem" value="<%= view.h(view.filters[:url_pattern].to_s) %>" placeholder="substring or *glob*">
|