Files
web-dashboard/index.html
T

633 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Myron's Dashboard</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--surface: #1a1d27;
--surface2: #22263a;
--border: #2e3350;
--accent: #4f8ef7;
--accent2: #7c5af7;
--green: #22c55e;
--yellow: #eab308;
--red: #ef4444;
--orange: #f97316;
--pink: #ec4899;
--cyan: #06b6d4;
--text: #e2e4f0;
--muted: #8b90a8;
--radius: 12px;
}
body {
background: var(--bg);
color: var(--text);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
min-height: 100vh;
padding: 2rem 1.5rem;
}
header {
text-align: center;
margin-bottom: 2.5rem;
}
header h1 {
font-size: 2rem;
font-weight: 700;
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.5px;
}
header p {
color: var(--muted);
font-size: 0.85rem;
margin-top: 0.35rem;
letter-spacing: 0.5px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.25rem;
max-width: 1400px;
margin: 0 auto;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
transition: border-color 0.2s, transform 0.2s;
}
.card:hover { border-color: var(--accent); transform: translateY(-2px); }
.card-header {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.85rem 1rem;
background: var(--surface2);
border-bottom: 1px solid var(--border);
}
.card-header .icon {
font-size: 1.1rem;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
flex-shrink: 0;
}
.card-header h2 {
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--muted);
}
.links { padding: 0.5rem 0; }
.link-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.6rem 1rem;
text-decoration: none;
color: var(--text);
font-size: 0.9rem;
transition: background 0.15s;
border-radius: 0;
}
.link-item:hover { background: var(--surface2); }
.link-item .dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.link-item .name { flex: 1; font-weight: 500; }
.link-item .sub {
font-size: 0.75rem;
color: var(--muted);
}
.link-item .arrow {
color: var(--muted);
font-size: 0.7rem;
opacity: 0;
transition: opacity 0.15s;
}
.link-item:hover .arrow { opacity: 1; }
.dot-blue { background: var(--accent); }
.dot-purple { background: var(--accent2); }
.dot-green { background: var(--green); }
.dot-yellow { background: var(--yellow); }
.dot-red { background: var(--red); }
.dot-orange { background: var(--orange); }
.dot-pink { background: var(--pink); }
.dot-cyan { background: var(--cyan); }
.dot-gray { background: var(--muted); }
.icon-blue { background: rgba(79,142,247,0.15); }
.icon-purple { background: rgba(124,90,247,0.15); }
.icon-green { background: rgba(34,197,94,0.15); }
.icon-yellow { background: rgba(234,179,8,0.15); }
.icon-orange { background: rgba(249,115,22,0.15); }
.icon-pink { background: rgba(236,72,153,0.15); }
.icon-cyan { background: rgba(6,182,212,0.15); }
.icon-red { background: rgba(239,68,68,0.15); }
.divider {
height: 1px;
background: var(--border);
margin: 0.25rem 1rem;
}
@media (max-width: 600px) {
body { padding: 1rem; }
.grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header>
<h1>⚡ Blair HQ</h1>
<p id="clock"></p>
</header>
<div class="grid">
<!-- INFRASTRUCTURE -->
<div class="card">
<div class="card-header">
<div class="icon icon-blue">🖥️</div>
<h2>Infrastructure</h2>
</div>
<div class="links">
<a class="link-item" href="https://10.48.200.90:8006" target="_blank">
<span class="dot dot-blue"></span><span class="name">Proxmox PVE1</span><span class="sub">Primary</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://10.48.200.91:8006" target="_blank">
<span class="dot dot-blue"></span><span class="name">Proxmox PVE2</span><span class="sub">Secondary</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.201:81" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Nginx Proxy Manager</span><span class="sub">10.48.200.201</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="http://10.48.200.210:11434" target="_blank">
<span class="dot dot-purple"></span><span class="name">Ollama</span><span class="sub">Local LLM</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.249:5000" target="_blank">
<span class="dot dot-gray"></span><span class="name">Synology NAS</span><span class="sub">10.48.200.249</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- JARVIS -->
<div class="card">
<div class="card-header">
<div class="icon icon-blue">🤖</div>
<h2>JARVIS</h2>
</div>
<div class="links">
<a class="link-item" href="https://jarvis.orbishosting.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">JARVIS Dashboard</span><span class="sub">Main UI</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://jarvis.orbishosting.com/admin" target="_blank">
<span class="dot dot-purple"></span><span class="name">JARVIS Admin</span><span class="sub">Admin panel</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.211/phpmyadmin" target="_blank">
<span class="dot dot-orange"></span><span class="name">phpMyAdmin</span><span class="sub">JARVIS DB</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- HOME AUTOMATION -->
<div class="card">
<div class="card-header">
<div class="icon icon-green">🏠</div>
<h2>Home Automation</h2>
</div>
<div class="links">
<a class="link-item" href="https://hoa.orbishosting.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Home Assistant</span><span class="sub">hoa.orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.18:8581" target="_blank">
<span class="dot dot-orange"></span><span class="name">HomeBridge</span><span class="sub">10.48.200.18</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.97:8123" target="_blank">
<span class="dot dot-cyan"></span><span class="name">HA (Direct LAN)</span><span class="sub">10.48.200.97</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- MEDIA -->
<div class="card">
<div class="card-header">
<div class="icon icon-purple">🎬</div>
<h2>Media</h2>
</div>
<div class="links">
<a class="link-item" href="http://10.48.200.33:8096" target="_blank">
<span class="dot dot-purple"></span><span class="name">Jellyfin</span><span class="sub">Media server</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:8989" target="_blank">
<span class="dot dot-blue"></span><span class="name">Sonarr</span><span class="sub">TV shows</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:7878" target="_blank">
<span class="dot dot-yellow"></span><span class="name">Radarr</span><span class="sub">Movies</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:9696" target="_blank">
<span class="dot dot-orange"></span><span class="name">Prowlarr</span><span class="sub">Indexers</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.35:8080" target="_blank">
<span class="dot dot-green"></span><span class="name">qBittorrent</span><span class="sub">Downloads</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- HOSTING PANEL -->
<div class="card">
<div class="card-header">
<div class="icon icon-cyan">⚙️</div>
<h2>NovaCPX Panel</h2>
</div>
<div class="links">
<a class="link-item" href="https://admin.novacpx.orbishosting.com" target="_blank">
<span class="dot dot-red"></span><span class="name">Admin Panel</span><span class="sub">Root access</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://reseller.novacpx.orbishosting.com" target="_blank">
<span class="dot dot-orange"></span><span class="name">Reseller Panel</span><span class="sub">Reseller access</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://panel.novacpx.orbishosting.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Client Panel</span><span class="sub">End user access</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://novacpx.orbishosting.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">Client Panel (alt)</span><span class="sub">novacpx.orbishosting.com</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- WEBSITES -->
<div class="card">
<div class="card-header">
<div class="icon icon-pink">🌐</div>
<h2>Websites</h2>
</div>
<div class="links">
<a class="link-item" href="https://orbishosting.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">Orbis Hosting</span><span class="sub">orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://orbis.orbishosting.com" target="_blank">
<span class="dot dot-cyan"></span><span class="name">Orbis Portal</span><span class="sub">orbis.orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://tomsjavajive.com" target="_blank">
<span class="dot dot-yellow"></span><span class="name">Tom's Java Jive</span><span class="sub">tomsjavajive.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://tomtomgames.com" target="_blank">
<span class="dot dot-purple"></span><span class="name">Tom Tom Games</span><span class="sub">tomtomgames.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://epictravelexpeditions.com" target="_blank">
<span class="dot dot-green"></span><span class="name">Epic Travel</span><span class="sub">epictravelexpeditions.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://parkerslingshotrentals.com" target="_blank">
<span class="dot dot-red"></span><span class="name">Parker Slingshot Rentals</span><span class="sub">parkerslingshotrentals.com</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- COMMUNICATIONS -->
<div class="card">
<div class="card-header">
<div class="icon icon-orange">📞</div>
<h2>Communications</h2>
</div>
<div class="links">
<a class="link-item" href="https://fusion.orbishosting.com" target="_blank">
<span class="dot dot-orange"></span><span class="name">FusionPBX</span><span class="sub">fusion.orbishosting.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://fusion.orbishosting.com/app/settings" target="_blank">
<span class="dot dot-yellow"></span><span class="name">PBX Settings</span><span class="sub">Extensions &amp; trunks</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- NETWORKING -->
<div class="card">
<div class="card-header">
<div class="icon icon-yellow">🔒</div>
<h2>Networking</h2>
</div>
<div class="links">
<a class="link-item" href="https://10.48.200.1" target="_blank">
<span class="dot dot-yellow"></span><span class="name">FortiGate</span><span class="sub">10.48.200.1</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.80" target="_blank">
<span class="dot dot-cyan"></span><span class="name">WB610H Bridge</span><span class="sub">10.48.200.80</span><span class="arrow"></span>
</a>
<div class="divider"></div>
<a class="link-item" href="http://10.48.200.250" target="_blank">
<span class="dot dot-green"></span><span class="name">Goalake Switch 1</span><span class="sub">10.48.200.250</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.251" target="_blank">
<span class="dot dot-green"></span><span class="name">Goalake Switch 2</span><span class="sub">10.48.200.251</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://10.48.200.19" target="_blank">
<span class="dot dot-green"></span><span class="name">WireGuard CT</span><span class="sub">10.48.200.19</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://login.tailscale.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">Tailscale</span><span class="sub">login.tailscale.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://dash.cloudflare.com" target="_blank">
<span class="dot dot-orange"></span><span class="name">Cloudflare</span><span class="sub">DNS &amp; CDN</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- GITHUB -->
<div class="card">
<div class="card-header">
<div class="icon icon-purple">🐙</div>
<h2>GitHub Repos</h2>
</div>
<div class="links">
<a class="link-item" href="https://github.com/myronblair/novacpx" target="_blank">
<span class="dot dot-cyan"></span><span class="name">NovaCPX</span><span class="sub">myronblair/novacpx</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/jarvis" target="_blank">
<span class="dot dot-blue"></span><span class="name">JARVIS</span><span class="sub">myronblair/jarvis</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair/infra" target="_blank">
<span class="dot dot-gray"></span><span class="name">Infra</span><span class="sub">myronblair/infra</span><span class="arrow"></span>
</a>
<a class="link-item" href="https://github.com/myronblair" target="_blank">
<span class="dot dot-purple"></span><span class="name">All Repos</span><span class="sub">github.com/myronblair</span><span class="arrow"></span>
</a>
</div>
</div>
<!-- DO SERVER -->
<div class="card">
<div class="card-header">
<div class="icon icon-blue">🌊</div>
<h2>DigitalOcean</h2>
</div>
<div class="links">
<a class="link-item" href="https://cloud.digitalocean.com" target="_blank">
<span class="dot dot-blue"></span><span class="name">DO Dashboard</span><span class="sub">cloud.digitalocean.com</span><span class="arrow"></span>
</a>
<a class="link-item" href="http://165.22.1.228:8090" target="_blank">
<span class="dot dot-cyan"></span><span class="name">CyberPanel</span><span class="sub">165.22.1.228:8090</span><span class="arrow"></span>
</a>
</div>
</div>
</div>
<script>
function tick() {
const now = new Date();
document.getElementById('clock').textContent =
now.toLocaleDateString('en-US', {weekday:'long',year:'numeric',month:'long',day:'numeric'}) +
' · ' +
now.toLocaleTimeString('en-US', {hour:'2-digit',minute:'2-digit',second:'2-digit'});
}
tick(); setInterval(tick, 1000);
// ── Notes — server-backed so they sync across all devices ─────────────
const API = '/notes.php';
let _notes = [];
function escHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>');
}
async function notesApi(action, body = {}) {
try {
const r = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, ...body }),
credentials: 'same-origin',
});
return await r.json();
} catch (e) { return { ok: false }; }
}
async function fetchNotes() {
const el = document.getElementById('notes-list');
if (el && !_notes.length) el.innerHTML = '<div style="color:#8b90a8;font-size:.85rem">Loading…</div>';
try {
const r = await fetch(API + '?action=list', { credentials: 'same-origin' });
const d = await r.json();
_notes = d.notes || [];
} catch (e) { _notes = []; }
renderNotes();
}
function renderNotes() {
const list = document.getElementById('notes-list');
if (!list) return;
if (!_notes.length) {
list.innerHTML = '<div style="color:#8b90a8;font-size:.85rem;padding:.5rem 0">No notes yet. Add one above.</div>';
return;
}
list.innerHTML = _notes.map(n => `
<div class="note-item ${n.done ? 'note-done' : ''}">
<button class="note-check" onclick="toggleNote('${n.id}')" title="${n.done ? 'Mark incomplete' : 'Mark done'}">
${n.done ? '✅' : '<span class="note-circle"></span>'}
</button>
<div class="note-body">
<div class="note-text">${escHtml(n.text)}</div>
${n.detail ? `<div class="note-detail">${escHtml(n.detail)}</div>` : ''}
<div class="note-meta">${new Date(n.ts).toLocaleString('en-US',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'})}</div>
</div>
<button class="note-del" onclick="deleteNote('${n.id}')" title="Delete">✕</button>
</div>`).join('');
}
async function addNote() {
const txt = document.getElementById('note-input').value.trim();
const det = document.getElementById('note-detail').value.trim();
if (!txt) { document.getElementById('note-input').focus(); return; }
const btn = document.querySelector('.btn-note-add');
btn.disabled = true;
const r = await notesApi('add', { text: txt, detail: det });
btn.disabled = false;
if (r.ok) {
_notes.unshift(r.note);
document.getElementById('note-input').value = '';
document.getElementById('note-detail').value = '';
document.getElementById('note-detail').style.display = 'none';
document.getElementById('note-detail-toggle').textContent = '+ Add details';
renderNotes();
}
}
async function toggleNote(id) {
_notes = _notes.map(n => n.id === id ? {...n, done: !n.done} : n);
renderNotes();
await notesApi('toggle', { id });
}
async function deleteNote(id) {
_notes = _notes.filter(n => n.id !== id);
renderNotes();
await notesApi('delete', { id });
}
async function clearDone() {
_notes = _notes.filter(n => !n.done);
renderNotes();
await notesApi('clear-done');
}
function toggleDetail() {
const d = document.getElementById('note-detail');
const btn = document.getElementById('note-detail-toggle');
const show = d.style.display === 'none';
d.style.display = show ? 'block' : 'none';
btn.textContent = show ? ' Hide details' : '+ Add details';
if (show) d.focus();
}
document.addEventListener('DOMContentLoaded', () => {
fetchNotes();
// Re-sync when tab becomes visible (switching back from another device)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') fetchNotes();
});
document.getElementById('note-input').addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); addNote(); }
});
});
</script>
<!-- ── NOTES SECTION ─────────────────────────────────────────────────────── -->
<style>
.notes-wrap {
max-width:1400px; margin:2rem auto 0; padding:0 1.5rem 3rem;
}
.notes-header {
display:flex; align-items:center; justify-content:space-between;
margin-bottom:1.25rem;
}
.notes-title {
font-size:.75rem; font-weight:700; text-transform:uppercase;
letter-spacing:1.5px; color:#8b90a8;
}
.notes-add {
background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius); padding:1rem; margin-bottom:1rem;
}
.notes-add-row {
display:flex; gap:.6rem; align-items:center;
}
#note-input {
flex:1; background:var(--surface2); border:1px solid var(--border);
border-radius:6px; color:var(--text); padding:.6rem .85rem;
font-size:.9rem; font-family:inherit; outline:none;
transition:border-color .15s;
}
#note-input:focus { border-color:var(--accent); }
#note-detail {
width:100%; margin-top:.6rem; background:var(--surface2);
border:1px solid var(--border); border-radius:6px; color:var(--text);
padding:.6rem .85rem; font-size:.85rem; font-family:inherit;
outline:none; resize:vertical; min-height:70px;
transition:border-color .15s; display:none;
}
#note-detail:focus { border-color:var(--accent); }
.btn-note-add {
background:var(--accent); color:#fff; border:none; border-radius:6px;
padding:.6rem 1.1rem; font-size:.85rem; cursor:pointer; font-weight:600;
white-space:nowrap; transition:opacity .15s;
}
.btn-note-add:hover { opacity:.85; }
#note-detail-toggle {
background:none; border:none; color:#8b90a8; font-size:.78rem;
cursor:pointer; padding:.25rem 0; margin-top:.4rem;
transition:color .15s;
}
#note-detail-toggle:hover { color:var(--text); }
.note-item {
display:flex; align-items:flex-start; gap:.75rem;
padding:.8rem .75rem; border-radius:8px;
background:var(--surface); border:1px solid var(--border);
margin-bottom:.5rem; transition:opacity .2s, border-color .2s;
}
.note-item:hover { border-color:var(--accent); }
.note-done { opacity:.55; }
.note-check {
background:none; border:none; cursor:pointer; padding:.1rem;
font-size:1.1rem; flex-shrink:0; margin-top:.1rem;
}
.note-circle {
display:inline-block; width:18px; height:18px; border-radius:50%;
border:2px solid #8b90a8; vertical-align:middle;
transition:border-color .15s;
}
.note-item:hover .note-circle { border-color:var(--accent); }
.note-body { flex:1; min-width:0; }
.note-text {
font-size:.9rem; color:var(--text); line-height:1.45;
word-break:break-word;
}
.note-done .note-text { text-decoration:line-through; color:#8b90a8; }
.note-detail {
font-size:.8rem; color:#8b90a8; margin-top:.3rem; line-height:1.5;
}
.note-meta {
font-size:.72rem; color:#5a5f78; margin-top:.35rem;
}
.note-del {
background:none; border:none; color:#5a5f78; cursor:pointer;
font-size:.85rem; padding:.1rem .2rem; flex-shrink:0;
opacity:0; transition:opacity .15s, color .15s;
}
.note-item:hover .note-del { opacity:1; }
.note-del:hover { color:#ef4444; }
.btn-clear {
background:none; border:1px solid var(--border); color:#8b90a8;
border-radius:5px; padding:.3rem .7rem; font-size:.75rem;
cursor:pointer; transition:all .15s;
}
.btn-clear:hover { border-color:#ef4444; color:#ef4444; }
@media(max-width:600px){
.notes-wrap { padding:0 1rem 2rem; }
.notes-add-row { flex-direction:column; align-items:stretch; }
}
</style>
<div class="notes-wrap">
<div class="notes-header">
<div class="notes-title">📝 Notes</div>
<button class="btn-clear" onclick="clearDone()">Clear completed</button>
</div>
<div class="notes-add">
<div class="notes-add-row">
<input id="note-input" type="text" placeholder="Add a note — press Enter to save…">
<button class="btn-note-add" onclick="addNote()">Add</button>
</div>
<button id="note-detail-toggle" onclick="toggleDetail()">+ Add details</button>
<textarea id="note-detail" placeholder="Additional details…"></textarea>
</div>
<div id="notes-list"></div>
</div>
</body>
</html>