From 8cdf97b74e21521effffee05abf887880e3207cd Mon Sep 17 00:00:00 2001 From: Zaine Arch Date: Sun, 22 Feb 2026 22:10:55 +0000 Subject: [PATCH] First commit --- app.js | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++++ app.js~ | 118 ++++++++++++++++++++++++ index.html | 127 ++++++++++++++++++++++++++ index.html~ | 44 +++++++++ style.css | 185 ++++++++++++++++++++++++++++++++++++++ style.css~ | 79 ++++++++++++++++ 6 files changed, 806 insertions(+) create mode 100644 app.js create mode 100644 app.js~ create mode 100644 index.html create mode 100644 index.html~ create mode 100644 style.css create mode 100644 style.css~ diff --git a/app.js b/app.js new file mode 100644 index 0000000..11ea8a7 --- /dev/null +++ b/app.js @@ -0,0 +1,253 @@ +const healthEl = document.getElementById("health"); +const globalStatus = document.getElementById("globalStatus"); +const lastUpdated = document.getElementById("lastUpdated"); + +const serverDot = document.getElementById("serverDot"); +const webDot = document.getElementById("webDot"); +const roamDot = document.getElementById("roamDot"); + +const buildBtn = document.getElementById("buildBtn"); +const buildResultEl = document.getElementById("buildResult"); +const logOutput = document.getElementById("logOutput"); + +const orgRoamBtn = document.getElementById("orgRoamBtn"); +const orgRoamResult = document.getElementById("orgRoamResult"); +const orgRoamLogs = document.getElementById("orgRoamLogs"); + +let webEventSource = null; +let roamEventSource = null; + +/* ========================================= + HEALTH +========================================= */ + +async function fetchHealth() { + try { + const res = await fetch("/api/health"); + const text = await res.text(); + + healthEl.textContent = "Online"; + healthEl.className = "badge ok"; + + serverDot.className = "dot green"; + globalStatus.className = "global-ok"; + globalStatus.textContent = "🟢 All Systems Running Normally"; + + lastUpdated.textContent = + "Last checked: " + new Date().toLocaleTimeString(); + + } catch { + healthEl.textContent = "Offline"; + healthEl.className = "badge error"; + + serverDot.className = "dot red"; + globalStatus.className = "global-error"; + globalStatus.textContent = "⚠ Attention Required"; + } +} + +// Auto refresh every 10s +setInterval(fetchHealth, 10000); + +/* ========================================= + LOG TOGGLE +========================================= */ + +document.querySelectorAll(".toggleLogs").forEach(btn => { + btn.addEventListener("click", () => { + const target = document.getElementById(btn.dataset.target); + target.classList.toggle("hidden"); + + btn.textContent = + target.classList.contains("hidden") + ? "Show technical details" + : "Hide technical details"; + }); +}); + +/* ========================================= + STREAM HELPERS +========================================= */ + +function startLogStream(url, outputEl, setter) { + const source = new EventSource(url); + + source.onmessage = (event) => { + outputEl.textContent += event.data + "\n"; + outputEl.scrollTop = outputEl.scrollHeight; + }; + + source.onerror = () => { + source.close(); + }; + + setter(source); +} + +function closeStream(source) { + if (source) source.close(); +} + +/* ========================================= + WEBSITE BUILD +========================================= */ + +async function runWebBuild() { + if (!confirm("This will update the website. Continue?")) return; + + buildBtn.disabled = true; + buildResultEl.textContent = "🔄 Updating website..."; + webDot.className = "dot yellow"; + logOutput.textContent = ""; + + try { + const res = await fetch("/api/build-web", { method: "POST" }); + if (!res.ok) throw new Error(); + + closeStream(webEventSource); + + startLogStream("/api/build-web/logs", logOutput, src => webEventSource = src); + + pollWebStatus(); + + } catch { + buildResultEl.textContent = "❌ Could not start update"; + buildBtn.disabled = false; + webDot.className = "dot red"; + } +} + +async function pollWebStatus() { + const interval = setInterval(async () => { + const res = await fetch("/api/build-web/status"); + const status = await res.json(); + + if (!status.running) { + clearInterval(interval); + closeStream(webEventSource); + buildBtn.disabled = false; + + if (status.lastExitCode === 0) { + buildResultEl.textContent = "✅ Website updated successfully"; + webDot.className = "dot green"; + } else { + buildResultEl.textContent = + "⚠ Update finished with issue (code " + status.lastExitCode + ")"; + webDot.className = "dot red"; + } + } + }, 2000); +} + +/* ========================================= + ORG ROAM BUILD +========================================= */ + +async function runOrgRoamBuild() { + if (!confirm("Refresh the knowledge base?")) return; + + orgRoamBtn.disabled = true; + orgRoamResult.textContent = "🔄 Refreshing notes..."; + roamDot.className = "dot yellow"; + orgRoamLogs.textContent = ""; + + try { + const res = await fetch("/api/build-roam", { method: "POST" }); + if (!res.ok) throw new Error(); + + closeStream(roamEventSource); + + startLogStream("/api/build-roam/logs", orgRoamLogs, src => roamEventSource = src); + + pollRoamStatus(); + + } catch { + orgRoamResult.textContent = "❌ Could not start refresh"; + orgRoamBtn.disabled = false; + roamDot.className = "dot red"; + } +} + +async function pollRoamStatus() { + const interval = setInterval(async () => { + const res = await fetch("/api/build-roam/status"); + const status = await res.json(); + + if (!status.running) { + clearInterval(interval); + closeStream(roamEventSource); + orgRoamBtn.disabled = false; + + if (status.lastExitCode === 0) { + orgRoamResult.textContent = "✅ Notes refreshed successfully"; + roamDot.className = "dot green"; + } else { + orgRoamResult.textContent = + "⚠ Refresh finished with issue (code " + status.lastExitCode + ")"; + roamDot.className = "dot red"; + } + } + }, 2000); +} + +/* ========================================= + DAILY MESSAGE +========================================= */ + +const messages = [ + "You are my favourite person.", +]; + +function setDailyMessage() { + const today = new Date().toDateString(); + const index = today.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0) % messages.length; + + document.getElementById("dailyMessage").textContent = messages[index]; +} + +setDailyMessage(); +/* ========================================= + COUNTDOWN +========================================= */ + +/* ========================================= + COUNTDOWN (LIVE - UPDATES EVERY SECOND) +========================================= */ + +function startCountdown() { + const targetDate = new Date("2026-08-03T00:00:00"); // change this + const label = "Nikah"; + + const countdownEl = document.getElementById("countdownText"); + + function updateCountdown() { + const now = new Date(); + const diff = targetDate - now; + + if (diff <= 0) { + countdownEl.textContent = `${label} is happening now! 🎉`; + clearInterval(interval); + return; + } + + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diff / (1000 * 60 * 60)) % 24); + const minutes = Math.floor((diff / (1000 * 60)) % 60); + const seconds = Math.floor((diff / 1000) % 60); + + countdownEl.textContent = + `${label} in ${days}d ${hours}h ${minutes}m ${seconds}s`; + } + + updateCountdown(); + const interval = setInterval(updateCountdown, 1000); +} + +startCountdown(); +/* ========================================= + INIT +========================================= */ + +fetchHealth(); +buildBtn.addEventListener("click", runWebBuild); +orgRoamBtn.addEventListener("click", runOrgRoamBuild); diff --git a/app.js~ b/app.js~ new file mode 100644 index 0000000..1d3239b --- /dev/null +++ b/app.js~ @@ -0,0 +1,118 @@ +const statusEl = document.getElementById("status"); +const healthEl = document.getElementById("health"); +const buildBtn = document.getElementById("buildBtn"); +const buildResultEl = document.getElementById("buildResult"); + +async function fetchStatus() { + try { + const res = await fetch("/api/status"); + const text = await res.text(); + statusEl.textContent = text; + statusEl.className = "badge ok"; + } catch { + statusEl.textContent = "ERROR"; + statusEl.className = "badge error"; + } +} + +async function fetchHealth() { + try { + const res = await fetch("/api/health"); + const text = await res.text(); + healthEl.textContent = text; + healthEl.className = "badge ok"; + } catch { + healthEl.textContent = "ERROR"; + healthEl.className = "badge error"; + } +} + +async function runBuild() { + buildBtn.disabled = true; + buildResultEl.textContent = "Running build..."; + buildResultEl.className = "muted"; + + try { + const res = await fetch("/api/build", { + method: "POST" + }); + + if (!res.ok) { + throw new Error("Build failed"); + } + + const text = await res.text(); + buildResultEl.textContent = text || "Build started successfully"; + } catch (err) { + buildResultEl.textContent = "Build failed"; + } finally { + buildBtn.disabled = false; + } +} +const logOutput = document.getElementById("logOutput"); + +function streamLogs() { + const eventSource = new EventSource("/api/build/logs"); + + eventSource.onmessage = function(event) { + logOutput.textContent += event.data + "\n"; + logOutput.scrollTop = logOutput.scrollHeight; + }; + + eventSource.onerror = function() { + eventSource.close(); + }; +} + +streamLogs(); +// Initial load +fetchStatus(); +fetchHealth(); + +// Button handler +buildBtn.addEventListener("click", runBuild); + +const orgRoamBtn = document.getElementById("orgRoamBtn"); +const orgRoamResult = document.getElementById("orgRoamResult"); +const orgRoamLogs = document.getElementById("orgRoamLogs"); + +let orgRoamEventSource = null; + +async function runOrgRoamBuild() { + + orgRoamBtn.disabled = true; + orgRoamLogs.textContent = ""; // 🔥 clear logs each run + orgRoamResult.textContent = "Running make..."; + + try { + const res = await fetch("/api/orgroam/build", { method: "POST" }); + + if (!res.ok) { + throw new Error("Build failed"); + } + + orgRoamResult.textContent = "Build started"; + + if (orgRoamEventSource) { + orgRoamEventSource.close(); + } + + orgRoamEventSource = new EventSource("/api/orgroam/logs"); + + orgRoamEventSource.onmessage = function(event) { + orgRoamLogs.textContent += event.data + "\n"; + orgRoamLogs.scrollTop = orgRoamLogs.scrollHeight; + }; + + orgRoamEventSource.onerror = function() { + orgRoamEventSource.close(); + }; + + } catch (err) { + orgRoamResult.textContent = "Build failed"; + } finally { + orgRoamBtn.disabled = false; + } +} + +orgRoamBtn.addEventListener("click", runOrgRoamBuild); diff --git a/index.html b/index.html new file mode 100644 index 0000000..7333bab --- /dev/null +++ b/index.html @@ -0,0 +1,127 @@ + + + + + Home Server Control + + + + +
+ +

