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-12 00:35:29
Added
+8
lines
Removed
-0
lines
Context
278
unchanged
Blobs
from b85517f9ee2e
to 66b67331501d
to 66b67331501d
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 | 3 | <h1 class="page-title">Servers & agents</h1> |
| 4 | 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 | <div class="ds-guide"> | |
| 8 | <div class="dsg-icon">i</div> | |
| 9 | <div> | |
| 10 | <span class="dsg-title">Two flavors:</span> | |
| 11 | <strong>Local</strong> = scanner runs on the same box as the file being watched (fast, zero-network). <strong>SSH agent</strong> = scanner SSHes into the target box using a restricted key; only footprint on that box is one <code>authorized_keys</code> entry. Great for <em>stealth deployment</em> where you don't want developers on the monitored box to see DriftSense installed. Paste a PEM key below to store it <span class="ds-help" data-help-title="Encrypted at rest" data-help-body="PEM keys pasted here are encrypted with an HMAC-SHA-256 CTR construction using a per-install master key at <code>/etc/drift_sense/master.key</code> (mode 0640, group psacln). Neither the DB nor a filesystem snapshot exposes plaintext keys."></span> in the DB (encrypted). Test any row with the "Test" button. | |
| 12 | </div> | |
| 5 | 13 | </div> |
| 6 | 14 | |
| 7 | 15 | [if:$has_imported] |
| 8 | 16 | <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 | 17 | Bulk import created $imported server(s). They'll be scanned on the next agent tick (within 5 minutes). |
| 10 | 18 | </div> |
| 11 | 19 | [/if] |
| 12 | 20 | |
| 13 | 21 | [if:!$crypto_ready] |
| 14 | 22 | <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 | 23 | <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 | 24 | </div> |
| 17 | 25 | [/if] |
| 18 | 26 | |
| 19 | 27 | <!-- ============================ TOP TABS ============================ --> |
| 20 | 28 | <div style="display:flex;gap:8px;margin-bottom:16px"> |
| 21 | 29 | <a class="tr-chip[if:!$bulk_mode] is-active[/if]" href="/servers.cgi">Single-server form</a> |
| 22 | 30 | <a class="tr-chip[if:$bulk_mode] is-active[/if]" href="/servers.cgi?bulk=1">Bulk import</a> |
| 23 | 31 | </div> |
| 24 | 32 | |
| 25 | 33 | <div class="dash-grid" style="grid-template-columns:1fr 1fr;margin-bottom:20px"> |
| 26 | 34 | <!-- ============================ SINGLE-SERVER FORM ============================ --> |
| 27 | 35 | [if:!$bulk_mode] |
| 28 | 36 | <div class="module glow-teal"> |
| 29 | 37 | <div class="module-head"><div class="left"> |
| 30 | 38 | <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 | 39 | <div class="module-title">[if:$is_edit]Edit server #$server_id[/if][if:!$is_edit]Add server[/if]</div> |
| 32 | 40 | <span class="module-subtitle">Pick <code>local</code> for the DriftSense host itself, or <code>ssh_agent</code> for anything remote.</span> |
| 33 | 41 | </div></div> |
| 34 | 42 | <div class="module-body"> |
| 35 | 43 | <form method="post" action="/servers.cgi" style="display:flex;flex-direction:column;gap:12px"> |
| 36 | 44 | <input type="hidden" name="op" value="$form_op"> |
| 37 | 45 | [if:$is_edit]<input type="hidden" name="server_id" value="$server_id">[/if] |
| 38 | 46 | |
| 39 | 47 | <div style="display:flex;gap:12px"> |
| 40 | 48 | <label style="flex:2;display:flex;flex-direction:column;gap:4px"> |
| 41 | 49 | <span class="rb-label">Server name</span> |
| 42 | 50 | <input type="text" name="server_name" value="$server_name" required placeholder="e.g. prod-web-1" |
| 43 | 51 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:inherit"> |
| 44 | 52 | </label> |
| 45 | 53 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> |
| 46 | 54 | <span class="rb-label">Kind</span> |
| 47 | 55 | <select name="kind" style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px"> |
| 48 | 56 | <option value="local" $k_local_sel>local</option> |
| 49 | 57 | <option value="ssh_agent" $k_ssh_sel>ssh_agent</option> |
| 50 | 58 | </select> |
| 51 | 59 | </label> |
| 52 | 60 | </div> |
| 53 | 61 | |
| 54 | 62 | <div style="display:flex;gap:12px"> |
| 55 | 63 | <label style="flex:2;display:flex;flex-direction:column;gap:4px"> |
| 56 | 64 | <span class="rb-label">SSH host</span> |
| 57 | 65 | <input type="text" name="ssh_host" value="$ssh_host" placeholder="e.g. prod-web-1.internal or 10.0.0.42" |
| 58 | 66 | 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 | 67 | </label> |
| 60 | 68 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> |
| 61 | 69 | <span class="rb-label">User</span> |
| 62 | 70 | <input type="text" name="ssh_user" value="$ssh_user" |
| 63 | 71 | 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 | 72 | </label> |
| 65 | 73 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> |
| 66 | 74 | <span class="rb-label">Port</span> |
| 67 | 75 | <input type="number" name="ssh_port" value="$ssh_port" min="1" max="65535" |
| 68 | 76 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px"> |
| 69 | 77 | </label> |
| 70 | 78 | </div> |
| 71 | 79 | |
| 72 | 80 | <label style="display:flex;flex-direction:column;gap:4px"> |
| 73 | 81 | <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 | 82 | <input type="text" name="ssh_key_path" value="$ssh_key_path" placeholder="/etc/drift_sense/keys/agent_key" |
| 75 | 83 | 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 | 84 | </label> |
| 77 | 85 | |
| 78 | 86 | <label style="display:flex;flex-direction:column;gap:4px"> |
| 79 | 87 | <span class="rb-label"> |
| 80 | 88 | Paste PEM key (encrypted at rest with master key) |
| 81 | 89 | [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 | 90 | </span> |
| 83 | 91 | <textarea name="ssh_key_blob_plain" rows="4" placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY-----" |
| 84 | 92 | 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 | 93 | </label> |
| 86 | 94 | |
| 87 | 95 | <label style="display:flex;flex-direction:column;gap:4px"> |
| 88 | 96 | <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 | 97 | <input type="text" name="ssh_options" value="$ssh_options" placeholder="e.g. IdentitiesOnly=yes ProxyJump=bastion.internal" |
| 90 | 98 | 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 | 99 | </label> |
| 92 | 100 | |
| 93 | 101 | <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:6px"> |
| 94 | 102 | [if:$is_edit]<a href="/servers.cgi" class="tr-chip">Cancel</a>[/if] |
| 95 | 103 | <button type="submit" class="tr-chip is-active" style="border:none">[if:$is_edit]Save[/if][if:!$is_edit]Add server[/if]</button> |
| 96 | 104 | </div> |
| 97 | 105 | </form> |
| 98 | 106 | </div> |
| 99 | 107 | </div> |
| 100 | 108 | [/if] |
| 101 | 109 | |
| 102 | 110 | <!-- ============================ BULK IMPORT ============================ --> |
| 103 | 111 | [if:$bulk_mode] |
| 104 | 112 | <div class="module glow-emerald" style="grid-column:1 / span 2"> |
| 105 | 113 | <div class="module-head"><div class="left"> |
| 106 | 114 | <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 | 115 | <div class="module-title">Bulk import</div> |
| 108 | 116 | <span class="module-subtitle">Paste one hostname per line + one shared SSH key + a base scan config. DriftSense fans out.</span> |
| 109 | 117 | </div></div> |
| 110 | 118 | <div class="module-body"> |
| 111 | 119 | <form method="post" action="/servers.cgi" style="display:flex;flex-direction:column;gap:14px"> |
| 112 | 120 | <input type="hidden" name="op" value="bulk_import"> |
| 113 | 121 | |
| 114 | 122 | <label style="display:flex;flex-direction:column;gap:4px"> |
| 115 | 123 | <span class="rb-label">Hostnames — one per line, optional <code>label|host</code> or <code>label=host</code> format</span> |
| 116 | 124 | <textarea name="hosts" rows="7" placeholder="prod-web-1.internal prod-web-2 = 10.0.0.11 webstls | webstls.com staging | staging.internal" |
| 117 | 125 | 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 | 126 | </label> |
| 119 | 127 | |
| 120 | 128 | <div style="display:flex;gap:12px"> |
| 121 | 129 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> |
| 122 | 130 | <span class="rb-label">User</span> |
| 123 | 131 | <input type="text" name="ssh_user" value="root" |
| 124 | 132 | 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 | 133 | </label> |
| 126 | 134 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> |
| 127 | 135 | <span class="rb-label">Port</span> |
| 128 | 136 | <input type="number" name="ssh_port" value="22" |
| 129 | 137 | style="background:var(--bg-1);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 12px"> |
| 130 | 138 | </label> |
| 131 | 139 | </div> |
| 132 | 140 | |
| 133 | 141 | <label style="display:flex;flex-direction:column;gap:4px"> |
| 134 | 142 | <span class="rb-label">Shared PEM key (encrypted at rest) — optional if all hosts use <code>ssh_key_path</code></span> |
| 135 | 143 | <textarea name="ssh_key_blob_plain" rows="4" placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY-----" |
| 136 | 144 | 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 | 145 | </label> |
| 138 | 146 | |
| 139 | 147 | <hr style="border:none;border-top:1px solid var(--border);margin:4px 0"> |
| 140 | 148 | |
| 141 | 149 | <div class="rb-label" style="margin-bottom:-6px">Base scan config — applied to every host</div> |
| 142 | 150 | |
| 143 | 151 | <label style="display:flex;flex-direction:column;gap:4px"> |
| 144 | 152 | <span class="rb-label">Scan path</span> |
| 145 | 153 | <input type="text" name="scan_path" value="/etc" placeholder="/etc or /var/www" |
| 146 | 154 | 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 | 155 | </label> |
| 148 | 156 | |
| 149 | 157 | <div style="display:flex;gap:12px"> |
| 150 | 158 | <label style="flex:2;display:flex;flex-direction:column;gap:4px"> |
| 151 | 159 | <span class="rb-label">Ignore globs (comma-separated)</span> |
| 152 | 160 | <input type="text" name="ignore_list" value="*.log,*.pid,*.sock,*.tmp,tmp/*,cache/*,logs/*,.git/*,node_modules/*" |
| 153 | 161 | 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 | 162 | </label> |
| 155 | 163 | <label style="flex:1;display:flex;flex-direction:column;gap:4px"> |
| 156 | 164 | <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 | 165 | <input type="text" name="file_type_filter" value="" placeholder="cgi,pl,pm,html,css,js,md,sql,conf" |
| 158 | 166 | 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 | 167 | </label> |
| 160 | 168 | </div> |
| 161 | 169 | |
| 162 | 170 | <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:6px"> |
| 163 | 171 | <a href="/servers.cgi" class="tr-chip">Cancel</a> |
| 164 | 172 | <button type="submit" class="tr-chip is-active" style="border:none">Import all</button> |
| 165 | 173 | </div> |
| 166 | 174 | </form> |
| 167 | 175 | </div> |
| 168 | 176 | </div> |
| 169 | 177 | [/if] |
| 170 | 178 | |
| 171 | 179 | <!-- ============================ QUICK HELP CARD ============================ --> |
| 172 | 180 | [if:!$bulk_mode] |
| 173 | 181 | <div class="module glow-sky"> |
| 174 | 182 | <div class="module-head"><div class="left"> |
| 175 | 183 | <div class="module-title">Quick help</div> |
| 176 | 184 | <span class="module-subtitle">The three most common setup shapes.</span> |
| 177 | 185 | </div></div> |
| 178 | 186 | <div class="module-body" style="font-size:13.5px;color:var(--text-dim);line-height:1.75"> |
| 179 | 187 | <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 | 188 | <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 | 189 | <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 | 190 | <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 | 191 | </div> |
| 184 | 192 | </div> |
| 185 | 193 | [/if] |
| 186 | 194 | </div> |
| 187 | 195 | |
| 188 | 196 | <!-- ============================ SERVER LIST ============================ --> |
| 189 | 197 | <div class="module glow-sky"> |
| 190 | 198 | <div class="module-head"><div class="left"> |
| 191 | 199 | <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> |
| 192 | 200 | <div class="module-title">Registered servers</div> |
| 193 | 201 | <span class="module-subtitle">$total total.</span> |
| 194 | 202 | </div></div> |
| 195 | 203 | <div class="module-body tight"> |
| 196 | 204 | [if:$has_rows] |
| 197 | 205 | <table class="tbl"> |
| 198 | 206 | <thead><tr> |
| 199 | 207 | <th style="width:140px">Kind / status</th> |
| 200 | 208 | <th>Name</th> |
| 201 | 209 | <th style="width:180px">Reach</th> |
| 202 | 210 | <th style="width:130px">Key</th> |
| 203 | 211 | <th style="width:150px">Last scan</th> |
| 204 | 212 | <th style="width:220px">Actions</th> |
| 205 | 213 | </tr></thead> |
| 206 | 214 | <tbody> |
| 207 | 215 | [loop:@rows] |
| 208 | 216 | <tr> |
| 209 | 217 | <td> |
| 210 | 218 | <span class="pill $kind_pill">$kind</span> |
| 211 | 219 | <span class="pill $status_pill" style="margin-left:4px">$status</span> |
| 212 | 220 | </td> |
| 213 | 221 | <td> |
| 214 | 222 | <a href="$edit_url" style="color:var(--accent-hi);text-decoration:none"><strong>$server_name</strong></a> |
| 215 | 223 | [if:$err_short]<div class="dim" style="font-size:11px;color:var(--accent-danger)">$err_short</div>[/if] |
| 216 | 224 | </td> |
| 217 | 225 | <td class="mono dim" style="font-size:12px">$ssh_user@$host_display:$ssh_port</td> |
| 218 | 226 | <td class="mono dim" style="font-size:11.5px">$key_display</td> |
| 219 | 227 | <td class="mono dim"><span class="ts" data-ts="$last_scan_epoch" data-fmt="relative">$last_scan_h</span></td> |
| 220 | 228 | <td> |
| 221 | 229 | <button type="button" class="tr-chip" onclick="dsTestConn($server_id, this)">Test</button> |
| 222 | 230 | <a href="$edit_url" class="tr-chip">Edit</a> |
| 223 | 231 | <form method="post" action="/servers.cgi" style="display:inline" onsubmit="return confirm('Disable this server?')"> |
| 224 | 232 | <input type="hidden" name="op" value="delete"> |
| 225 | 233 | <input type="hidden" name="server_id" value="$server_id"> |
| 226 | 234 | <button type="submit" class="tr-chip" title="Disable">×</button> |
| 227 | 235 | </form> |
| 228 | 236 | </td> |
| 229 | 237 | </tr> |
| 230 | 238 | [/loop] |
| 231 | 239 | </tbody> |
| 232 | 240 | </table> |
| 233 | 241 | [/if] |
| 234 | 242 | [if:!$has_rows] |
| 235 | 243 | <div style="padding:36px 14px;text-align:center;color:var(--text-dim);font-size:13.5px"> |
| 236 | 244 | No servers registered. Use the form above to add one, or switch to Bulk Import. |
| 237 | 245 | </div> |
| 238 | 246 | [/if] |
| 239 | 247 | </div> |
| 240 | 248 | </div> |
| 241 | 249 | |
| 242 | 250 | <script> |
| 243 | 251 | (function () { |
| 244 | 252 | window.dsTestConn = async function (id, btn) { |
| 245 | 253 | var orig = btn.textContent; |
| 246 | 254 | btn.textContent = 'Testing...'; |
| 247 | 255 | btn.disabled = true; |
| 248 | 256 | try { |
| 249 | 257 | var r = await fetch('/servers.cgi?op=test&id=' + id + '&nc=' + Date.now(), { cache: 'no-store' }); |
| 250 | 258 | var j = await r.json(); |
| 251 | 259 | if (j.ok) { |
| 252 | 260 | btn.textContent = 'OK'; |
| 253 | 261 | btn.style.background = '#34d399'; |
| 254 | 262 | btn.style.color = '#0a1220'; |
| 255 | 263 | btn.style.borderColor = '#34d399'; |
| 256 | 264 | alert('SSH probe OK\n\n' + (j.message || '')); |
| 257 | 265 | } else { |
| 258 | 266 | btn.textContent = 'FAIL'; |
| 259 | 267 | btn.style.background = '#f43f5e'; |
| 260 | 268 | btn.style.color = '#fff'; |
| 261 | 269 | btn.style.borderColor = '#f43f5e'; |
| 262 | 270 | alert('SSH probe failed\n\n' + (j.message || 'unknown error')); |
| 263 | 271 | } |
| 264 | 272 | } catch (e) { |
| 265 | 273 | alert('Probe error: ' + e.message); |
| 266 | 274 | btn.textContent = orig; |
| 267 | 275 | } finally { |
| 268 | 276 | setTimeout(function () { |
| 269 | 277 | btn.disabled = false; |
| 270 | 278 | btn.textContent = orig; |
| 271 | 279 | btn.style.background = ''; |
| 272 | 280 | btn.style.color = ''; |
| 273 | 281 | btn.style.borderColor = ''; |
| 274 | 282 | }, 3500); |
| 275 | 283 | } |
| 276 | 284 | }; |
| 277 | 285 | })(); |
| 278 | 286 | </script> |