mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 07:41:27 +00:00
Single self-contained index.html under landing/ — no build step. Product design tokens (dark theme, Geist + Geist Mono self-hosted, #3b82f6 accent), hero with framed board screenshot, feature bento, screenshots strip, install commands with copy buttons. Deployed as Vercel project "dash" -> https://dash.vercel.app Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
466 lines
21 KiB
HTML
466 lines
21 KiB
HTML
<!doctype html>
|
|
<html lang="en" data-theme="dark">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Dash — Self-hosted homelab dashboard</title>
|
|
<meta name="description" content="Service boards, uptime monitors, domain watch, and system metrics in one container. A single static Go binary with an embedded React UI. No YAML, no external database.">
|
|
<link rel="canonical" href="https://dash.vercel.app">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:title" content="Dash — Self-hosted homelab dashboard">
|
|
<meta property="og:description" content="Service boards, uptime monitors, domain watch, and system metrics in one container. One static Go binary, no YAML.">
|
|
<meta property="og:image" content="https://dash.vercel.app/assets/board.png">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<link rel="icon" href="assets/dash.svg" type="image/svg+xml">
|
|
<style>
|
|
@font-face {
|
|
font-family: "Geist";
|
|
src: url("fonts/geist-var.woff2") format("woff2");
|
|
font-weight: 100 900;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Geist Mono";
|
|
src: url("fonts/geist-mono-var.woff2") format("woff2");
|
|
font-weight: 100 900;
|
|
font-display: swap;
|
|
}
|
|
|
|
:root {
|
|
--ease: cubic-bezier(0.2, 0, 0, 1);
|
|
--bg: #0a0a0a;
|
|
--surface: #111111;
|
|
--surface-hover: #161616;
|
|
--border: rgba(255, 255, 255, 0.08);
|
|
--border-strong: rgba(255, 255, 255, 0.14);
|
|
--text: #fafafa;
|
|
--text-dim: #a1a1aa;
|
|
--text-faint: #52525b;
|
|
--accent: #3b82f6;
|
|
--up: #22c55e;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; }
|
|
html { -webkit-font-smoothing: antialiased; scroll-behavior: smooth; }
|
|
body {
|
|
font-family: "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
min-height: 100dvh;
|
|
}
|
|
.mono { font-family: "Geist Mono", ui-monospace, monospace; }
|
|
a { color: inherit; text-decoration: none; }
|
|
img { display: block; max-width: 100%; }
|
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
|
|
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
|
|
|
|
/* ---------- reveal ---------- */
|
|
.reveal { opacity: 0; transform: translateY(14px); transition: opacity 500ms var(--ease), transform 500ms var(--ease); }
|
|
.reveal.in { opacity: 1; transform: none; }
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html { scroll-behavior: auto; }
|
|
.reveal { opacity: 1; transform: none; transition: none; }
|
|
}
|
|
|
|
/* ---------- nav ---------- */
|
|
nav {
|
|
position: sticky; top: 0; z-index: 10;
|
|
background: color-mix(in srgb, var(--bg) 80%, transparent);
|
|
backdrop-filter: blur(12px);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
nav .wrap { display: flex; align-items: center; gap: 10px; height: 56px; }
|
|
.brand { display: flex; align-items: center; gap: 9px; font-weight: 600; font-size: 14.5px; letter-spacing: -0.01em; }
|
|
.brand img { width: 22px; height: 22px; border-radius: 5px; }
|
|
.navlinks { display: flex; gap: 2px; margin-left: 18px; }
|
|
.navlinks a {
|
|
padding: 6px 10px; border-radius: 7px;
|
|
font-size: 13.5px; color: var(--text-dim);
|
|
transition: color 150ms var(--ease), background 150ms var(--ease);
|
|
}
|
|
.navlinks a:hover { color: var(--text); background: var(--surface-hover); }
|
|
.nav-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
|
|
|
|
.btn {
|
|
display: inline-flex; align-items: center; justify-content: center; gap: 7px;
|
|
height: 34px; padding: 0 14px; border-radius: 8px;
|
|
border: 1px solid var(--border); background: var(--surface); color: var(--text-dim);
|
|
font-family: inherit; font-size: 13.5px; font-weight: 500;
|
|
cursor: pointer; white-space: nowrap;
|
|
transition: color 150ms var(--ease), border-color 150ms var(--ease), background 150ms var(--ease), transform 100ms var(--ease);
|
|
}
|
|
.btn:hover { color: var(--text); border-color: var(--border-strong); }
|
|
.btn:active { transform: scale(0.97); }
|
|
.btn.primary { background: var(--text); color: var(--bg); border-color: transparent; }
|
|
.btn.primary:hover { background: #e4e4e7; color: var(--bg); }
|
|
.btn.icon { width: 34px; padding: 0; }
|
|
|
|
/* ---------- section header (product convention) ---------- */
|
|
.sec-head {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 4px 2px; margin-bottom: 26px;
|
|
}
|
|
.sec-head h2 {
|
|
font-family: "Geist Mono", ui-monospace, monospace;
|
|
font-size: 11px; font-weight: 600;
|
|
text-transform: uppercase; letter-spacing: 0.14em;
|
|
color: var(--text-dim);
|
|
}
|
|
.sec-head .line { flex: 1; height: 1px; background: var(--border); }
|
|
.sec-head .tag { font-family: "Geist Mono", ui-monospace, monospace; font-size: 11px; color: var(--text-faint); }
|
|
|
|
/* ---------- hero ---------- */
|
|
.hero { padding: 92px 0 0; }
|
|
.eyebrow {
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
font-family: "Geist Mono", ui-monospace, monospace;
|
|
font-size: 12px; color: var(--text-dim);
|
|
padding: 5px 10px; border: 1px solid var(--border); border-radius: 999px;
|
|
background: var(--surface);
|
|
}
|
|
.eyebrow .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--up); }
|
|
.hero h1 {
|
|
margin-top: 22px;
|
|
font-size: clamp(40px, 6.4vw, 68px);
|
|
font-weight: 560;
|
|
letter-spacing: -0.035em;
|
|
line-height: 1.04;
|
|
max-width: 15ch;
|
|
}
|
|
.hero p.sub {
|
|
margin-top: 20px;
|
|
font-size: 17px; line-height: 1.6;
|
|
color: var(--text-dim);
|
|
max-width: 56ch;
|
|
}
|
|
.hero .cta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 30px; }
|
|
|
|
.cmdline {
|
|
display: inline-flex; align-items: center; gap: 10px;
|
|
height: 38px; padding: 0 8px 0 14px;
|
|
background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 13px; color: var(--text-dim);
|
|
}
|
|
.cmdline code { color: var(--text); }
|
|
.copybtn {
|
|
display: flex; align-items: center; justify-content: center;
|
|
width: 26px; height: 26px; border: none; border-radius: 6px;
|
|
background: transparent; color: var(--text-faint); cursor: pointer;
|
|
transition: color 150ms var(--ease), background 150ms var(--ease);
|
|
}
|
|
.copybtn:hover { color: var(--text); background: var(--surface-hover); }
|
|
|
|
/* ---------- framed screenshot ---------- */
|
|
.frame {
|
|
margin-top: 56px;
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
background: var(--surface);
|
|
box-shadow: 0 24px 80px -24px rgba(0, 0, 0, 0.7);
|
|
}
|
|
.frame-bar {
|
|
display: flex; align-items: center; gap: 8px;
|
|
height: 36px; padding: 0 14px;
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--surface);
|
|
}
|
|
.frame-bar i { width: 9px; height: 9px; border-radius: 50%; background: var(--border-strong); }
|
|
.frame-bar .url {
|
|
margin-left: 10px;
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 11px; color: var(--text-faint);
|
|
padding: 3px 10px; border: 1px solid var(--border); border-radius: 6px;
|
|
background: var(--bg);
|
|
}
|
|
|
|
/* ---------- stat strip ---------- */
|
|
.stats {
|
|
display: grid; grid-template-columns: repeat(4, 1fr);
|
|
border: 1px solid var(--border); border-radius: 12px;
|
|
margin-top: 72px; overflow: hidden;
|
|
}
|
|
.stats div { padding: 18px 20px; border-left: 1px solid var(--border); }
|
|
.stats div:first-child { border-left: none; }
|
|
.stats .k { font-family: "Geist Mono", ui-monospace, monospace; font-size: 11px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-faint); }
|
|
.stats .v { margin-top: 7px; font-size: 15px; font-weight: 550; }
|
|
|
|
/* ---------- features bento ---------- */
|
|
section { padding: 96px 0 0; }
|
|
.bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; }
|
|
.card {
|
|
grid-column: span 3;
|
|
padding: 26px 26px 24px;
|
|
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
transition: border-color 150ms var(--ease), background 150ms var(--ease);
|
|
}
|
|
.card:hover { border-color: var(--border-strong); background: var(--surface-hover); }
|
|
.card.wide { grid-column: span 6; }
|
|
.card.third { grid-column: span 2; }
|
|
.card .label {
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 11px;
|
|
text-transform: uppercase; letter-spacing: 0.14em; color: var(--accent);
|
|
}
|
|
.card h3 { margin-top: 10px; font-size: 17px; font-weight: 560; letter-spacing: -0.01em; }
|
|
.card p { margin-top: 8px; font-size: 14px; line-height: 1.6; color: var(--text-dim); }
|
|
.card .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }
|
|
.card .chips span {
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 11px; color: var(--text-dim);
|
|
padding: 4px 9px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg);
|
|
}
|
|
.beats { display: flex; align-items: flex-end; gap: 3px; height: 30px; margin-top: 18px; }
|
|
.beats i { flex: 1; border-radius: 2px; background: var(--up); opacity: 0.85; }
|
|
.beats i.warn { background: var(--accent); }
|
|
|
|
/* ---------- screenshots ---------- */
|
|
.shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
|
|
.shot {
|
|
border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
|
|
background: var(--surface);
|
|
transition: border-color 150ms var(--ease);
|
|
}
|
|
.shot:hover { border-color: var(--border-strong); }
|
|
.shot img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; object-position: top; }
|
|
.shot figcaption {
|
|
padding: 10px 14px; border-top: 1px solid var(--border);
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 11px; color: var(--text-faint);
|
|
}
|
|
|
|
/* ---------- install ---------- */
|
|
.install-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
.term {
|
|
border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
|
|
background: var(--surface);
|
|
}
|
|
.term-head {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 10px 14px; border-bottom: 1px solid var(--border);
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 11px;
|
|
text-transform: uppercase; letter-spacing: 0.12em; color: var(--text-faint);
|
|
}
|
|
.term pre {
|
|
padding: 18px; overflow-x: auto;
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 13px; line-height: 1.8;
|
|
color: var(--text-dim);
|
|
}
|
|
.term pre b { color: var(--text); font-weight: 500; }
|
|
.term pre .c { color: var(--text-faint); }
|
|
.install-note { margin-top: 18px; font-size: 14px; color: var(--text-dim); line-height: 1.6; max-width: 62ch; }
|
|
.install-note code {
|
|
font-family: "Geist Mono", ui-monospace, monospace; font-size: 12.5px;
|
|
padding: 2px 6px; border: 1px solid var(--border); border-radius: 5px; background: var(--surface);
|
|
}
|
|
|
|
/* ---------- bottom cta ---------- */
|
|
.cta-band {
|
|
margin-top: 96px;
|
|
border: 1px solid var(--border); border-radius: 14px;
|
|
background: linear-gradient(180deg, var(--surface) 0%, var(--bg) 130%);
|
|
padding: 44px 40px;
|
|
display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap;
|
|
}
|
|
.cta-band h2 { font-size: 26px; font-weight: 560; letter-spacing: -0.02em; }
|
|
.cta-band p { margin-top: 8px; font-size: 14.5px; color: var(--text-dim); }
|
|
|
|
/* ---------- footer ---------- */
|
|
footer { margin-top: 96px; border-top: 1px solid var(--border); }
|
|
footer .wrap {
|
|
display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
|
|
padding-top: 26px; padding-bottom: 40px;
|
|
font-size: 13px; color: var(--text-faint);
|
|
}
|
|
footer .flinks { display: flex; gap: 18px; margin-left: auto; }
|
|
footer a { color: var(--text-dim); transition: color 150ms var(--ease); }
|
|
footer a:hover { color: var(--text); }
|
|
|
|
/* ---------- responsive ---------- */
|
|
@media (max-width: 860px) {
|
|
.navlinks { display: none; }
|
|
.stats { grid-template-columns: repeat(2, 1fr); }
|
|
.stats div:nth-child(3) { border-left: none; }
|
|
.stats div:nth-child(n+3) { border-top: 1px solid var(--border); }
|
|
.card, .card.third { grid-column: span 6; }
|
|
.shots { grid-template-columns: 1fr; }
|
|
.install-grid { grid-template-columns: 1fr; }
|
|
.hero { padding-top: 64px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<nav>
|
|
<div class="wrap">
|
|
<a class="brand" href="#"><img src="assets/dash.svg" alt="Dash logo">Dash</a>
|
|
<div class="navlinks">
|
|
<a href="#features">Features</a>
|
|
<a href="#screenshots">Screenshots</a>
|
|
<a href="#install">Install</a>
|
|
<a href="https://github.com/Dvorinka/Dash/tree/main/docs">Docs</a>
|
|
</div>
|
|
<div class="nav-right">
|
|
<a class="btn icon" href="https://github.com/Dvorinka/Dash" aria-label="GitHub repository">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
|
|
</a>
|
|
<a class="btn primary" href="#install">Get started</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<header class="hero">
|
|
<div class="wrap">
|
|
<span class="eyebrow reveal"><span class="dot"></span>self-hosted · open source · MIT</span>
|
|
<h1 class="reveal">One container for your whole homelab.</h1>
|
|
<p class="sub reveal">Dash is a self-hosted dashboard: service boards, uptime monitors, domain
|
|
watch, and system metrics in a single static Go binary with the UI baked in.
|
|
No YAML, no external database, no mandatory accounts.</p>
|
|
<div class="cta reveal">
|
|
<a class="btn primary" href="#install">Get started</a>
|
|
<a class="btn" href="https://github.com/Dvorinka/Dash">GitHub</a>
|
|
<span class="cmdline"><code>docker run -p 3000:3000 ghcr.io/dvorinka/dash</code>
|
|
<button class="copybtn" data-copy="docker run -d -v ./data:/data -p 3000:3000 ghcr.io/dvorinka/dash:latest" aria-label="Copy docker command">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
</button>
|
|
</span>
|
|
</div>
|
|
<div class="frame reveal">
|
|
<div class="frame-bar"><i></i><i></i><i></i><span class="url">dash.localhost:3000</span></div>
|
|
<img src="assets/board.png" alt="Dash service board with sections for media, infrastructure, and apps" width="1440" height="900">
|
|
</div>
|
|
<div class="stats reveal">
|
|
<div><div class="k">Deploy</div><div class="v">One static binary</div></div>
|
|
<div><div class="k">Database</div><div class="v">SQLite, embedded</div></div>
|
|
<div><div class="k">Config</div><div class="v">UI-managed, no YAML</div></div>
|
|
<div><div class="k">License</div><div class="v">MIT</div></div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<section id="features">
|
|
<div class="wrap">
|
|
<div class="sec-head"><h2>Features</h2><span class="line"></span><span class="tag">06</span></div>
|
|
<div class="bento">
|
|
<div class="card reveal">
|
|
<span class="label">Boards</span>
|
|
<h3>Service boards your way</h3>
|
|
<p>Sections, drag-and-drop, multi-URL services, icon upload. Four renderers
|
|
- Bento, Cards, Index, Console - share one state, so the look is a setting,
|
|
not a migration.</p>
|
|
<div class="chips"><span>bento</span><span>cards</span><span>index</span><span>console</span><span>dnd</span><span>cmd+k</span></div>
|
|
</div>
|
|
<div class="card reveal">
|
|
<span class="label">Monitors</span>
|
|
<h3>Uptime checks on a schedule</h3>
|
|
<p>HTTP, TCP, ping, DNS, keyword, JSON-path, and push checks with heartbeat
|
|
history, latency graphs, and per-monitor alert rules.</p>
|
|
<div class="beats" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i class="warn"></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
|
</div>
|
|
<div class="card third reveal">
|
|
<span class="label">Domains</span>
|
|
<h3>Domain watch</h3>
|
|
<p>RDAP/WHOIS, TLS expiry, DNS records attributed to their provider, and
|
|
CT-log subdomain discovery.</p>
|
|
</div>
|
|
<div class="card third reveal">
|
|
<span class="label">Systems</span>
|
|
<h3>System metrics</h3>
|
|
<p>A tiny <span class="mono">dash-agent</span> pushes CPU, memory, disk,
|
|
network, temps, and per-container stats.</p>
|
|
</div>
|
|
<div class="card third reveal">
|
|
<span class="label">Status</span>
|
|
<h3>Status pages</h3>
|
|
<p>Public <span class="mono">/status/:slug</span> pages, incidents,
|
|
maintenance windows, and SVG badges.</p>
|
|
</div>
|
|
<div class="card wide reveal">
|
|
<span class="label">Alerts</span>
|
|
<h3>Notified where you already are</h3>
|
|
<p>Alert rules for monitors, domains, and systems dispatch through the
|
|
transports you already run.</p>
|
|
<div class="chips"><span>webhook</span><span>slack</span><span>discord</span><span>telegram</span><span>gotify</span><span>ntfy</span><span>smtp</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="screenshots">
|
|
<div class="wrap">
|
|
<div class="sec-head"><h2>Screenshots</h2><span class="line"></span><span class="tag">03</span></div>
|
|
<div class="shots">
|
|
<figure class="shot reveal"><img src="assets/monitors.png" alt="Uptime monitors list with status, uptime percentage, and latency" width="1440" height="900" loading="lazy" decoding="async"><figcaption>uptime monitors</figcaption></figure>
|
|
<figure class="shot reveal"><img src="assets/domains.png" alt="Domain watch page with expiry countdowns and provider detection" width="1440" height="900" loading="lazy" decoding="async"><figcaption>domain watch</figcaption></figure>
|
|
<figure class="shot reveal"><img src="assets/status-page.png" alt="Public status page showing operational state of services" width="1440" height="900" loading="lazy" decoding="async"><figcaption>public status page</figcaption></figure>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="install">
|
|
<div class="wrap">
|
|
<div class="sec-head"><h2>Install</h2><span class="line"></span><span class="tag">~1 min</span></div>
|
|
<div class="install-grid">
|
|
<div class="term reveal">
|
|
<div class="term-head"><span>one-liner</span>
|
|
<button class="copybtn" data-copy="curl -fsSL https://raw.githubusercontent.com/Dvorinka/Dash/main/install.sh | sh" aria-label="Copy install command">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
</button>
|
|
</div>
|
|
<pre><b>$</b> curl -fsSL https://raw.githubusercontent.com/<br> Dvorinka/Dash/main/install.sh | sh<br><span class="c"># installs into ./dash, serves :3000</span></pre>
|
|
</div>
|
|
<div class="term reveal">
|
|
<div class="term-head"><span>docker</span>
|
|
<button class="copybtn" data-copy="docker run -d -v ./data:/data -p 3000:3000 ghcr.io/dvorinka/dash:latest" aria-label="Copy docker command">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
</button>
|
|
</div>
|
|
<pre><b>$</b> docker run -d -v ./data:/data \<br> -p 3000:3000 \<br> ghcr.io/dvorinka/dash:latest</pre>
|
|
</div>
|
|
</div>
|
|
<p class="install-note reveal">Everything is configured from the UI at
|
|
<code>http://localhost:3000</code>. All state lives in one directory - a SQLite
|
|
database plus uploaded icons. Importers for Homepage, Homarr, and Dashy
|
|
configs are built in.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="wrap">
|
|
<div class="cta-band reveal">
|
|
<div>
|
|
<h2>Run it in one command.</h2>
|
|
<p>MIT licensed. Self-hosted. Yours.</p>
|
|
</div>
|
|
<a class="btn primary" href="https://github.com/Dvorinka/Dash">Star on GitHub</a>
|
|
</div>
|
|
</div>
|
|
|
|
<footer>
|
|
<div class="wrap">
|
|
<a class="brand" href="#"><img src="assets/dash.svg" alt="" width="18" height="18" style="border-radius:4px">Dash</a>
|
|
<span>© 2026 · MIT License</span>
|
|
<div class="flinks">
|
|
<a href="https://github.com/Dvorinka/Dash">GitHub</a>
|
|
<a href="https://github.com/Dvorinka/Dash/tree/main/docs">Docs</a>
|
|
<a href="https://github.com/Dvorinka/Dash/releases">Releases</a>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
// copy buttons
|
|
for (const b of document.querySelectorAll(".copybtn")) {
|
|
b.addEventListener("click", async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(b.dataset.copy);
|
|
b.style.color = "var(--up)";
|
|
setTimeout(() => { b.style.color = ""; }, 1200);
|
|
} catch {}
|
|
});
|
|
}
|
|
// reveal on scroll
|
|
const io = new IntersectionObserver((es) => {
|
|
for (const e of es) if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); }
|
|
}, { threshold: 0.12 });
|
|
for (const el of document.querySelectorAll(".reveal")) io.observe(el);
|
|
</script>
|
|
</body>
|
|
</html>
|