First commit

This commit is contained in:
2026-02-22 22:10:55 +00:00
commit 8cdf97b74e
6 changed files with 806 additions and 0 deletions

253
app.js Normal file
View File

@@ -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);

118
app.js~ Normal file
View File

@@ -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);

127
index.html Normal file
View File

@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Home Server Control</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="container">
<h1>🏠 Home Server Control</h1>
<section class="card">
<section class="card">
<h2>💌 Daily Message</h2>
<p id="dailyMessage" class="daily-message"></p>
</section>
<section class="card">
<h2>⏳ Countdown</h2>
<p id="countdownText"></p>
</section>
<h2>🚀 Quick Links</h2>
<div class="quick-links">
<a href="https://notes.zainezq.com" target="_blank" class="link-card">
📚 Notes
</a>
<a href="https://zainezq.com" target="_blank" class="link-card">
🌍 Website
</a>
<a href="https://filebrowser.zainezq.com" target="_blank" class="link-card">
📂 Files
</a>
<a href="https://calibre.zainezq.com" target="_blank" class="link-card">
📖 Library
</a>
<a href="https://miniflux.zainezq.com" target="_blank" class="link-card">
📰 News
</a>
<a href="https://guac.zainezq.com" target="_blank" class="link-card">
🖥️ RDP
</a>
<a href="https://portainer.zainezq.com" target="_blank" class="link-card">
🐳 Portainer
</a>
<a href="https://nextcloud.zainezq.com" target="_blank" class="link-card">
☁️ Nextcloud
</a>
<a href="https://pgadmin.zainezq.com" target="_blank" class="link-card">
🗄️ Databases
</a>
</div>
</section>
<!-- Global Status -->
<div id="globalStatus" class="global-ok">
🟢 All Systems Running Normally
</div>
<!-- Overview -->
<div class="overview">
<div class="stat">
<span>Server</span>
<span id="serverDot" class="dot green"></span>
</div>
<div class="stat">
<span>Website</span>
<span id="webDot" class="dot green"></span>
</div>
<div class="stat">
<span>Org Notes</span>
<span id="roamDot" class="dot green"></span>
</div>
</div>
<!-- Health -->
<section class="card">
<h2>📡 Server Status</h2>
<p>
Health:
<span id="health" class="badge neutral">Checking...</span>
</p>
<p id="lastUpdated" class="muted"></p>
</section>
<!-- Website -->
<section class="card">
<h2>🌍 Website</h2>
<button id="buildBtn">🔄 Update Website</button>
<p id="buildResult" class="muted">💤 Waiting for action</p>
<button class="toggleLogs" data-target="logOutput">
Show technical details
</button>
<pre id="logOutput" class="log-box hidden"></pre>
</section>
<!-- Org Roam -->
<section class="card">
<h2>📚 Knowledge Base</h2>
<button id="orgRoamBtn">🔄 Refresh Notes</button>
<p id="orgRoamResult" class="muted">💤 Waiting for action</p>
<button class="toggleLogs" data-target="orgRoamLogs">
Show technical details
</button>
<pre id="orgRoamLogs" class="log-box hidden"></pre>
</section>
</main>
<script src="app.js"></script>
</body>
</html>

44
index.html~ Normal file
View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Server Zone</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="container">
<h1>Server Zone</h1>
<!-- Status Section -->
<section class="card">
<h2>Status</h2>
<p>
Server Status:
<span id="status" class="badge neutral">Loading...</span>
</p>
<p>
Health:
<span id="health" class="badge neutral">Loading...</span>
</p>
</section>
<section class="card">
<h2>Build</h2>
<button id="buildBtn">Run Build Script</button>
<p id="buildResult" class="muted">Idle</p>
<pre id="logOutput" class="log-box"></pre>
</section>
<section class="card">
<h2>Org Roam</h2>
<button id="orgRoamBtn">Run Org Roam Make</button>
<p id="orgRoamResult" class="muted">Idle</p>
<pre id="orgRoamLogs" class="log-box"></pre>
</section>
</main>
<script src="app.js"></script>
</body>
</html>

185
style.css Normal file
View File

@@ -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;
}

79
style.css~ Normal file
View File

@@ -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;
}