Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/admin_maintenance.cgi

O Operator
Diff

/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/admin_maintenance.cgi

added on local at 2026-07-01 13:57:36

Added
+214
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to a2bec6583788
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# AffSoft -- /admin_maintenance.cgi
4#
5# Lock the site for everyone except admins. Edit the message that users
6# see while the site is locked, with a live split-view preview.
7#
8# Consolidates the former /admin_maintenance_action.cgi (POST handler)
9# via SCRIPT_NAME dispatch. The .cgi=action file is a wrapper that
10# `do`s this file.
11#======================================================================
12use strict;
13use warnings;
14use lib '/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com';
15use CGI;
16use MODS::Login;
17use MODS::DBConnect;
18use MODS::AffSoft::Config;
19use MODS::AffSoft::Wrapper;
20
21my $q = CGI->new;
22my $form = $q->Vars;
23my $auth = MODS::Login->new;
24my $u = $auth->login_verify;
25
26my $entry = ($ENV{SCRIPT_NAME} || '') =~ m{/([^/]+)\.cgi$} ? $1 : 'admin_maintenance';
27
28if ($entry eq 'admin_maintenance_action') {
29 unless ($u && $u->{is_admin}) {
30 print "Status: 403 Forbidden\nContent-Type: text/plain\n\nadmin only\n";
31 exit;
32 }
33 _handle_action();
34 exit;
35}
36
37unless ($u) { print "Status: 302 Found\nLocation: /login.cgi\n\n"; exit; }
38unless ($u->{is_admin}) {
39 print "Status: 403 Forbidden\nContent-Type: text/html\n\n<h1>Forbidden</h1><p>Only admins can manage maintenance mode.</p>\n";
40 exit;
41}
42
43my $cfg = MODS::AffSoft::Config->new;
44my $locked = int($cfg->settings('maintenance_locked') || 0);
45my $msg = $cfg->settings('maintenance_message') || '';
46
47sub _esc {
48 my $s = shift; $s //= '';
49 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g; $s =~ s/"/&quot;/g;
50 return $s;
51}
52
53my $flash = '';
54if ($form->{saved}) { $flash = '<div class="banner success">Maintenance message saved.</div>'; }
55if ($form->{locked}) { $flash = '<div class="banner warn"><strong>System is now LOCKED.</strong> Only admins can sign in or stay signed in.</div>'; }
56if ($form->{unlocked}) { $flash = '<div class="banner success">System is now open to all users.</div>'; }
57if ($form->{err}) { my $e = _esc($form->{err}); $flash = qq~<div class="banner danger">$e</div>~; }
58
59my $msg_h = _esc($msg);
60my $status_lbl = $locked ? 'LOCKED' : 'OPEN';
61my $status_pill_class = $locked ? 'pill-bad' : 'pill-ok';
62my $toggle_op = $locked ? 'unlock' : 'lock';
63my $toggle_lbl = $locked ? 'Unlock System' : 'Lock System';
64my $toggle_btn_class = $locked ? 'btn btn-primary' : 'btn btn-danger';
65my $toggle_confirm = $locked
66 ? "return confirm('Unlock the site? Regular users will be able to sign in again.');"
67 : "return confirm('Lock the site? Only admins will be able to sign in until you unlock. Existing non-admin sessions are killed immediately.');";
68
69my $body = qq~
70<div class="page-head"><div class="left">
71 <span class="page-eyebrow"><span class="dot"></span> Operations</span>
72 <h1 class="page-title">Maintenance mode</h1>
73 <p class="page-subtitle">Block all non-admin users from signing in (or staying signed in), with a custom message shown on the login page.</p>
74</div></div>
75$flash
76
77<div class="module" style="margin-bottom:16px">
78 <div class="module-head"><div class="left"><div class="module-title">Status</div></div></div>
79 <div class="module-body" style="display:flex;gap:16px;align-items:center;flex-wrap:wrap">
80 <span class="pill $status_pill_class" style="font-size:14px;padding:4px 12px">$status_lbl</span>
81 <form method="POST" action="/admin_maintenance_action.cgi" style="margin:0" onsubmit="$toggle_confirm">
82 <input type="hidden" name="op" value="$toggle_op">
83 <button type="submit" class="$toggle_btn_class">$toggle_lbl</button>
84 </form>
85 <div style="flex:1"></div>
86 <a href="/login.cgi?_preview_maintenance=1" target="_blank" class="btn">View as user (opens login)</a>
87 </div>
88</div>
89
90<div class="module">
91 <div class="module-head"><div class="left"><div class="module-title">Message users see while locked</div></div></div>
92 <div class="module-body">
93 <form method="POST" action="/admin_maintenance_action.cgi">
94 <input type="hidden" name="op" value="save_message">
95 <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:12px">
96 <div>
97 <label class="lbl"><span class="t">HTML source</span>
98 <style>
99 #maint_src{width:100%;height:360px;padding:12px 14px;background-color:#0a0a0a;color:#cbd5e1;color-scheme:dark;border:1px solid rgba(255,255,255,.08);border-radius:8px;font-family:'JetBrains Mono','Consolas',monospace;font-size:12px;line-height:1.5;resize:vertical;box-sizing:border-box}
100 </style>
101 <textarea id="maint_src" name="message" spellcheck="false">$msg_h</textarea>
102 </label>
103 <div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:4px;font-size:11px">
104 <button type="button" class="btn btn-sm" onclick="wrap('<strong>','</strong>')">B</button>
105 <button type="button" class="btn btn-sm" onclick="wrap('<em>','</em>')"><em>I</em></button>
106 <button type="button" class="btn btn-sm" onclick="wrap('<h2>','</h2>')">H2</button>
107 <button type="button" class="btn btn-sm" onclick="wrap('<p>','</p>')">P</button>
108 <button type="button" class="btn btn-sm" onclick="ins('<br>')">BR</button>
109 <button type="button" class="btn btn-sm" onclick="linkPrompt()">Link</button>
110 <button type="button" class="btn btn-sm" onclick="resetMsg()">Reset to default</button>
111 </div>
112 </div>
113 <div>
114 <label class="lbl"><span class="t">Live preview</span>
115 <iframe id="maint_preview"
116 style="width:100%;height:360px;background:#0a0a0a;border:1px solid var(--col-border);border-radius:5px"></iframe>
117 </label>
118 <div class="dim" style="font-size:11px;margin-top:4px">Preview updates as you type. Bg is the same dark theme users see on /login.cgi.</div>
119 </div>
120 </div>
121 <button type="submit" class="btn btn-primary">Save message</button>
122 </form>
123 </div>
124</div>
125
126<script>
127(function(){
128 var ta = document.getElementById('maint_src');
129 var pv = document.getElementById('maint_preview');
130 function refresh() {
131 var html = '<style>body{font-family:system-ui,-apple-system,Segoe UI,Arial,sans-serif;background:#0a0a0a;color:#e5e5e5;margin:0;padding:24px;min-height:100vh;display:flex;align-items:center;justify-content:center}</style>' + ta.value;
132 pv.srcdoc = html;
133 }
134 ta.addEventListener('input', refresh);
135 refresh();
136 window.wrap = function(o,c){
137 var s=ta.selectionStart, e=ta.selectionEnd, v=ta.value;
138 ta.value = v.slice(0,s) + o + v.slice(s,e) + c + v.slice(e);
139 ta.focus(); ta.selectionStart=s+o.length; ta.selectionEnd=e+o.length; refresh();
140 };
141 window.ins = function(t){
142 var s=ta.selectionStart, v=ta.value;
143 ta.value = v.slice(0,s) + t + v.slice(s);
144 ta.focus(); ta.selectionStart=ta.selectionEnd=s+t.length; refresh();
145 };
146 window.linkPrompt = function(){
147 var url = prompt('Link URL', 'https://');
148 if (!url) return;
149 wrap('<a href="' + url + '">', '</a>');
150 };
151 window.resetMsg = function(){
152 if (!confirm('Reset to default message? Unsaved edits will be lost.')) return;
153 ta.value = '<div style="text-align:center;padding:40px 20px;max-width:560px;margin:0 auto">' +
154 '<h2 style="margin:0 0 14px;font-size:28px">We will be right back</h2>' +
155 '<p style="margin:0 0 14px;color:#aaa;font-size:16px;line-height:1.6">We are rolling out a quick improvement to the system. Most updates wrap up in under an hour.</p>' +
156 '<p style="margin:24px 0 0;color:#888;font-size:13px">Thanks for your patience. -- The team</p>' +
157 '</div>';
158 refresh();
159 };
160})();
161</script>
162~;
163
164print "Content-Type: text/html; charset=utf-8
165Cache-Control: no-cache
166
167";
168MODS::AffSoft::Wrapper->new->render({ userinfo => $u, title => 'Maintenance', page_key => 'admin_maintenance', body => $body });
169
170#======================================================================
171# admin_maintenance_action entry: POST handler
172# op=lock -- set maintenance_locked = 1
173# op=unlock -- set maintenance_locked = 0
174# op=save_message -- save the HTML body shown to locked users
175#======================================================================
176sub _handle_action {
177 my $db = MODS::DBConnect->new;
178 my $dbh = $db->db_connect;
179 unless ($dbh) {
180 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?err=db_unavailable\n\n";
181 return;
182 }
183
184 my $op = $form->{op} || '';
185
186 if ($op eq 'lock' || $op eq 'unlock') {
187 my $v = ($op eq 'lock') ? '1' : '0';
188 $db->db_readwrite($dbh, qq~
189 INSERT INTO platform_settings (setting_key, setting_value)
190 VALUES ('maintenance_locked', '$v')
191 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
192 ~, __FILE__, __LINE__);
193 $db->db_disconnect($dbh);
194 my $flag = ($op eq 'lock') ? 'locked=1' : 'unlocked=1';
195 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?$flag\n\n";
196 return;
197 }
198 elsif ($op eq 'save_message') {
199 my $msg = $form->{message} // '';
200 $msg = substr($msg, 0, 20000) if length($msg) > 20000;
201 my $msg_q = $msg; $msg_q =~ s/'/''/g; $msg_q =~ s/\\/\\\\/g;
202 $db->db_readwrite($dbh, qq~
203 INSERT INTO platform_settings (setting_key, setting_value)
204 VALUES ('maintenance_message', '$msg_q')
205 ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)
206 ~, __FILE__, __LINE__);
207 $db->db_disconnect($dbh);
208 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?saved=1\n\n";
209 return;
210 }
211
212 $db->db_disconnect($dbh);
213 print "Status: 302 Found\nLocation: /admin_maintenance.cgi?err=unknown_op\n\n";
214}