Diff -- /var/www/vhosts/3dshawn.com/site1/TEMPLATES/servers.html

O Operator
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
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
11<div class="page-head">
22 <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 &amp; agents</h1>
4 <p class="page-subtitle">The nodes DriftSense knows about &mdash; 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-----&#10;...&#10;-----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 &mdash; 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&#10;prod-web-2 = 10.0.0.11&#10;webstls | webstls.com&#10;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) &mdash; 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-----&#10;...&#10;-----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 &mdash; 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> &mdash; 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> &mdash; 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> &mdash; 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> &mdash; 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]
5186</div>
6187
188<!-- ============================ SERVER LIST ============================ -->
7189<div class="module glow-sky">
8190 <div class="module-head"><div class="left">
9191 <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>
11194 </div></div>
12195 <div class="module-body tight">
13196 [if:$has_rows]
14197 <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>
16206 <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">&times;</button>
227 </form>
228 </td>
229 </tr>
230 [/loop]
18231 </tbody>
19232 </table>
20233 [/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]
22239 </div>
23240</div>
24241
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 &rarr; 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>