2026-03-13 10:52:43 +01:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2026-04-15 15:28:42 +02:00
< title > Cameleer — Transaction Explorer< / title >
2026-03-13 10:52:43 +01:00
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin >
< link href = "https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=JetBrains+Mono:wght@300;400;500;600&display=swap" rel = "stylesheet" >
< style >
:root {
--bg-deep: #f7f5f2;
--bg-base: #efecea;
--bg-surface: #ffffff;
--bg-raised: #f3f1ee;
--bg-hover: #eae7e3;
--border: #d4cfc8;
--border-subtle: #e4e0db;
--text-primary: #1c1917;
--text-secondary: #57534e;
--text-muted: #a8a29e;
--amber: #b45309;
--amber-dim: #92400e;
--amber-glow: rgba(180, 83, 9, 0.07);
--cyan: #0e7490;
--cyan-dim: #155e75;
--cyan-glow: rgba(14, 116, 144, 0.06);
--rose: #be123c;
--rose-dim: #9f1239;
--rose-glow: rgba(190, 18, 60, 0.05);
--green: #047857;
--green-glow: rgba(4, 120, 87, 0.06);
--blue: #1d4ed8;
--purple: #7c3aed;
--font-body: 'DM Sans', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg-deep);
color: var(--text-primary);
font-family: var(--font-body);
font-size: 14px;
line-height: 1.5;
min-height: 100vh;
overflow-x: hidden;
}
/* Topographic background pattern */
body::before {
content: '';
position: fixed;
inset: 0;
background:
radial-gradient(ellipse 800px 400px at 20% 20%, rgba(180, 83, 9, 0.02), transparent),
radial-gradient(ellipse 600px 600px at 80% 80%, rgba(14, 116, 144, 0.015), transparent);
pointer-events: none;
z-index: 0;
}
/* Topo lines */
body::after {
content: '';
position: fixed;
inset: 0;
opacity: 0.018;
background-image: url("data:image/svg+xml,%3Csvg width='400' height='400' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 200 Q100 150 200 200 T400 200' fill='none' stroke='%23b45309' stroke-width='1'/%3E%3Cpath d='M0 220 Q120 170 200 220 T400 220' fill='none' stroke='%23b45309' stroke-width='0.5'/%3E%3Cpath d='M0 180 Q80 130 200 180 T400 180' fill='none' stroke='%23b45309' stroke-width='0.5'/%3E%3Cpath d='M0 100 Q150 60 200 100 T400 100' fill='none' stroke='%230e7490' stroke-width='0.5'/%3E%3Cpath d='M0 300 Q100 260 200 300 T400 300' fill='none' stroke='%230e7490' stroke-width='0.5'/%3E%3C/svg%3E");
background-size: 400px 400px;
pointer-events: none;
z-index: 0;
}
a { color: var(--amber); text-decoration: none; }
a:hover { color: var(--text-primary); }
/* ─── Top Navigation ─── */
.topnav {
position: sticky;
top: 0;
z-index: 100;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(20px) saturate(1.2);
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
border-bottom: 1px solid var(--border-subtle);
padding: 0 24px;
display: flex;
align-items: center;
height: 56px;
gap: 32px;
}
.topnav .logo {
font-family: var(--font-mono);
font-weight: 600;
font-size: 16px;
color: var(--amber);
letter-spacing: -0.5px;
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.topnav .logo svg { width: 22px; height: 22px; }
.topnav .nav-links {
display: flex;
gap: 4px;
list-style: none;
}
.topnav .nav-links a {
padding: 8px 16px;
border-radius: var(--radius-sm);
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
transition: all 0.15s;
position: relative;
}
.topnav .nav-links a:hover { color: var(--text-primary); background: var(--bg-raised); }
.topnav .nav-links a.active {
color: var(--amber);
background: var(--amber-glow);
}
.topnav .nav-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 16px;
}
.env-badge {
font-family: var(--font-mono);
font-size: 11px;
padding: 4px 10px;
border-radius: 99px;
background: var(--green-glow);
color: var(--green);
border: 1px solid rgba(4, 120, 87, 0.2);
font-weight: 500;
}
.cluster-count {
font-size: 12px;
color: var(--text-muted);
font-family: var(--font-mono);
}
/* ─── Main Layout ─── */
.main {
position: relative;
z-index: 1;
max-width: 1440px;
margin: 0 auto;
padding: 24px;
}
/* ─── Page Header ─── */
.page-header {
margin-bottom: 24px;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
}
.page-header h1 {
font-size: 24px;
font-weight: 700;
letter-spacing: -0.5px;
color: var(--text-primary);
}
.page-header .subtitle {
font-size: 13px;
color: var(--text-muted);
margin-top: 2px;
}
.live-indicator {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--green);
font-family: var(--font-mono);
font-weight: 500;
}
.live-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--green);
animation: livePulse 2s ease-in-out infinite;
}
@keyframes livePulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(4, 120, 87, 0.3); }
50% { box-shadow: 0 0 0 6px rgba(4, 120, 87, 0); }
}
/* ─── Stats Bar ─── */
.stats-bar {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.stat-card {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 16px 20px;
position: relative;
overflow: hidden;
transition: border-color 0.2s;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.stat-card:hover { border-color: var(--border); }
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
}
.stat-card.amber::before { background: linear-gradient(90deg, var(--amber), transparent); }
.stat-card.cyan::before { background: linear-gradient(90deg, var(--cyan), transparent); }
.stat-card.rose::before { background: linear-gradient(90deg, var(--rose), transparent); }
.stat-card.green::before { background: linear-gradient(90deg, var(--green), transparent); }
.stat-card.blue::before { background: linear-gradient(90deg, var(--blue), transparent); }
.stat-card .stat-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
margin-bottom: 8px;
}
.stat-card .stat-value {
font-family: var(--font-mono);
font-size: 26px;
font-weight: 600;
letter-spacing: -1px;
}
.stat-card.amber .stat-value { color: var(--amber); }
.stat-card.cyan .stat-value { color: var(--cyan); }
.stat-card.rose .stat-value { color: var(--rose); }
.stat-card.green .stat-value { color: var(--green); }
.stat-card.blue .stat-value { color: var(--blue); }
.stat-card .stat-change {
font-size: 11px;
font-family: var(--font-mono);
margin-top: 4px;
}
.stat-card .stat-change.up { color: var(--rose); }
.stat-card .stat-change.down { color: var(--green); }
.stat-card .stat-change.neutral { color: var(--text-muted); }
/* ─── Search & Filters ─── */
.filter-bar {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
padding: 16px 20px;
margin-bottom: 16px;
display: flex;
flex-direction: column;
gap: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.filter-row {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.search-input-wrap {
flex: 1;
min-width: 300px;
position: relative;
}
.search-input-wrap svg {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
color: var(--text-muted);
}
.search-input-wrap input {
width: 100%;
background: var(--bg-base);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 14px 10px 40px;
color: var(--text-primary);
font-family: var(--font-mono);
font-size: 13px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.search-input-wrap input::placeholder { color: var(--text-muted); font-family: var(--font-body); }
.search-input-wrap input:focus {
border-color: var(--amber);
box-shadow: 0 0 0 3px var(--amber-glow);
}
.search-input-wrap .search-hint {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-family: var(--font-mono);
font-size: 10px;
padding: 3px 8px;
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-muted);
}
.filter-group {
display: flex;
align-items: center;
gap: 6px;
}
.filter-group label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
white-space: nowrap;
}
.filter-select {
background: var(--bg-base);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 8px 32px 8px 12px;
color: var(--text-primary);
font-family: var(--font-body);
font-size: 13px;
outline: none;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23a8a29e' fill='none' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
transition: border-color 0.2s;
}
.filter-select:focus { border-color: var(--amber); }
.filter-chips {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 12px;
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: 99px;
font-size: 12px;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
user-select: none;
}
.chip:hover { border-color: var(--text-muted); color: var(--text-primary); }
.chip.active { background: var(--amber-glow); border-color: var(--amber); color: var(--amber); }
.chip.active.green-chip { background: var(--green-glow); border-color: rgba(4,120,87,0.3); color: var(--green); }
.chip.active.rose-chip { background: var(--rose-glow); border-color: rgba(190,18,60,0.3); color: var(--rose); }
.chip.active.blue-chip { background: rgba(29,78,216,0.06); border-color: rgba(29,78,216,0.3); color: var(--blue); }
.chip .chip-count {
font-family: var(--font-mono);
font-size: 10px;
opacity: 0.7;
}
.date-input {
background: var(--bg-base);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 8px 12px;
color: var(--text-primary);
font-family: var(--font-mono);
font-size: 12px;
outline: none;
width: 160px;
transition: border-color 0.2s;
}
.date-input:focus { border-color: var(--amber); }
.duration-range {
display: flex;
align-items: center;
gap: 8px;
}
.duration-range input[type="range"] {
width: 100px;
accent-color: var(--amber);
cursor: pointer;
}
.duration-range .range-label {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
min-width: 50px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--bg-surface);
color: var(--text-secondary);
font-family: var(--font-body);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.btn:hover { background: var(--bg-raised); color: var(--text-primary); border-color: var(--text-muted); }
.btn-primary {
background: var(--amber);
color: #ffffff;
border-color: var(--amber);
font-weight: 600;
}
.btn-primary:hover { background: #92400e; border-color: #92400e; color: #ffffff; }
.filter-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
align-items: center;
}
.filter-tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: var(--amber-glow);
border: 1px solid rgba(180,83,9,0.2);
border-radius: 99px;
font-size: 12px;
color: var(--amber);
font-family: var(--font-mono);
}
.filter-tag .remove {
cursor: pointer;
opacity: 0.5;
font-size: 14px;
line-height: 1;
}
.filter-tag .remove:hover { opacity: 1; }
.filter-tags .clear-all {
font-size: 11px;
color: var(--text-muted);
cursor: pointer;
padding: 4px 8px;
}
.filter-tags .clear-all:hover { color: var(--rose); }
/* ─── Results Table ─── */
.results-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
padding: 0 4px;
}
.results-count {
font-size: 12px;
color: var(--text-muted);
font-family: var(--font-mono);
}
.results-count strong { color: var(--text-secondary); }
.results-actions {
display: flex;
gap: 8px;
}
.table-wrap {
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
thead {
background: var(--bg-raised);
border-bottom: 1px solid var(--border);
}
thead th {
padding: 12px 16px;
text-align: left;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
cursor: pointer;
user-select: none;
white-space: nowrap;
transition: color 0.15s;
position: relative;
}
thead th:hover { color: var(--text-secondary); }
thead th.sorted { color: var(--amber); }
thead th.sorted::after {
content: ' ▼';
font-size: 8px;
}
thead th.sorted.asc::after { content: ' ▲'; }
tbody tr {
border-bottom: 1px solid var(--border-subtle);
transition: background 0.1s;
cursor: pointer;
}
tbody tr:last-child { border-bottom: none; }
tbody tr:hover { background: var(--bg-raised); }
tbody td {
padding: 12px 16px;
vertical-align: middle;
white-space: nowrap;
}
.td-expand {
width: 32px;
text-align: center;
color: var(--text-muted);
font-size: 16px;
transition: transform 0.2s;
}
tr.expanded .td-expand { transform: rotate(90deg); color: var(--amber); }
.status-pill {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 10px;
border-radius: 99px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.3px;
}
.status-pill.completed { background: var(--green-glow); color: var(--green); }
.status-pill.failed { background: var(--rose-glow); color: var(--rose); }
.status-pill.running { background: rgba(29,78,216,0.06); color: var(--blue); }
.status-pill .status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
.status-pill.running .status-dot { animation: livePulse 1.5s ease-in-out infinite; }
.mono { font-family: var(--font-mono); font-size: 12px; }
.text-muted { color: var(--text-muted); }
.text-secondary { color: var(--text-secondary); }
.text-amber { color: var(--amber); }
.app-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 2px 8px;
background: var(--bg-raised);
border: 1px solid var(--border-subtle);
border-radius: 4px;
font-size: 11px;
font-family: var(--font-mono);
color: var(--text-secondary);
}
.app-badge .app-dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
.duration-bar {
display: flex;
align-items: center;
gap: 8px;
}
.duration-bar .bar {
width: 60px;
height: 4px;
background: var(--bg-base);
border-radius: 2px;
overflow: hidden;
}
.duration-bar .bar-fill {
height: 100%;
border-radius: 2px;
transition: width 0.3s;
}
.duration-bar .bar-fill.fast { background: var(--green); }
.duration-bar .bar-fill.medium { background: var(--amber); }
.duration-bar .bar-fill.slow { background: var(--rose); }
.correlation-id {
max-width: 140px;
overflow: hidden;
text-overflow: ellipsis;
}
/* ─── Expanded Row / Processor Tree ─── */
.detail-row { display: none; }
.detail-row.visible { display: table-row; }
.detail-row td {
padding: 0;
background: var(--bg-raised);
border-bottom: 1px solid var(--border);
}
.detail-content {
padding: 20px 24px;
display: flex;
gap: 24px;
}
.processor-tree {
flex: 1;
min-width: 0;
}
.processor-tree h4 {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
margin-bottom: 12px;
}
.proc-node {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 8px 12px;
border-radius: var(--radius-sm);
margin-bottom: 2px;
transition: background 0.1s;
position: relative;
}
.proc-node:hover { background: var(--bg-surface); }
.proc-connector {
position: absolute;
left: 22px;
top: 28px;
bottom: -4px;
width: 1px;
background: var(--border);
}
.proc-node:last-child .proc-connector { display: none; }
.proc-icon {
width: 28px;
height: 28px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
z-index: 1;
font-family: var(--font-mono);
}
.proc-icon.endpoint { background: rgba(29,78,216,0.08); color: var(--blue); border: 1px solid rgba(29,78,216,0.2); }
.proc-icon.processor { background: var(--green-glow); color: var(--green); border: 1px solid rgba(4,120,87,0.2); }
.proc-icon.eip { background: rgba(124,58,237,0.07); color: var(--purple); border: 1px solid rgba(124,58,237,0.2); }
.proc-icon.error { background: var(--rose-glow); color: var(--rose); border: 1px solid rgba(190,18,60,0.2); }
.proc-info { flex: 1; min-width: 0; }
.proc-info .proc-type { font-size: 12px; font-weight: 600; color: var(--text-primary); }
.proc-info .proc-uri {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.proc-timing {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
flex-shrink: 0;
text-align: right;
}
.proc-timing .proc-duration { font-weight: 600; color: var(--text-secondary); }
.detail-sidebar {
width: 280px;
flex-shrink: 0;
}
.detail-sidebar h4 {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
margin-bottom: 12px;
}
.detail-kv {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 12px;
font-size: 12px;
}
.detail-kv dt { color: var(--text-muted); font-weight: 500; }
.detail-kv dd { font-family: var(--font-mono); color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; }
.detail-body-preview {
margin-top: 16px;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
padding: 12px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-secondary);
max-height: 120px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
}
.detail-body-preview .label {
font-family: var(--font-body);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
display: block;
margin-bottom: 6px;
}
.error-preview {
margin-top: 12px;
background: var(--rose-glow);
border: 1px solid rgba(190,18,60,0.2);
border-radius: var(--radius-sm);
padding: 10px 12px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--rose);
max-height: 80px;
overflow: auto;
}
/* ─── Pagination ─── */
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-top: 20px;
}
.pagination .page-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
color: var(--text-secondary);
font-family: var(--font-mono);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
.pagination .page-btn:hover { border-color: var(--border); background: var(--bg-raised); }
.pagination .page-btn.active { background: var(--amber-glow); border-color: var(--amber-dim); color: var(--amber); }
.pagination .page-btn.disabled { opacity: 0.3; cursor: default; }
.pagination .page-ellipsis {
color: var(--text-muted);
padding: 0 4px;
font-family: var(--font-mono);
}
/* ─── Animations ─── */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-in { animation: fadeIn 0.3s ease-out both; }
.delay-1 { animation-delay: 0.05s; }
.delay-2 { animation-delay: 0.1s; }
.delay-3 { animation-delay: 0.15s; }
.delay-4 { animation-delay: 0.2s; }
.delay-5 { animation-delay: 0.25s; }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
/* ─── Responsive ─── */
@media (max-width: 1200px) {
.stats-bar { grid-template-columns: repeat(3, 1fr); }
.detail-content { flex-direction: column; }
.detail-sidebar { width: 100%; }
}
@media (max-width: 768px) {
.stats-bar { grid-template-columns: 1fr 1fr; }
.filter-row { flex-direction: column; align-items: stretch; }
.search-input-wrap { min-width: unset; }
}
< / style >
< / head >
< body >
<!-- ═══ Top Navigation ═══ -->
< nav class = "topnav" >
< div class = "logo" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" >
< path d = "M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2" / >
< path d = "M12 6v6l4 2" / >
< / svg >
2026-04-15 15:28:42 +02:00
cameleer
2026-03-13 10:52:43 +01:00
< / div >
< ul class = "nav-links" >
< li > < a href = "dashboard-light.html" > Dashboard< / a > < / li >
< li > < a href = "transaction-explorer-light.html" class = "active" > Transactions< / a > < / li >
< li > < a href = "route-detail-light.html" > Routes< / a > < / li >
< / ul >
< div class = "nav-right" >
< span class = "env-badge" > PRODUCTION< / span >
< span class = "cluster-count" > 47 apps · 1,283 routes< / span >
< / div >
< / nav >
<!-- ═══ Main Content ═══ -->
< main class = "main" >
<!-- Page Header -->
< div class = "page-header animate-in" >
< div >
< h1 > Transaction Explorer< / h1 >
< div class = "subtitle" > Search across 47 applications · 2.4M transactions today< / div >
< / div >
< div class = "live-indicator" >
< span class = "live-dot" > < / span >
STREAMING
< / div >
< / div >
<!-- Stats Bar -->
< div class = "stats-bar" >
< div class = "stat-card amber animate-in delay-1" >
< div class = "stat-label" > Total Matches< / div >
< div class = "stat-value" > 12,847< / div >
< div class = "stat-change neutral" > of 2.4M today< / div >
< / div >
< div class = "stat-card cyan animate-in delay-2" >
< div class = "stat-label" > Avg Duration< / div >
< div class = "stat-value" > 47ms< / div >
< div class = "stat-change down" > ↓ 12% vs yesterday< / div >
< / div >
< div class = "stat-card rose animate-in delay-3" >
< div class = "stat-label" > Failure Rate< / div >
< div class = "stat-value" > 0.34%< / div >
< div class = "stat-change up" > ↑ 0.08% vs yesterday< / div >
< / div >
< div class = "stat-card green animate-in delay-4" >
< div class = "stat-label" > P99 Latency< / div >
< div class = "stat-value" > 312ms< / div >
< div class = "stat-change down" > ↓ 23ms vs yesterday< / div >
< / div >
< div class = "stat-card blue animate-in delay-5" >
< div class = "stat-label" > Active Now< / div >
< div class = "stat-value" > 1,293< / div >
< div class = "stat-change neutral" > exchanges in-flight< / div >
< / div >
< / div >
<!-- Filters -->
< div class = "filter-bar animate-in delay-3" >
<!-- Row 1: Search -->
< div class = "filter-row" >
< div class = "search-input-wrap" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < circle cx = "11" cy = "11" r = "8" / > < path d = "M21 21l-4.35-4.35" / > < / svg >
< input type = "text" placeholder = "Search by correlation ID, order ID, error message, route ID..." value = "ORD-2024-88431" >
< span class = "search-hint" > ⌘K< / span >
< / div >
< button class = "btn-primary btn" > Search< / button >
< button class = "btn" >
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M4 21v-7m0 0V5a2 2 0 012-2h2.5l1 2H21l-3 6H9.5l-1-2H6v7z" / > < / svg >
Export
< / button >
< / div >
<!-- Row 2: Filters -->
< div class = "filter-row" >
< div class = "filter-group" >
< label > Status< / label >
< div class = "filter-chips" >
< span class = "chip active green-chip" > < span class = "status-dot" style = "width:6px;height:6px;border-radius:50%;background:var(--green);display:inline-block" > < / span > Completed < span class = "chip-count" > 12,804< / span > < / span >
< span class = "chip active rose-chip" > < span class = "status-dot" style = "width:6px;height:6px;border-radius:50%;background:var(--rose);display:inline-block" > < / span > Failed < span class = "chip-count" > 43< / span > < / span >
< span class = "chip blue-chip" > < span class = "status-dot" style = "width:6px;height:6px;border-radius:50%;background:var(--blue);display:inline-block" > < / span > Running < span class = "chip-count" > 0< / span > < / span >
< / div >
< / div >
< div style = "width:1px;height:24px;background:var(--border);margin:0 4px" > < / div >
< div class = "filter-group" >
< label > Date< / label >
< input type = "text" class = "date-input" value = "2026-03-09 00:00" placeholder = "From" >
< span class = "text-muted" style = "font-size:12px" > →< / span >
< input type = "text" class = "date-input" value = "2026-03-09 23:59" placeholder = "To" >
< / div >
< div style = "width:1px;height:24px;background:var(--border);margin:0 4px" > < / div >
< div class = "filter-group" >
< label > Duration< / label >
< div class = "duration-range" >
< span class = "range-label" > 0ms< / span >
< input type = "range" min = "0" max = "5000" value = "5000" >
< span class = "range-label" > ≤ 5000ms< / span >
< / div >
< / div >
< / div >
<!-- Row 3: App + Route filters -->
< div class = "filter-row" >
< div class = "filter-group" >
< label > Application< / label >
< select class = "filter-select" >
< option > All Applications (47)< / option >
< option selected > order-service-eu< / option >
< option > payment-gateway< / option >
< option > inventory-sync< / option >
< option > notification-hub< / option >
< option > etl-pipeline-prod< / option >
< / select >
< / div >
< div class = "filter-group" >
< label > Route< / label >
< select class = "filter-select" >
< option > All Routes (1,283)< / option >
< option > order-intake< / option >
< option selected > content-based-routing< / option >
< option > payment-processing< / option >
< option > inventory-check< / option >
< / select >
< / div >
< div class = "filter-tags" style = "margin-left:8px" >
< span class = "filter-tag" > app:order-service-eu < span class = "remove" > × < / span > < / span >
< span class = "filter-tag" > route:content-based-routing < span class = "remove" > × < / span > < / span >
< span class = "filter-tag" > text:"ORD-2024-88431" < span class = "remove" > × < / span > < / span >
< span class = "clear-all" > Clear all< / span >
< / div >
< / div >
< / div >
<!-- Results Header -->
< div class = "results-header animate-in delay-4" >
< span class = "results-count" > Showing < strong > 1– 25< / strong > of < strong > 12,847< / strong > results · sorted by < strong > timestamp< / strong > desc< / span >
< div class = "results-actions" >
< button class = "btn" style = "font-size:12px;padding:6px 10px" >
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M3 6h18M3 12h18M3 18h18" / > < / svg >
Columns
< / button >
< button class = "btn" style = "font-size:12px;padding:6px 10px" >
< svg width = "12" height = "12" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4m4-5l5 5 5-5m-5 5V3" / > < / svg >
CSV
< / button >
< / div >
< / div >
<!-- Results Table -->
< div class = "table-wrap animate-in delay-4" >
< table >
< thead >
< tr >
< th style = "width:32px" > < / th >
< th class = "sorted" > Timestamp< / th >
< th > Status< / th >
< th > Application< / th >
< th > Route< / th >
< th > Correlation ID< / th >
< th > Duration< / th >
< th > Processors< / th >
< / tr >
< / thead >
< tbody id = "results-body" >
< / tbody >
< / table >
< / div >
<!-- Pagination -->
< div class = "pagination animate-in delay-5" >
< div class = "page-btn disabled" > ‹ < / div >
< div class = "page-btn active" > 1< / div >
< div class = "page-btn" > 2< / div >
< div class = "page-btn" > 3< / div >
< div class = "page-btn" > 4< / div >
< div class = "page-btn" > 5< / div >
< span class = "page-ellipsis" > …< / span >
< div class = "page-btn" > 514< / div >
< div class = "page-btn" > › < / div >
< / div >
< / main >
< script >
// ─── Mock Data Generator ───
const APPS = [
{ name: 'order-service-eu', color: '#1d4ed8' },
{ name: 'payment-gateway', color: '#b45309' },
{ name: 'inventory-sync', color: '#047857' },
{ name: 'notification-hub', color: '#7c3aed' },
{ name: 'etl-pipeline-prod', color: '#be123c' },
{ name: 'shipping-tracker', color: '#0e7490' },
{ name: 'customer-portal', color: '#c026d3' },
];
const ROUTES = [
'order-intake', 'content-based-routing', 'payment-processing',
'inventory-check', 'notification-dispatch', 'etl-transform',
'shipment-update', 'order-validation', 'fraud-check',
'email-sender', 'sms-gateway', 'audit-log',
];
const ERROR_MESSAGES = [
'Connection refused: payment-provider-api:8443',
'Timeout waiting for response from inventory-service (30000ms)',
'org.apache.camel.CamelExecutionException: MVEL expression failed',
'javax.jms.JMSException: Queue ORDERS.PRIORITY is full',
'HttpOperationFailedException: HTTP operation failed with status 503',
'com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: field "qty"',
];
const PROCESSOR_TREES = {
completed: [
{ type: 'From', icon: 'EP', iconClass: 'endpoint', uri: 'direct:order-intake', duration: 0 },
{ type: 'SetHeader', icon: 'PR', iconClass: 'processor', uri: 'X-Cameleer-CorrelationId', duration: 1 },
{ type: 'Log', icon: 'PR', iconClass: 'processor', uri: 'Received order: ${body}', duration: 0 },
{ type: 'Choice', icon: 'CB', iconClass: 'eip', uri: 'content-based-router', duration: 0 },
{ type: 'When', icon: 'CB', iconClass: 'eip', uri: '${header.priority} == "HIGH"', duration: 0 },
{ type: 'To', icon: 'EP', iconClass: 'endpoint', uri: 'direct:priority-processing', duration: 35 },
{ type: 'Marshal', icon: 'PR', iconClass: 'processor', uri: 'jackson', duration: 2 },
{ type: 'To', icon: 'EP', iconClass: 'endpoint', uri: 'jms:queue:ORDERS.COMPLETED', duration: 8 },
],
failed: [
{ type: 'From', icon: 'EP', iconClass: 'endpoint', uri: 'direct:payment-processing', duration: 0 },
{ type: 'SetHeader', icon: 'PR', iconClass: 'processor', uri: 'Authorization: Bearer ***', duration: 0 },
{ type: 'To', icon: 'EP', iconClass: 'endpoint', uri: 'https://api.payment-provider.com/v2/charge', duration: 3042 },
{ type: 'Exception', icon: '!!', iconClass: 'error', uri: 'HttpOperationFailedException: HTTP 503', duration: 0 },
]
};
function randomId() {
const hex = () => Math.random().toString(16).slice(2, 10);
return hex() + hex();
}
function generateTransactions(count) {
const txns = [];
const now = Date.now();
for (let i = 0; i < count ; i + + ) {
const isFailed = Math.random() < 0.04 ;
const isRunning = !isFailed & & Math.random() < 0.005 ;
const status = isRunning ? 'running' : (isFailed ? 'failed' : 'completed');
const app = APPS[Math.floor(Math.random() * APPS.length)];
const route = ROUTES[Math.floor(Math.random() * ROUTES.length)];
const duration = isFailed
? Math.floor(Math.random() * 30000) + 100
: Math.floor(Math.random() * 500) + 5;
const procCount = Math.floor(Math.random() * 12) + 3;
const orderId = `ORD-2024-${(88000 + Math.floor(Math.random() * 1000)).toString()}`;
const ts = new Date(now - i * (Math.random() * 2000 + 500));
txns.push({
id: randomId(),
timestamp: ts,
status,
app,
route,
correlationId: randomId(),
duration,
processorCount: procCount,
orderId,
errorMessage: isFailed ? ERROR_MESSAGES[Math.floor(Math.random() * ERROR_MESSAGES.length)] : null,
});
}
// Ensure our search target is in the list
txns[2].orderId = 'ORD-2024-88431';
txns[2].app = APPS[0];
txns[2].route = 'content-based-routing';
txns[2].status = 'completed';
txns[2].duration = 47;
txns[7].status = 'failed';
txns[7].duration = 3142;
txns[7].errorMessage = ERROR_MESSAGES[0];
return txns;
}
function formatTime(date) {
return date.toTimeString().slice(0, 12);
}
function durationClass(ms) {
if (ms < 100 ) return ' fast ' ;
if (ms < 1000 ) return ' medium ' ;
return 'slow';
}
function durationWidth(ms) {
return Math.min(100, (ms / 5000) * 100);
}
function renderTable(txns) {
const tbody = document.getElementById('results-body');
let html = '';
txns.forEach((txn, idx) => {
html += `
< tr onclick = "toggleDetail(${idx})" data-idx = "${idx}" >
< td class = "td-expand" > › < / td >
< td class = "mono" > ${formatTime(txn.timestamp)}< / td >
< td >
< span class = "status-pill ${txn.status}" >
< span class = "status-dot" > < / span >
${txn.status.charAt(0).toUpperCase() + txn.status.slice(1)}
< / span >
< / td >
< td >
< span class = "app-badge" >
< span class = "app-dot" style = "background:${txn.app.color}" > < / span >
${txn.app.name}
< / span >
< / td >
< td class = "mono text-secondary" > ${txn.route}< / td >
< td class = "mono text-muted correlation-id" title = "${txn.correlationId}" > ${txn.correlationId}< / td >
< td >
< div class = "duration-bar" >
< span class = "mono" style = "color:${txn.duration > 1000 ? 'var(--rose)' : txn.duration > 100 ? 'var(--amber)' : 'var(--green)'}" > ${txn.duration.toLocaleString()}ms< / span >
< div class = "bar" > < div class = "bar-fill ${durationClass(txn.duration)}" style = "width:${durationWidth(txn.duration)}%" > < / div > < / div >
< / div >
< / td >
< td class = "mono text-muted" > ${txn.processorCount}< / td >
< / tr >
< tr class = "detail-row" id = "detail-${idx}" >
< td colspan = "8" >
< div class = "detail-content" >
< div class = "processor-tree" >
< h4 > Processor Execution Tree< / h4 >
${renderProcessorTree(txn)}
< / div >
< div class = "detail-sidebar" >
< h4 > Exchange Details< / h4 >
< dl class = "detail-kv" >
< dt > Exchange ID< / dt > < dd > ${txn.id}< / dd >
< dt > Correlation< / dt > < dd > ${txn.correlationId}< / dd >
< dt > Order ID< / dt > < dd > ${txn.orderId}< / dd >
< dt > Application< / dt > < dd > ${txn.app.name}< / dd >
< dt > Route< / dt > < dd > ${txn.route}< / dd >
< dt > Timestamp< / dt > < dd > ${txn.timestamp.toISOString()}< / dd >
< dt > Duration< / dt > < dd > ${txn.duration}ms< / dd >
< dt > Processors< / dt > < dd > ${txn.processorCount}< / dd >
< / dl >
< div class = "detail-body-preview" >
< span class = "label" > Input Body< / span > {"orderId":"${txn.orderId}","customer":"ACME Corp","items":[{"sku":"WDG-4401","qty":3,"price":29.99}],"priority":"HIGH","region":"EU-WEST"}< / div >
${txn.errorMessage ? `< div class = "error-preview" > ${txn.errorMessage}< / div > ` : ''}
< / div >
< / div >
< / td >
< / tr >
`;
});
tbody.innerHTML = html;
}
function renderProcessorTree(txn) {
const tree = txn.status === 'failed' ? PROCESSOR_TREES.failed : PROCESSOR_TREES.completed;
return tree.map(proc => `
< div class = "proc-node" >
< div class = "proc-connector" > < / div >
< div class = "proc-icon ${proc.iconClass}" > ${proc.icon}< / div >
< div class = "proc-info" >
< div class = "proc-type" > ${proc.type}< / div >
< div class = "proc-uri" > ${proc.uri}< / div >
< / div >
< div class = "proc-timing" >
< span class = "proc-duration" > ${proc.duration}ms< / span >
< / div >
< / div >
`).join('');
}
function toggleDetail(idx) {
const row = document.querySelector(`tr[data-idx="${idx}"]`);
const detail = document.getElementById(`detail-${idx}`);
row.classList.toggle('expanded');
detail.classList.toggle('visible');
}
// Init
const transactions = generateTransactions(25);
renderTable(transactions);
// Chip toggle
document.querySelectorAll('.chip').forEach(chip => {
chip.addEventListener('click', () => chip.classList.toggle('active'));
});
< / script >
< / body >
< / html >