diff --git a/cmd/yggdrasil/dashboard.html b/cmd/yggdrasil/dashboard.html index 2b03907..3a70863 100644 --- a/cmd/yggdrasil/dashboard.html +++ b/cmd/yggdrasil/dashboard.html @@ -22,7 +22,7 @@ body { font-family: var(--font-sans); background: var(--bg-color); color: var(-- header { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding-bottom: 16px; margin-bottom: 22px; border-bottom: 6px solid var(--accent-orange); } header h2 { margin: 0; font-size: 30px; font-weight: 800; letter-spacing: -1px; text-transform: uppercase; } #stamp { font-family: var(--font-mono); font-size: 12px; color: var(--text-secondary); background: #fff; padding: 6px 12px; border: 1px solid var(--border-color); border-radius: 2px; } -.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 18px; } +.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 18px; } .card { background: var(--card-bg); border: 1px solid var(--border-color); border-radius: 4px; padding: 18px; box-shadow: 0 4px 6px rgba(0,0,0,0.02); min-width: 0; } .card h3 { margin: 0 0 12px 0; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.2px; color: var(--accent-orange); border-bottom: 1px solid var(--border-color); padding-bottom: 8px; } .table-wrap { overflow-x: auto; width: 100%; } @@ -30,6 +30,9 @@ table { width: 100%; border-collapse: collapse; font-size: 12px; table-layout: f th { font-weight: 600; color: var(--text-secondary); padding: 10px 6px; border-bottom: 2px solid var(--border-color); font-size: 11px; text-transform: uppercase; white-space: nowrap; } td { padding: 8px 6px; border-bottom: 1px solid #eee; font-family: var(--font-mono); color: var(--text-main); vertical-align: middle; overflow: hidden; text-overflow: ellipsis; } tr:last-child td { border-bottom: none; } +tr.peer-row { cursor: help; transition: background 0.1s; } +tr.peer-row:hover { background-color: #f9f9f9; } + button { background: #fff; color: #d32f2f; border: 1px solid #d32f2f; border-radius: 3px; padding: 6px 10px; cursor: pointer; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; white-space: nowrap; } button:hover { background: #d32f2f; color: #fff; } pre { white-space: pre-wrap; margin: 0; font-family: var(--font-mono); font-size: 11px; background: #fafafa; padding: 12px; border: 1px solid var(--border-color); color: #555; max-height: 340px; overflow: auto; } @@ -46,12 +49,313 @@ svg.port-icon { width: 24px; height: 24px; } .port-icon.active .port-body { stroke: var(--network-green); } .port-icon.active .port-led { fill: var(--network-green); animation: blink-traffic 1s steps(2) infinite; } @keyframes blink-traffic { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } } + +/* Cell Phone Signal Icon Styles */ +.signal-icon-wrapper { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-left: 4px; } +svg.signal-icon { width: 16px; height: 16px; } +.signal-bar { fill: #ddd; transition: fill 0.2s; } +.signal-bar.active { fill: var(--network-green); } + .graph-container { width: 100%; height: 220px; background: #fafafa; border: 1px solid var(--border-color); border-radius: 4px; overflow: hidden; } canvas#trafficGraph { width: 100%; height: 100%; display: block; } .graph-stats { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 10px; font-family: var(--font-mono); font-size: 14px; flex-wrap: wrap; } .stat-item { display: flex; align-items: center; gap: 8px; } .stat-val { font-weight: bold; font-size: 16px; } .legend-dot { width: 10px; height: 10px; border-radius: 50%; } + +.tooltip { + position: fixed; + background: #222; + color: #fff; + padding: 10px; + border-radius: 4px; + font-size: 11px; + font-family: var(--font-mono); + z-index: 9999; + pointer-events: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + border: 1px solid #444; + max-width: 350px; + line-height: 1.5; + display: none; +} +.tooltip strong { color: var(--accent-orange); } +.tooltip-row { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 2px; } + +/* Tree Visualization Styles (Mindmap / Binary Tree Style) */ +.tree-visual { + padding: 40px 20px; + overflow: auto; + text-align: center; + min-height: 300px; + background: #fafafa; + border-radius: 4px; + border: 1px solid var(--border-color); +} +.tree-visual ul { + padding-top: 20px; + position: relative; + transition: all 0.5s; + display: flex; + justify-content: center; +} +.tree-visual li { + float: left; + text-align: center; + list-style-type: none; + position: relative; + padding: 20px 10px 0 10px; + transition: all 0.5s; +} +/* Connectors */ +.tree-visual li::before, .tree-visual li::after { + content: ''; + position: absolute; + top: 0; + right: 50%; + border-top: 1px solid #ccc; + width: 50%; + height: 20px; +} +.tree-visual li::after { + right: auto; + left: 50%; + border-left: 1px solid #ccc; +} +.tree-visual li:only-child::after, .tree-visual li:only-child::before { + display: none; +} +.tree-visual li:only-child { + padding-top: 0; +} +.tree-visual li:first-child::before, .tree-visual li:last-child::after { + border: 0 none; +} +.tree-visual li:last-child::before { + border-right: 1px solid #ccc; + border-radius: 0 5px 0 0; +} +.tree-visual li:first-child::after { + border-radius: 5px 0 0 0; +} +.tree-visual ul ul::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + border-left: 1px solid #ccc; + width: 0; + height: 20px; +} + +/* Node Styling */ +.tree-node { + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 10px 14px; + text-decoration: none; + background: #fff; + border: 1px solid #ccc; + border-radius: 6px; + transition: all 0.3s; + position: relative; + z-index: 2; + min-width: 100px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + cursor: pointer; /* Nodes are clickable */ +} +.tree-node-up { + border-color: var(--network-green); + border-top-width: 3px; +} +.tree-node-down { + border-color: #ccc; + color: #888; + opacity: 0.8; +} +.tree-node-active { + border-color: var(--accent-orange); + border-top-width: 3px; +} +.tree-node-title { + font-weight: 700; + font-size: 13px; + color: var(--text-main); + margin-bottom: 4px; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tree-node-subtitle { + font-size: 10px; + font-family: var(--font-mono); + color: var(--text-secondary); + background: #f0f0f0; + padding: 2px 6px; + border-radius: 4px; + margin-top: 4px; +} +.tree-node-ip { + font-size: 10px; + font-family: var(--font-mono); + color: var(--text-secondary); + background: #f0f0f0; + padding: 2px 6px; + border-radius: 4px; +} +.tree-node-details { + font-size: 9px; + font-family: var(--font-mono); + color: var(--text-secondary); + margin-top: 2px; + text-align: left; + width: 100%; +} +.tree-node-details div { + padding: 1px 0; +} +.tree-node:hover { + transform: scale(1.05); + z-index: 10; + box-shadow: 0 8px 16px rgba(0,0,0,0.1); +} +.tree-node-up:hover { + background: #f0fff4; +} +.tree-node-down:hover { + background: #f9f9f9; +} +.tree-node-active:hover { + background: #fff4e6; +} + +/* Path node specific styling */ +.tree-node-path { + border-color: #9c27b0; + border-top-width: 3px; +} +.tree-node-path:hover { + background: #f3e5f5; +} + +/* Expandable tree nodes */ +.tree-visual .expandable { + cursor: pointer; +} +.tree-visual .expandable::after { + content: '+'; + position: absolute; + top: -8px; + right: -8px; + background: var(--accent-orange); + color: #fff; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: bold; +} +.tree-visual .expanded::after { + content: '-'; +} +.tree-visual li.collapsed > ul { + display: none; +} + +/* Interactive Card Styles for Full Screen Feature */ +.card.interactive-card { + cursor: pointer; + transition: box-shadow 0.2s, transform 0.2s, border-color 0.2s; + position: relative; +} +.card.interactive-card:hover { + border-color: var(--accent-orange); + box-shadow: 0 8px 16px rgba(0,0,0,0.1); + transform: translateY(-2px); +} +/* Add a hint icon or visual cue */ +.card.interactive-card h3::after { + content: '⤢'; + float: right; + opacity: 0.3; + font-size: 14px; + line-height: 14px; + margin-top: 2px; +} + +/* Full Screen Modal Overlay */ +#fullScreenModal { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(255, 255, 255, 0.95); + z-index: 10000; + display: flex; + flex-direction: column; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; +} +#fullScreenModal.active { + opacity: 1; + pointer-events: auto; +} +.modal-header { + flex: 0 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 30px; + background: #fff; + border-bottom: 1px solid var(--border-color); + box-shadow: 0 2px 10px rgba(0,0,0,0.05); +} +.modal-title { + font-size: 18px; + font-weight: 700; + color: var(--text-main); + text-transform: uppercase; +} +.modal-close-btn { + background: transparent; + border: none; + font-size: 24px; + color: var(--text-secondary); + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + transition: all 0.2s; + line-height: 1; +} +.modal-close-btn:hover { + background: #f0f0f0; + color: #d32f2f; +} +.modal-body { + flex: 1 1 auto; + overflow: auto; + padding: 20px; + position: relative; + width: 100%; + height: 100%; +} +/* Ensure the tree visualizer takes full available space in modal */ +#modalBodyContent .tree-visual { + height: 100%; + width: 100%; + min-height: unset; /* Let it grow */ + border: none; + background: transparent; +} + @media (max-width: 900px) { body { padding: 12px; } .grid { grid-template-columns: 1fr; } @@ -61,18 +365,19 @@ canvas#trafficGraph { width: 100%; height: 100%; display: block; }
\{\}