🏠 Home Server Control

+
+
+

💌 Daily Message

+

+
+
+

⏳ Countdown

+

+
+ +

🚀 Quick Links

+ + +
+ +
+ 🟢 All Systems Running Normally +
+ + +
+
+ Server + +
+
+ Website + +
+
+ Org Notes + +
+
+ + +
+

📡 Server Status

+

+ Health: + Checking... +

+

+
+ + +
+

🌍 Website

+ + +

💤 Waiting for action

+ + + + +
+ + +
+

📚 Knowledge Base

+ + +

💤 Waiting for action

+ + + + +
+ +
+ + + + diff --git a/index.html~ b/index.html~ new file mode 100644 index 0000000..c9f8544 --- /dev/null +++ b/index.html~ @@ -0,0 +1,44 @@ + + + + + Server Zone + + + + +
+

Server Zone

+ + +
+

Status

+

+ Server Status: + Loading... +

+

+ Health: + Loading... +

+
+ +
+

Build

+ +

Idle

+ +

+    
+
+

Org Roam

+ +

Idle

+

+    
+ +
+ + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..3c72ba6 --- /dev/null +++ b/style.css @@ -0,0 +1,185 @@ +* { + box-sizing: border-box; + font-family: system-ui, -apple-system, monospace; +} + +body { + margin: 0; + background: #0f172a; + color: #e5e7eb; +} + +.container { + max-width: 900px; + margin: 2rem auto; + padding: 1rem; +} + +h1 { + margin-bottom: 1.5rem; +} + +.card { + background: #020617; + border: 1px solid #1e293b; + border-radius: 8px; + padding: 1.2rem; + margin-bottom: 1.2rem; + transition: 0.2s ease; +} + +.card:hover { + border-color: #334155; +} + +/* Global Banner */ + +.global-ok { + background: #064e3b; + padding: 0.8rem; + border-radius: 8px; + text-align: center; + margin-bottom: 1rem; + font-weight: bold; +} + +.global-error { + background: #7f1d1d; + padding: 0.8rem; + border-radius: 8px; + text-align: center; + margin-bottom: 1rem; + font-weight: bold; +} + +/* Overview */ + +.overview { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; +} + +.stat { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.green { background: #22c55e; } +.red { background: #ef4444; } +.yellow { background: #eab308; } + +/* Buttons */ + +button { + background: #1d4ed8; + color: white; + border: none; + padding: 0.6rem 1rem; + border-radius: 6px; + cursor: pointer; + margin-top: 0.5rem; +} + +button:disabled { + background: #475569; + cursor: not-allowed; +} + +.toggleLogs { + background: transparent; + color: #94a3b8; + margin-top: 0.5rem; +} + +/* Badges */ + +.badge { + padding: 0.2rem 0.6rem; + border-radius: 6px; + font-weight: bold; +} + +.badge.ok { + background: #166534; + color: #dcfce7; +} + +.badge.error { + background: #7f1d1d; + color: #fee2e2; +} + +.badge.neutral { + background: #334155; +} + +/* Logs */ + +.log-box { + background: #111; + color: #22c55e; + padding: 1rem; + height: 250px; + overflow-y: auto; + border-radius: 8px; + margin-top: 0.5rem; + font-size: 0.85rem; +} + +.hidden { + display: none; +} + +.muted { + color: #94a3b8; + margin-top: 0.5rem; +} + +/* Mobile */ + +@media (max-width: 600px) { + .overview { + flex-direction: column; + gap: 0.5rem; + } + + button { + width: 100%; + } +} +.quick-links { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 1rem; + margin-top: 1rem; +} + +.link-card { + background: #111827; + border: 1px solid #1e293b; + padding: 1rem; + border-radius: 10px; + text-align: center; + text-decoration: none; + color: #e5e7eb; + transition: 0.2s ease; + font-weight: 500; +} + +.link-card:hover { + background: #1e293b; + transform: translateY(-3px); +} +.daily-message { + font-size: 1.1rem; + font-style: italic; + margin-top: 0.5rem; +} diff --git a/style.css~ b/style.css~ new file mode 100644 index 0000000..543f588 --- /dev/null +++ b/style.css~ @@ -0,0 +1,79 @@ +* { + box-sizing: border-box; + font-family: system-ui, monospace; +} + +body { + margin: 0; + padding: 0; + background: #0f172a; + color: #e5e7eb; +} + +.container { + max-width: 800px; + margin: 2rem auto; + padding: 1rem; +} + +h1 { + margin-bottom: 1.5rem; +} + +.card { + background: #020617; + border: 1px solid #1e293b; + border-radius: 6px; + padding: 1rem; + margin-bottom: 1rem; +} + +button { + background: #1d4ed8; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; +} + +button:disabled { + background: #475569; + cursor: not-allowed; +} + +.badge { + padding: 0.15rem 0.5rem; + border-radius: 4px; + font-weight: bold; +} + +.badge.ok { + background: #166534; + color: #dcfce7; +} + +.badge.error { + background: #7f1d1d; + color: #fee2e2; +} + +.badge.neutral { + background: #334155; + color: #e5e7eb; +} + +.muted { + color: #94a3b8; + margin-top: 0.5rem; +} +.log-box { + margin-top: 1rem; + background: #000; + color: #00ff88; + padding: 1rem; + height: 300px; + overflow-y: auto; + font-size: 0.85rem; + border-radius: 4px; +}