Diff -- /var/www/vhosts/3dshawn.com/admin.3dshawn.com/sites.cgi
Diff

/var/www/vhosts/3dshawn.com/admin.3dshawn.com/sites.cgi

added on local at 2026-07-01 14:22:50

Added
+287
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to c1e90c669e77
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# Meta-Admin -- /sites.cgi
4#
5# CRUD UI for the site_connections table. Lists every configured site
6# with its connection details + last-test result, opens an editor
7# panel, lets the operator add a new one. Read-only over the actual
8# remote DBs -- this page just edits LOCAL meta-admin config.
9#
10# Actions POST to /site_action.cgi (save / test / deactivate / reactivate).
11#======================================================================
12use strict;
13use warnings;
14use lib '/var/www/vhosts/3dshawn.com/admin.3dshawn.com';
15use CGI;
16use MODS::Login;
17use MODS::DBConnect;
18use MODS::MetaAdmin::Config;
19use MODS::MetaAdmin::Wrapper;
20use MODS::MetaAdmin::SiteConn;
21
22my $q = CGI->new;
23my $form = $q->Vars;
24my $auth = MODS::Login->new;
25my $cfg = MODS::MetaAdmin::Config->new;
26my $wrap = MODS::MetaAdmin::Wrapper->new;
27my $u = $auth->login_verify;
28
29my $entry = ($ENV{SCRIPT_NAME} || '') =~ m{/([^/]+)\.cgi$} ? $1 : 'sites';
30
31if ($entry eq 'site_action') {
32 unless ($u) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
33 _handle_action($q, $form);
34 exit;
35}
36
37unless ($u) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
38
39my $edit_id = ($form->{edit} || 0) + 0;
40my $is_new = ($form->{new} || 0) ? 1 : 0;
41
42my $sc = MODS::MetaAdmin::SiteConn->new;
43my $rows = $sc->list_sites(active => 0);
44
45# Resolve the row being edited (if any)
46my $row;
47if ($edit_id) {
48 foreach my $r (@$rows) { $row = $r if $r->{id} == $edit_id; last if $row; }
49}
50
51sub _esc {
52 my $s = shift; $s //= '';
53 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
54 $s =~ s/"/&quot;/g;
55 return $s;
56}
57
58my $flash = '';
59if ($form->{saved}) { $flash = qq~<div class="banner success">Saved site connection.</div>~; }
60if ($form->{tested_ok}) { $flash = qq~<div class="banner success">Connection test succeeded.</div>~; }
61if ($form->{tested_err}) {
62 my $e = _esc($form->{tested_err});
63 $flash = qq~<div class="banner danger">Connection test failed: $e</div>~;
64}
65if ($form->{deactivated}) { $flash = qq~<div class="banner warn">Site connection deactivated.</div>~; }
66if ($form->{reactivated}) { $flash = qq~<div class="banner success">Site connection reactivated.</div>~; }
67if ($form->{err}) {
68 my $e = _esc($form->{err});
69 $flash = qq~<div class="banner danger">$e</div>~;
70}
71
72sub _brand_letters {
73 my ($s) = @_;
74 my $slug = $s->{slug} || '';
75 my %map = (
76 webstls => 'WS',
77 ptmatrix => 'PT',
78 affsoft => 'AS',
79 shopcart => 'SC',
80 repricer => 'RP',
81 abforge => 'AB',
82 contactforge => 'CF',
83 );
84 return $map{$slug} || uc(substr($s->{display_name} || '?', 0, 1));
85}
86my $body = '';
87
88$body .= q~
89<div class="page-head"><div class="left">
90 <span class="page-eyebrow"><span class="dot"></span> Connections</span>
91 <h1 class="page-title">Sites</h1>
92 <p class="page-subtitle">One row per site. Passwords are encrypted at rest with the operator key in Settings. Test before saving.</p>
93</div>
94<div class="page-head-actions"><a href="/sites.cgi?new=1" class="btn btn-primary">+ New site</a></div>
95</div>
96~;
97
98$body .= $flash;
99
100# Edit / new form -----------------------------------------------------
101if ($is_new || $row) {
102 my $title = $is_new ? 'Add a site' : "Edit &middot; " . _esc($row->{display_name});
103 my $id = $row ? $row->{id} : 0;
104 my $slug = _esc($row ? $row->{slug} : '');
105 my $name = _esc($row ? $row->{display_name} : '');
106 my $url = _esc($row ? $row->{display_url} : 'https://');
107 my $color = _esc($row ? $row->{brand_color} : '#92400e');
108 my $h = _esc($row ? $row->{db_host} : 'localhost');
109 my $port = _esc($row ? $row->{db_port} : '3306');
110 my $dbn = _esc($row ? $row->{db_name} : '');
111 my $usr = _esc($row ? $row->{db_user} : '');
112 my $vp = _esc($row ? $row->{vhost_path} : '');
113 my $so = _esc($row ? $row->{sort_order} : '100');
114 my $act_checked = ($row && $row->{is_active}) || $is_new ? 'checked' : '';
115 my $pw_placeholder = $row ? '(unchanged -- leave blank to keep)' : 'paste DB password';
116
117 $body .= qq~
118<div class="module glow-magenta" style="margin-bottom:16px">
119 <div class="module-head"><div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></div><div class="module-title">$title</div></div></div>
120 <div class="module-body">
121 <form method="POST" action="/site_action.cgi" style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
122 <input type="hidden" name="op" value="save">
123 <input type="hidden" name="id" value="$id">
124
125 <label class="lbl"><span class="t">Slug</span>
126 <input type="text" name="slug" value="$slug" placeholder="e.g. contactforge" pattern="[a-z0-9_-]+" required></label>
127 <label class="lbl"><span class="t">Display name</span>
128 <input type="text" name="display_name" value="$name" placeholder="e.g. ContactForge" required></label>
129
130 <label class="lbl"><span class="t">URL</span>
131 <input type="url" name="display_url" value="$url" placeholder="https://yoursite.com"></label>
132 <label class="lbl"><span class="t">Brand color</span>
133 <input type="text" name="brand_color" value="$color" placeholder="#92400e"></label>
134
135 <label class="lbl"><span class="t">DB host</span>
136 <input type="text" name="db_host" value="$h" required></label>
137 <label class="lbl"><span class="t">DB port</span>
138 <input type="number" name="db_port" value="$port" min="1" max="65535" required></label>
139
140 <label class="lbl"><span class="t">DB name</span>
141 <input type="text" name="db_name" value="$dbn" required></label>
142 <label class="lbl"><span class="t">DB user</span>
143 <input type="text" name="db_user" value="$usr" required></label>
144
145 <label class="lbl" style="grid-column:1/-1"><span class="t">DB password</span>
146 <input type="password" name="db_pass" placeholder="$pw_placeholder" autocomplete="new-password"></label>
147
148 <label class="lbl"><span class="t">Vhost path (for du -sb)</span>
149 <input type="text" name="vhost_path" value="$vp" placeholder="/var/www/vhosts/example.com/httpdocs"></label>
150 <label class="lbl"><span class="t">Sort order</span>
151 <input type="number" name="sort_order" value="$so"></label>
152
153 <div style="grid-column:1/-1;display:flex;align-items:center;gap:10px;font-size:13px">
154 <input type="checkbox" name="is_active" value="1" id="cf_active" $act_checked>
155 <label for="cf_active">Active (uncheck to soft-disable without deleting)</label>
156 </div>
157
158 <div style="grid-column:1/-1;display:flex;gap:10px;justify-content:flex-end">
159 <a href="/sites.cgi" class="btn">Cancel</a>
160 <button type="submit" class="btn btn-primary">Save site</button>
161 </div>
162 </form>
163 </div>
164</div>
165~;
166}
167
168# Existing sites table -----------------------------------------------
169$body .= q~
170<div class="module">
171 <div class="module-head"><div class="left"><div class="module-icon"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/></svg></div><div class="module-title">Configured sites</div></div></div>
172 <div class="module-body" style="padding:0">
173 <table class="tbl">
174 <thead><tr><th></th><th>Site</th><th>URL</th><th>DB</th><th>Last test</th><th>Status</th><th style="text-align:right">Actions</th></tr></thead>
175 <tbody>
176~;
177foreach my $s (@$rows) {
178 my $name = _esc($s->{display_name});
179 my $url = _esc($s->{display_url} || '');
180 my $color = _esc($s->{brand_color} || '#807aa8');
181 my $dbn = _esc($s->{db_name});
182 my $usr = _esc($s->{db_user});
183 my $host = _esc($s->{db_host}) . ':' . _esc($s->{db_port});
184 my $lt = _esc($s->{last_test_at} || '-');
185 my $lr = _esc($s->{last_test_result} || '-');
186 my $pill = $s->{is_active}
187 ? '<span class="pill pill-ok">Active</span>'
188 : '<span class="pill pill-bad">Off</span>';
189 my $test_pill = '';
190 if ($s->{last_test_result}) {
191 $test_pill = ($s->{last_test_result} eq 'ok')
192 ? '<span class="pill pill-ok" style="margin-left:6px">OK</span>'
193 : '<span class="pill pill-bad" style="margin-left:6px">FAIL</span>';
194 }
195 my $deact_btn = $s->{is_active}
196 ? qq~<form method="POST" action="/site_action.cgi" style="display:inline" onsubmit="return confirm('Disable this site? It can be reactivated any time.')"><input type="hidden" name="op" value="deactivate"><input type="hidden" name="id" value="$s->{id}"><button type="submit" class="btn btn-sm btn-danger">Disable</button></form>~
197 : qq~<form method="POST" action="/site_action.cgi" style="display:inline"><input type="hidden" name="op" value="reactivate"><input type="hidden" name="id" value="$s->{id}"><button type="submit" class="btn btn-sm">Re-enable</button></form>~;
198 my $ltr = _brand_letters($s);
199 $body .= qq~<tr>
200 <td><span class="brand-icon" style="--site-glow:$color" data-letter="$ltr"></span></td>
201 <td><strong>$name</strong></td>
202 <td><a href="$url" target="_blank">$url</a></td>
203 <td class="mono dim" style="font-size:11px"><strong>$dbn</strong><br>$usr\@$host</td>
204 <td class="mono" style="font-size:11px">$lt $test_pill</td>
205 <td>$pill</td>
206 <td style="text-align:right;white-space:nowrap">
207 <form method="POST" action="/site_action.cgi" style="display:inline"><input type="hidden" name="op" value="test"><input type="hidden" name="id" value="$s->{id}"><button type="submit" class="btn btn-sm">Test</button></form>
208 <a href="/sites.cgi?edit=$s->{id}" class="btn btn-sm">Edit</a>
209 $deact_btn
210 </td>
211 </tr>~;
212}
213$body .= q~
214 </tbody>
215 </table>
216 </div>
217</div>
218~;
219
220$wrap->render(title => 'Sites', page_key => 'sites', body => $body, userinfo => $u);
221
222#======================================================================
223# site_action entry: POST handler
224# op=save upsert a site_connections row (encrypts password)
225# op=test run SELECT 1 against the configured host
226# op=deactivate flip is_active=0
227# op=reactivate flip is_active=1
228#======================================================================
229sub _act_redirect {
230 my $q = shift || '';
231 print "Status: 302 Found\nLocation: /sites.cgi$q\n\n";
232}
233sub _act_url_esc {
234 my $s = shift; $s //= '';
235 $s =~ s/([^A-Za-z0-9 .,'\$+-])/ /g;
236 $s =~ s/ /+/g;
237 return $s;
238}
239
240sub _handle_action {
241 my ($q, $f) = @_;
242 my $sc = MODS::MetaAdmin::SiteConn->new;
243
244 my $op = lc($f->{op} || ''); $op =~ s/[^a-z_]//g;
245
246 if ($op eq 'save') {
247 my ($id, $err) = $sc->upsert(
248 id => $f->{id} || 0,
249 slug => $f->{slug},
250 display_name => $f->{display_name},
251 display_url => $f->{display_url},
252 brand_color => $f->{brand_color},
253 db_host => $f->{db_host},
254 db_port => $f->{db_port},
255 db_name => $f->{db_name},
256 db_user => $f->{db_user},
257 db_pass => $f->{db_pass},
258 vhost_path => $f->{vhost_path},
259 is_active => $f->{is_active},
260 sort_order => $f->{sort_order},
261 );
262 if ($err) { _act_redirect('?err=' . _act_url_esc($err)); return; }
263 _act_redirect('?saved=1'); return;
264 }
265
266 # All other ops act on a row by id -- look up the slug first.
267 my $id = $f->{id} || 0; $id =~ s/[^0-9]//g;
268 unless ($id) { _act_redirect('?err=' . _act_url_esc('missing id')); return; }
269
270 my $db = MODS::DBConnect->new;
271 my $dbh = $db->db_connect;
272 my $r = $db->db_readwrite($dbh, qq~SELECT slug FROM site_connections WHERE id='$id' LIMIT 1~, __FILE__, __LINE__);
273 $db->db_disconnect($dbh);
274 unless ($r && $r->{slug}) { _act_redirect('?err=' . _act_url_esc('unknown site')); return; }
275 my $slug = $r->{slug};
276
277 if ($op eq 'test') {
278 my ($ok, $err) = $sc->test_connection($slug);
279 if ($ok) { _act_redirect('?tested_ok=1'); return; }
280 _act_redirect('?tested_err=' . _act_url_esc($err || 'failed')); return;
281 }
282 if ($op eq 'deactivate') { $sc->deactivate($id); _act_redirect('?deactivated=1'); return; }
283 if ($op eq 'reactivate') { $sc->reactivate($id); _act_redirect('?reactivated=1'); return; }
284
285 _act_redirect('?err=' . _act_url_esc('unknown op'));
286 return;
287}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help