Diff -- /var/www/vhosts/3dshawn.com/site1/TEMPLATES/servers.html
Diff
/var/www/vhosts/3dshawn.com/site1/TEMPLATES/servers.html
modified on local at 2026-07-11 23:15:14
Added
+260
lines
Removed
-17
lines
Context
18
unchanged
Blobs
from a9369d53baac
to b85517f9ee2e
to b85517f9ee2e
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | 1 | <div class="page-head"> |
| 2 | 2 | <span class="page-eyebrow"><span class="dot"></span> Configure</span> |
| 3 | <h1 class="page-title">Servers & agents</h1> | |
| 4 | <p class="page-subtitle">The nodes DriftSense knows about. In a multi-server install, remote agents phone home to this central node over HMAC-signed HTTP and appear here on first heartbeat.</p> | |
| 3 | <h1 class="page-title">Servers & agents</h1> | |
| 4 | <p class="page-subtitle">The nodes DriftSense knows about — local (this box) and SSH agents (remote servers scanned over a restricted-key SSH connection). Add one at a time or bulk-import a list of hostnames sharing a single key.</p> | |
| 5 | </div> | |
| 6 | ||
| 7 | [if:$has_imported] | |
| 8 | <div class="banner banner-ok" style="margin:0 0 16px;padding:10px 14px;border-radius:10px;background:rgba(52,211,153,.10);border:1px solid rgba(52,211,153,.35);color:#a7f3d0;font-size:13px"> | |
| 9 | Bulk import created $imported server(s). They'll be scanned on the next agent tick (within 5 minutes). | |
| 10 | </div> | |
| 11 | [/if] | |
| 12 | ||
| 13 | [if:!$crypto_ready] | |
| 14 | <div class="banner banner-warn" style="margin:0 0 16px;padding:10px 14px;border-radius:10px;background:rgba(245,158,11,.10);border:1px solid rgba(245,158,11,.35);color:#fcd34d;font-size:13px"> | |
| 15 | <strong>Master encryption key missing.</strong> Encrypted key storage requires <code>/etc/drift_sense/master.key</code>. Run on the server: <code>sudo perl -e 'use lib "/var/www/vhosts/3dshawn.com/site1"; use MODS::Crypto; MODS::Crypto::ensure_master()'</code>. Until then, encrypted-blob storage is unavailable and only on-disk <code>ssh_key_path</code> works. | |
| 16 | </div> | |
| 17 | [/if] | |
| 18 | ||
| 19 | <!-- ============================ TOP TABS ============================ --> | |
| 20 | <div style="display:flex;gap:8px;margin-bottom:16px"> | |
| 21 | <a class="tr-chip[if:!$bulk_mode] is-active[/if]" href="/servers.cgi">Single-server form</a> | |
| 22 | <a class="tr-chip[if:$bulk_mode] is-active[/if]" href="/servers.cgi?bulk=1">Bulk import</a> | |
| 23 | </div> | |
| 24 | ||
| 25 | <div class="dash-grid" style="grid-template-columns:1fr 1fr;margin-bottom:20px"> | |
| 26 | <!-- ============================ SINGLE-SERVER FORM ============================ --> | |
| 27 | [if:!$bulk_mode] | |
| 28 | <div class="module glow-teal"> | |
| 29 | <div class="module-head"><div class="left"> | |
| 30 | <div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/></svg></div> | |
| 31 | <div class="module-title">[if:$is_edit]Edit server #$server_id[/if][if:!$is_edit]Add server[/if]</div> | |
| 32 | <span class="module-subtitle">Pick <code>local</code> for the DriftSense host itself, or <code>ssh_agent</code> for anything remote.</span> | |
| 33 | </div></div> | |
| 34 | <div class="module-body"> | |
| 35 | <form method="post" action="/servers.cgi" style="display:flex;flex-direction:column;gap:12px"> | |
| 36 | <input type="hidden" name="op" value="$form_op"> | |
| 37 | [if:$is_edit]<input type="hidden" name="server_id" value="$server_id">[/if] | |
| 38 | ||
| 39 | <div style="display:flex;gap:12px"> | |
| 40 | <label style="flex:2;display:flex;flex-direction:column;gap:4px"> | |
| 41 | <span class="rb-label">Server name</span> | |
| 42 | <input type="text" name="server_name" value="$server_name" required placeholder="e.g. prod-web-1" | |
| 43 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:inherit"> | |
| 44 | </label> | |
| 45 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> | |
| 46 | <span class="rb-label">Kind</span> | |
| 47 | <select name="kind" style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px"> | |
| 48 | <option value="local" $k_local_sel>local</option> | |
| 49 | <option value="ssh_agent" $k_ssh_sel>ssh_agent</option> | |
| 50 | </select> | |
| 51 | </label> | |
| 52 | </div> | |
| 53 | ||
| 54 | <div style="display:flex;gap:12px"> | |
| 55 | <label style="flex:2;display:flex;flex-direction:column;gap:4px"> | |
| 56 | <span class="rb-label">SSH host</span> | |
| 57 | <input type="text" name="ssh_host" value="$ssh_host" placeholder="e.g. prod-web-1.internal or 10.0.0.42" | |
| 58 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px"> | |
| 59 | </label> | |
| 60 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> | |
| 61 | <span class="rb-label">User</span> | |
| 62 | <input type="text" name="ssh_user" value="$ssh_user" | |
| 63 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px"> | |
| 64 | </label> | |
| 65 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> | |
| 66 | <span class="rb-label">Port</span> | |
| 67 | <input type="number" name="ssh_port" value="$ssh_port" min="1" max="65535" | |
| 68 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px"> | |
| 69 | </label> | |
| 70 | </div> | |
| 71 | ||
| 72 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 73 | <span class="rb-label">SSH key path <span class="dim" style="text-transform:none;letter-spacing:0;font-size:10.5px">(on-disk fallback if you don't paste a key below)</span></span> | |
| 74 | <input type="text" name="ssh_key_path" value="$ssh_key_path" placeholder="/etc/drift_sense/keys/agent_key" | |
| 75 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px"> | |
| 76 | </label> | |
| 77 | ||
| 78 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 79 | <span class="rb-label"> | |
| 80 | Paste PEM key (encrypted at rest with master key) | |
| 81 | [if:$has_key_blob]<span class="pill" style="background:rgba(52,211,153,.14);color:#86efac;border-color:rgba(52,211,153,.35);margin-left:6px">key already stored</span>[/if] | |
| 82 | </span> | |
| 83 | <textarea name="ssh_key_blob_plain" rows="4" placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY-----" | |
| 84 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:var(--mono);font-size:11.5px;resize:vertical"></textarea> | |
| 85 | </label> | |
| 86 | ||
| 87 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 88 | <span class="rb-label">Extra SSH options <span class="dim" style="text-transform:none;letter-spacing:0;font-size:10.5px">(optional, space-separated <code>-o key=value</code> pairs)</span></span> | |
| 89 | <input type="text" name="ssh_options" value="$ssh_options" placeholder="e.g. IdentitiesOnly=yes ProxyJump=bastion.internal" | |
| 90 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:11.5px"> | |
| 91 | </label> | |
| 92 | ||
| 93 | <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:6px"> | |
| 94 | [if:$is_edit]<a href="/servers.cgi" class="tr-chip">Cancel</a>[/if] | |
| 95 | <button type="submit" class="tr-chip is-active" style="border:none">[if:$is_edit]Save[/if][if:!$is_edit]Add server[/if]</button> | |
| 96 | </div> | |
| 97 | </form> | |
| 98 | </div> | |
| 99 | </div> | |
| 100 | [/if] | |
| 101 | ||
| 102 | <!-- ============================ BULK IMPORT ============================ --> | |
| 103 | [if:$bulk_mode] | |
| 104 | <div class="module glow-emerald" style="grid-column:1 / span 2"> | |
| 105 | <div class="module-head"><div class="left"> | |
| 106 | <div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg></div> | |
| 107 | <div class="module-title">Bulk import</div> | |
| 108 | <span class="module-subtitle">Paste one hostname per line + one shared SSH key + a base scan config. DriftSense fans out.</span> | |
| 109 | </div></div> | |
| 110 | <div class="module-body"> | |
| 111 | <form method="post" action="/servers.cgi" style="display:flex;flex-direction:column;gap:14px"> | |
| 112 | <input type="hidden" name="op" value="bulk_import"> | |
| 113 | ||
| 114 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 115 | <span class="rb-label">Hostnames — one per line, optional <code>label|host</code> or <code>label=host</code> format</span> | |
| 116 | <textarea name="hosts" rows="7" placeholder="prod-web-1.internal prod-web-2 = 10.0.0.11 webstls | webstls.com staging | staging.internal" | |
| 117 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:var(--mono);font-size:12.5px;resize:vertical" required></textarea> | |
| 118 | </label> | |
| 119 | ||
| 120 | <div style="display:flex;gap:12px"> | |
| 121 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> | |
| 122 | <span class="rb-label">User</span> | |
| 123 | <input type="text" name="ssh_user" value="root" | |
| 124 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px"> | |
| 125 | </label> | |
| 126 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> | |
| 127 | <span class="rb-label">Port</span> | |
| 128 | <input type="number" name="ssh_port" value="22" | |
| 129 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px"> | |
| 130 | </label> | |
| 131 | </div> | |
| 132 | ||
| 133 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 134 | <span class="rb-label">Shared PEM key (encrypted at rest) — optional if all hosts use <code>ssh_key_path</code></span> | |
| 135 | <textarea name="ssh_key_blob_plain" rows="4" placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY-----" | |
| 136 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-family:var(--mono);font-size:11.5px;resize:vertical"></textarea> | |
| 137 | </label> | |
| 138 | ||
| 139 | <hr style="border:none;border-top:1px solid var(--border);margin:4px 0"> | |
| 140 | ||
| 141 | <div class="rb-label" style="margin-bottom:-6px">Base scan config — applied to every host</div> | |
| 142 | ||
| 143 | <label style="display:flex;flex-direction:column;gap:4px"> | |
| 144 | <span class="rb-label">Scan path</span> | |
| 145 | <input type="text" name="scan_path" value="/etc" placeholder="/etc or /var/www" | |
| 146 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:12px" required> | |
| 147 | </label> | |
| 148 | ||
| 149 | <div style="display:flex;gap:12px"> | |
| 150 | <label style="flex:2;display:flex;flex-direction:column;gap:4px"> | |
| 151 | <span class="rb-label">Ignore globs (comma-separated)</span> | |
| 152 | <input type="text" name="ignore_list" value="*.log,*.pid,*.sock,*.tmp,tmp/*,cache/*,logs/*,.git/*,node_modules/*" | |
| 153 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:11.5px"> | |
| 154 | </label> | |
| 155 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> | |
| 156 | <span class="rb-label">File-type filter <span class="dim" style="text-transform:none;letter-spacing:0;font-size:10.5px">(blank = all)</span></span> | |
| 157 | <input type="text" name="file_type_filter" value="" placeholder="cgi,pl,pm,html,css,js,md,sql,conf" | |
| 158 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--mono);font-size:11.5px"> | |
| 159 | </label> | |
| 160 | </div> | |
| 161 | ||
| 162 | <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:6px"> | |
| 163 | <a href="/servers.cgi" class="tr-chip">Cancel</a> | |
| 164 | <button type="submit" class="tr-chip is-active" style="border:none">Import all</button> | |
| 165 | </div> | |
| 166 | </form> | |
| 167 | </div> | |
| 168 | </div> | |
| 169 | [/if] | |
| 170 | ||
| 171 | <!-- ============================ QUICK HELP CARD ============================ --> | |
| 172 | [if:!$bulk_mode] | |
| 173 | <div class="module glow-sky"> | |
| 174 | <div class="module-head"><div class="left"> | |
| 175 | <div class="module-title">Quick help</div> | |
| 176 | <span class="module-subtitle">The three most common setup shapes.</span> | |
| 177 | </div></div> | |
| 178 | <div class="module-body" style="font-size:13.5px;color:var(--text-dim);line-height:1.75"> | |
| 179 | <p style="margin-bottom:10px"><strong style="color:var(--text)">Same server</strong> — pick <code>local</code>, skip all the SSH fields. This is the default install monitoring itself.</p> | |
| 180 | <p style="margin-bottom:10px"><strong style="color:var(--text)">Remote via SSH</strong> — pick <code>ssh_agent</code>, fill <code>ssh_host</code> + <code>ssh_user</code>, and either paste the PEM key below (encrypted at rest with the master key) OR give a path to a keyfile on disk.</p> | |
| 181 | <p style="margin-bottom:10px"><strong style="color:var(--text)">Test the connection</strong> — after saving, hit the "Test" button on the server row. Runs an SSH probe and reports back inline.</p> | |
| 182 | <p><strong style="color:var(--text)">Bulk import 20 hosts at once</strong> — switch to the "Bulk import" tab. Paste hostnames, one shared key, one base scan config. DriftSense fans out and starts scanning within 5 minutes.</p> | |
| 183 | </div> | |
| 184 | </div> | |
| 185 | [/if] | |
| 5 | 186 | </div> |
| 6 | 187 | |
| 188 | <!-- ============================ SERVER LIST ============================ --> | |
| 7 | 189 | <div class="module glow-sky"> |
| 8 | 190 | <div class="module-head"><div class="left"> |
| 9 | 191 | <div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/></svg></div> |
| 10 | <div class="module-title">Registered nodes</div><span class="module-subtitle">$total total.</span> | |
| 192 | <div class="module-title">Registered servers</div> | |
| 193 | <span class="module-subtitle">$total total.</span> | |
| 11 | 194 | </div></div> |
| 12 | 195 | <div class="module-body tight"> |
| 13 | 196 | [if:$has_rows] |
| 14 | 197 | <table class="tbl"> |
| 15 | <thead><tr><th>Name</th><th style="width:130px">Role</th><th style="width:160px">Hostname / IP</th><th style="width:100px">Status</th><th style="width:140px">Agent</th><th style="width:170px">Last heartbeat</th></tr></thead> | |
| 198 | <thead><tr> | |
| 199 | <th style="width:140px">Kind / status</th> | |
| 200 | <th>Name</th> | |
| 201 | <th style="width:180px">Reach</th> | |
| 202 | <th style="width:130px">Key</th> | |
| 203 | <th style="width:150px">Last scan</th> | |
| 204 | <th style="width:220px">Actions</th> | |
| 205 | </tr></thead> | |
| 16 | 206 | <tbody> |
| 17 | [loop:@rows]<tr><td><strong>$server_name</strong></td><td class="mono dim">$role</td><td class="mono dim">$hostname</td><td><span class="pill $status_pill">$status</span></td><td class="mono dim">$agent_version</td><td class="mono dim">$heartbeat_h</td></tr>[/loop] | |
| 207 | [loop:@rows] | |
| 208 | <tr> | |
| 209 | <td> | |
| 210 | <span class="pill $kind_pill">$kind</span> | |
| 211 | <span class="pill $status_pill" style="margin-left:4px">$status</span> | |
| 212 | </td> | |
| 213 | <td> | |
| 214 | <a href="$edit_url" style="color:var(--accent-hi);text-decoration:none"><strong>$server_name</strong></a> | |
| 215 | [if:$err_short]<div class="dim" style="font-size:11px;color:var(--accent-danger)">$err_short</div>[/if] | |
| 216 | </td> | |
| 217 | <td class="mono dim" style="font-size:12px">$ssh_user@$host_display:$ssh_port</td> | |
| 218 | <td class="mono dim" style="font-size:11.5px">$key_display</td> | |
| 219 | <td class="mono dim"><span class="ts" data-ts="$last_scan_epoch" data-fmt="relative">$last_scan_h</span></td> | |
| 220 | <td> | |
| 221 | <button type="button" class="tr-chip" onclick="dsTestConn($server_id, this)">Test</button> | |
| 222 | <a href="$edit_url" class="tr-chip">Edit</a> | |
| 223 | <form method="post" action="/servers.cgi" style="display:inline" onsubmit="return confirm('Disable this server?')"> | |
| 224 | <input type="hidden" name="op" value="delete"> | |
| 225 | <input type="hidden" name="server_id" value="$server_id"> | |
| 226 | <button type="submit" class="tr-chip" title="Disable">×</button> | |
| 227 | </form> | |
| 228 | </td> | |
| 229 | </tr> | |
| 230 | [/loop] | |
| 18 | 231 | </tbody> |
| 19 | 232 | </table> |
| 20 | 233 | [/if] |
| 21 | [if:!$has_rows]<div style="padding:36px 14px;text-align:center;color:var(--text-dim)">No servers registered.</div>[/if] | |
| 234 | [if:!$has_rows] | |
| 235 | <div style="padding:36px 14px;text-align:center;color:var(--text-dim);font-size:13.5px"> | |
| 236 | No servers registered. Use the form above to add one, or switch to Bulk Import. | |
| 237 | </div> | |
| 238 | [/if] | |
| 22 | 239 | </div> |
| 23 | 240 | </div> |
| 24 | 241 | |
| 25 | <div class="module glow-teal" style="margin-top:16px"> | |
| 26 | <div class="module-head"><div class="left"><div class="module-title">How to add an agent</div><span class="module-subtitle">Standard multi-server install. See install docs for details.</span></div></div> | |
| 27 | <div class="module-body"> | |
| 28 | <ol style="color:var(--text-dim);line-height:1.8;margin:0;padding-left:20px;font-size:13.5px"> | |
| 29 | <li>On the remote server: <code>curl -sSL https://driftsense.com/agent-install.sh | sudo bash</code></li> | |
| 30 | <li>Enter this central node's URL: <code>https://watchtower.3dshawn.com</code></li> | |
| 31 | <li>Paste the HMAC secret from Settings → Agents</li> | |
| 32 | <li>The agent starts sending heartbeats + change captures via HMAC-signed HTTP within 60s</li> | |
| 33 | </ol> | |
| 34 | </div> | |
| 35 | </div> | |
| 242 | <script> | |
| 243 | (function () { | |
| 244 | window.dsTestConn = async function (id, btn) { | |
| 245 | var orig = btn.textContent; | |
| 246 | btn.textContent = 'Testing...'; | |
| 247 | btn.disabled = true; | |
| 248 | try { | |
| 249 | var r = await fetch('/servers.cgi?op=test&id=' + id + '&nc=' + Date.now(), { cache: 'no-store' }); | |
| 250 | var j = await r.json(); | |
| 251 | if (j.ok) { | |
| 252 | btn.textContent = 'OK'; | |
| 253 | btn.style.background = '#34d399'; | |
| 254 | btn.style.color = '#0a1220'; | |
| 255 | btn.style.borderColor = '#34d399'; | |
| 256 | alert('SSH probe OK\n\n' + (j.message || '')); | |
| 257 | } else { | |
| 258 | btn.textContent = 'FAIL'; | |
| 259 | btn.style.background = '#f43f5e'; | |
| 260 | btn.style.color = '#fff'; | |
| 261 | btn.style.borderColor = '#f43f5e'; | |
| 262 | alert('SSH probe failed\n\n' + (j.message || 'unknown error')); | |
| 263 | } | |
| 264 | } catch (e) { | |
| 265 | alert('Probe error: ' + e.message); | |
| 266 | btn.textContent = orig; | |
| 267 | } finally { | |
| 268 | setTimeout(function () { | |
| 269 | btn.disabled = false; | |
| 270 | btn.textContent = orig; | |
| 271 | btn.style.background = ''; | |
| 272 | btn.style.color = ''; | |
| 273 | btn.style.borderColor = ''; | |
| 274 | }, 3500); | |
| 275 | } | |
| 276 | }; | |
| 277 | })(); | |
| 278 | </script> |