Files
Catalyst/index.html
josh d7727badb1
All checks were successful
CI / test (pull_request) Successful in 14s
CI / build-dev (pull_request) Has been skipped
feat: jobs system with dedicated nav page and run history
Replaces ad-hoc Tailscale config tracking with a proper jobs system.
Jobs get their own nav page (master/detail layout), a dedicated DB
table, and full run history persisted forever. Tailscale connection
settings move from the Settings modal into the Jobs page. Registry
pattern makes adding future jobs straightforward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 19:09:42 -04:00

233 lines
9.0 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<base href="/">
<title>Catalyst</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&family=IBM+Plex+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<div id="app">
<nav>
<div class="nav-logo" onclick="navigate('dashboard')">Catalyst</div>
<div class="nav-sep"></div>
<div class="nav-status">
<div class="pulse"></div>
<span id="nav-count">— instances</span>
<span class="nav-divider">·</span>
<span id="nav-version"></span>
</div>
<button class="nav-btn" onclick="navigate('jobs')">Jobs <span id="nav-jobs-dot" class="nav-job-dot nav-job-dot--none"></span></button>
<button class="nav-btn" onclick="openSettingsModal()" title="Settings">&#9881;</button>
</nav>
<main>
<!-- DASHBOARD -->
<div id="page-dashboard" class="page">
<div class="dash-header">
<div class="dash-title">infrastructure / overview</div>
<div class="dash-headline">Instance Registry <span class="cursor-blink"></span></div>
</div>
<div class="stats-bar" id="stats-bar"></div>
<div class="toolbar">
<div class="search-wrap">
<span class="search-icon"></span>
<input type="text" id="search-input" placeholder="search instances..." oninput="filterInstances()">
</div>
<select id="filter-state" onchange="filterInstances()">
<option value="">all states</option>
<option value="deployed">deployed</option>
<option value="testing">testing</option>
<option value="degraded">degraded</option>
</select>
<select id="filter-stack" onchange="filterInstances()">
<option value="">all stacks</option>
</select>
<div class="toolbar-right">
<button class="btn primary" onclick="openNewModal()">+ new instance</button>
</div>
</div>
<div class="instance-grid" id="instance-grid"></div>
</div>
<!-- DETAIL PAGE -->
<div id="page-detail" class="page">
<div class="detail-page">
<div class="breadcrumb">
<a onclick="navigate('dashboard')">catalyst</a>
<span class="sep">/</span>
<span>instance</span>
<span class="sep">/</span>
<span id="detail-vmid-crumb"></span>
</div>
<div class="detail-header">
<div>
<div class="detail-name" id="detail-name"></div>
<div class="detail-sub">
<span><span class="lbl">vmid</span> <span class="val" id="detail-vmid-sub"></span></span>
<span><span class="lbl">created</span> <span class="val" id="detail-created-sub"></span></span>
</div>
</div>
<div class="detail-actions">
<button class="btn" id="detail-edit-btn">edit</button>
<button class="btn danger" id="detail-delete-btn">delete</button>
</div>
</div>
<div class="detail-grid">
<div class="detail-section">
<div class="section-title">identity</div>
<div id="detail-identity"></div>
</div>
<div class="detail-section">
<div class="section-title">network</div>
<div id="detail-network"></div>
</div>
<div class="detail-section full">
<div class="section-title">services</div>
<div class="services-grid" id="detail-services"></div>
</div>
<div class="detail-section full">
<div class="section-title">history</div>
<div id="detail-timestamps"></div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- MODAL: New / Edit Instance -->
<div class="modal-overlay" id="instance-modal">
<div class="modal">
<div class="modal-header">
<span class="modal-title" id="modal-title">new instance</span>
<button class="modal-close" onclick="closeModal()"></button>
</div>
<div class="modal-body">
<div class="form-row">
<div class="form-group">
<label class="form-label">name</label>
<input class="form-input" id="f-name" type="text" placeholder="plex">
</div>
<div class="form-group">
<label class="form-label">vmid</label>
<input class="form-input" id="f-vmid" type="number" placeholder="137" min="1">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">state</label>
<select class="form-input" id="f-state">
<option value="deployed">deployed</option>
<option value="testing">testing</option>
<option value="degraded">degraded</option>
</select>
</div>
<div class="form-group">
<label class="form-label">stack</label>
<select class="form-input" id="f-stack">
<option value="production">production</option>
<option value="development">development</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">tailscale ip</label>
<input class="form-input" id="f-tailscale-ip" type="text" placeholder="100.x.x.x">
</div>
<div class="form-group">
<label class="form-label">services</label>
<div class="toggle-grid">
<div class="form-check"><input type="checkbox" id="f-atlas"><label for="f-atlas">atlas</label></div>
<div class="form-check"><input type="checkbox" id="f-argus"><label for="f-argus">argus</label></div>
<div class="form-check"><input type="checkbox" id="f-semaphore"><label for="f-semaphore">semaphore</label></div>
<div class="form-check"><input type="checkbox" id="f-patchmon"><label for="f-patchmon">patchmon</label></div>
<div class="form-check"><input type="checkbox" id="f-tailscale"><label for="f-tailscale">tailscale</label></div>
<div class="form-check"><input type="checkbox" id="f-andromeda"><label for="f-andromeda">andromeda</label></div>
<div class="form-check"><input type="checkbox" id="f-hardware-accel"><label for="f-hardware-accel">hw acceleration</label></div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn" onclick="closeModal()">cancel</button>
<button class="btn primary" onclick="saveInstance()">save instance</button>
</div>
</div>
</div>
<!-- CONFIRM DIALOG -->
<div class="confirm-overlay" id="confirm-overlay">
<div class="confirm-box">
<div class="confirm-title" id="confirm-title">confirm action</div>
<div class="confirm-msg" id="confirm-msg"></div>
<div class="confirm-actions">
<button class="btn" onclick="closeConfirm()">cancel</button>
<button class="btn danger" id="confirm-ok">confirm delete</button>
</div>
</div>
</div>
<!-- JOBS PAGE -->
<div class="page" id="page-jobs">
<div class="jobs-layout">
<div class="jobs-sidebar">
<div class="jobs-sidebar-title">Jobs</div>
<div id="jobs-list"></div>
</div>
<div class="jobs-detail" id="jobs-detail">
<div class="jobs-placeholder">Select a job</div>
</div>
</div>
</div>
<!-- SETTINGS MODAL -->
<div id="settings-modal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<span class="modal-title">Settings</span>
<button class="modal-close" onclick="closeSettingsModal()">&#x2715;</button>
</div>
<div class="modal-body">
<div class="settings-section">
<div class="settings-section-title">Display</div>
<div class="settings-row">
<label class="settings-label" for="tz-select">Timezone</label>
<select id="tz-select" class="form-input settings-select"></select>
</div>
</div>
<div class="settings-section">
<div class="settings-section-title">Export</div>
<p class="settings-desc">Download all instance data as a JSON backup file.</p>
<button class="btn btn-secondary" onclick="exportDB()">Export Database</button>
</div>
<div class="settings-section">
<div class="settings-section-title">Import</div>
<p class="settings-desc">Restore from a backup file. This replaces all current instances.</p>
<div class="import-row">
<input type="file" id="import-file" accept=".json" class="form-input import-file-input">
<button class="btn btn-danger" onclick="importDB()">Import</button>
</div>
</div>
</div>
</div>
</div>
<!-- TOAST -->
<div class="toast" id="toast">
<div class="toast-dot"></div>
<span id="toast-msg"></span>
</div>
<script src="js/version.js" onerror="window.VERSION=null"></script>
<script src="js/config.js"></script>
<script src="js/db.js"></script>
<script src="js/ui.js"></script>
<script src="js/app.js"></script>
</body>
</html>