Diff -- /var/www/vhosts/3dshawn.com/repricer.3dshawn.com/TEMPLATES/repricer_admin_config.html
Diff
/var/www/vhosts/3dshawn.com/repricer.3dshawn.com/TEMPLATES/repricer_admin_config.html
added on local at 2026-07-01 21:47:22
Added
+235
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to e9238aec001c
to e9238aec001c
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | <!-- ===================================================================== | |
| 2 | Software Configuration (super-admin only) | |
| 3 | 1. Page header + flash | |
| 4 | 2. Each editable group rendered as a card with labeled inputs | |
| 5 | 3. Read-only "Lives in code" reference at the bottom | |
| 6 | Backed by /admin_config.cgi -> /admin_config_action.cgi. | |
| 7 | Secret fields are masked in the source server-side (Config.pm | |
| 8 | returns empty for is_secret rows with a value) and the form | |
| 9 | shows a "set" pill so the admin knows it's populated. | |
| 10 | ===================================================================== --> | |
| 11 | ||
| 12 | <style> | |
| 13 | .ac-wrap { max-width: 100%; margin: 0; } | |
| 14 | .ac-flash { | |
| 15 | border-radius: 8px; padding: 10px 14px; font-size: 13px; | |
| 16 | margin-bottom: 16px; | |
| 17 | } | |
| 18 | .ac-flash.ok { background: rgba(34,197,94,0.15); color: #4ade80; border: 1px solid rgba(34,197,94,0.35); } | |
| 19 | .ac-flash.danger { background: rgba(239,68,68,0.15); color: #fca5a5; border: 1px solid rgba(239,68,68,0.35); } | |
| 20 | .ac-group { | |
| 21 | background: var(--col-surface-1); | |
| 22 | border: 1px solid var(--col-border); | |
| 23 | border-radius: 12px; | |
| 24 | padding: 18px 20px; | |
| 25 | margin-bottom: 18px; | |
| 26 | } | |
| 27 | .ac-group h2 { | |
| 28 | font-size: 16px; font-weight: 600; color: var(--col-text); | |
| 29 | margin: 0 0 4px; | |
| 30 | } | |
| 31 | .ac-group p.intro { | |
| 32 | color: var(--col-text-2); font-size: 12.5px; line-height: 1.55; | |
| 33 | margin: 0 0 14px; | |
| 34 | } | |
| 35 | .ac-field { | |
| 36 | display: grid; grid-template-columns: 220px 1fr 80px; | |
| 37 | gap: 14px; align-items: center; | |
| 38 | padding: 10px 0; border-top: 1px solid var(--col-border); | |
| 39 | } | |
| 40 | .ac-field:first-of-type { border-top: 0; } | |
| 41 | .ac-field label { font-size: 13px; color: var(--col-text); } | |
| 42 | .ac-field label .k { | |
| 43 | display: block; font-size: 11px; color: var(--col-text-3); | |
| 44 | font-family: var(--font-mono, monospace); margin-top: 2px; | |
| 45 | } | |
| 46 | .ac-field input[type=text], | |
| 47 | .ac-field input[type=password] { | |
| 48 | width: 100%; padding: 8px 10px; border-radius: 6px; | |
| 49 | background: #0f172a; border: 1px solid var(--col-border); | |
| 50 | color: #fff; font-family: var(--font-mono, monospace); font-size: 13px; | |
| 51 | } | |
| 52 | .ac-field .desc { | |
| 53 | grid-column: 2 / 3; | |
| 54 | font-size: 11.5px; line-height: 1.55; | |
| 55 | color: var(--col-text-3); | |
| 56 | margin-top: 6px; | |
| 57 | } | |
| 58 | .ac-intro { | |
| 59 | background: rgba(5,150,105,0.08); | |
| 60 | border: 1px solid rgba(5,150,105,0.25); | |
| 61 | border-radius: 10px; | |
| 62 | padding: 14px 18px; | |
| 63 | margin-bottom: 20px; | |
| 64 | color: var(--col-text-2); | |
| 65 | font-size: 12.5px; | |
| 66 | line-height: 1.65; | |
| 67 | } | |
| 68 | .ac-intro h3 { | |
| 69 | font-size: 13px; color: #6ee7b7; | |
| 70 | margin: 0 0 6px; | |
| 71 | text-transform: uppercase; letter-spacing: 1.5px; font-weight: 700; | |
| 72 | } | |
| 73 | .ac-intro ul { margin: 6px 0 0 16px; padding: 0; } | |
| 74 | .ac-intro ul li { margin: 3px 0; } | |
| 75 | .ac-intro code { | |
| 76 | background: rgba(15,23,42,0.6); padding: 1px 6px; border-radius: 4px; | |
| 77 | font-family: var(--font-mono, monospace); font-size: 11.5px; color: #fde68a; | |
| 78 | } | |
| 79 | .ac-pill { | |
| 80 | display: inline-block; font-size: 10.5px; font-weight: 700; | |
| 81 | padding: 3px 8px; border-radius: 999px; letter-spacing: 1px; text-transform: uppercase; | |
| 82 | } | |
| 83 | .ac-pill.set { background: rgba(34,197,94,0.18); color: #86efac; } | |
| 84 | .ac-pill.empty { background: rgba(148,163,184,0.18); color: #cbd5e1; } | |
| 85 | .ac-pill.src-db { background: rgba(15,118,110,0.18); color: #6ee7b7; } | |
| 86 | .ac-pill.src-file { background: rgba(251,191,36,0.18); color: #fde68a; } | |
| 87 | .ac-pill.src-default { background: rgba(148,163,184,0.18); color: #cbd5e1; } | |
| 88 | .ac-codebox { | |
| 89 | background: var(--col-surface-1); border: 1px solid var(--col-border); | |
| 90 | border-radius: 12px; padding: 14px 18px; margin-top: 8px; | |
| 91 | } | |
| 92 | .ac-codebox h2 { font-size: 14px; color: var(--col-text); margin: 0 0 8px; } | |
| 93 | .ac-codebox table { width: 100%; border-collapse: collapse; } | |
| 94 | .ac-codebox td { | |
| 95 | font-size: 12.5px; color: var(--col-text-2); | |
| 96 | padding: 6px 8px; border-top: 1px solid var(--col-border); | |
| 97 | vertical-align: top; | |
| 98 | } | |
| 99 | .ac-codebox td.k { | |
| 100 | font-family: var(--font-mono, monospace); color: #fff; width: 220px; | |
| 101 | } | |
| 102 | .ac-actions { | |
| 103 | display: flex; justify-content: flex-end; gap: 10px; margin: 4px 0 20px; | |
| 104 | } | |
| 105 | ||
| 106 | ||
| 107 | /* Help popup (CSS :target -- no JS needed) */ | |
| 108 | .ac-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 6px; } | |
| 109 | .ac-section-head h2 { margin: 0; } | |
| 110 | .ac-sec-anchor { position: relative; top: -90px; height: 0; width: 0; overflow: hidden; } | |
| 111 | html { scroll-behavior: smooth; } | |
| 112 | a.ac-help-toggle { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: 50%; background: rgba(16,185,129,0.16); color: rgb(110,231,183); font-weight: 700; font-size: 13px; cursor: pointer; border: 1px solid rgba(16,185,129,0.36); transition: all 0.16s ease; position: relative; text-decoration: none; } | |
| 113 | a.ac-help-toggle:hover { background: rgba(16,185,129,0.32); color: #fff; transform: scale(1.08); text-decoration: none; } | |
| 114 | a.ac-help-toggle .ac-help-tip { position: absolute; right: calc(100% + 10px); top: 50%; transform: translateY(-50%); padding: 8px 12px; background: rgba(8,12,20,0.98); border: 1px solid rgba(16,185,129,0.4); border-radius: 8px; color: #cfd6e0; font-size: 12px; font-weight: 400; white-space: nowrap; opacity: 0; box-shadow: 0 10px 24px rgba(0,0,0,0.5); transition: opacity 0.16s ease; pointer-events: none; } | |
| 115 | a.ac-help-toggle:hover .ac-help-tip { opacity: 1; } | |
| 116 | .ac-help-content { position: fixed; inset: 0; z-index: 200; display: none; align-items: center; justify-content: center; padding: 24px; } | |
| 117 | .ac-help-content:target { display: flex; } | |
| 118 | a.ac-help-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.72); backdrop-filter: blur(4px); display: block; } | |
| 119 | .ac-help-modal { position: relative; z-index: 1; background: linear-gradient(180deg, #031310 0%, #020806 100%); border: 1px solid rgba(16,185,129,0.32); border-radius: 16px; box-shadow: 0 32px 80px rgba(0,0,0,0.8), 0 0 40px rgba(16,185,129,0.18); max-width: 640px; width: 100%; max-height: 85vh; overflow: hidden; display: flex; flex-direction: column; animation: ac-pop 0.22s cubic-bezier(.4,0,.2,1); } | |
| 120 | @keyframes ac-pop { from { opacity: 0; transform: scale(0.94) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } } | |
| 121 | .ac-help-modal-head { position: relative; padding: 22px 24px 18px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; | |
| 122 | background: | |
| 123 | radial-gradient(ellipse 140% 90% at 0% 0%, rgba(16,185,129,0.32) 0%, transparent 65%), | |
| 124 | radial-gradient(ellipse 100% 80% at 100% 40%, rgba(167,243,208,0.14) 0%, transparent 60%), | |
| 125 | linear-gradient(180deg, #05201a 0%, #020806 100%); | |
| 126 | } | |
| 127 | .ac-help-modal-head::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: linear-gradient(90deg, rgb(6,78,59) 0%, rgb(16,185,129) 38%, rgb(110,231,183) 60%, rgb(16,185,129) 80%, rgb(6,78,59) 100%); box-shadow: 0 0 14px rgba(110,231,183,0.55), 0 0 28px rgba(16,185,129,0.32); } | |
| 128 | .ac-help-modal-head h2 { margin: 0; color: #fff; font-size: 22px; font-weight: 700; letter-spacing: -0.01em; text-shadow: 0 2px 14px rgba(110,231,183,0.32); border: 0; } | |
| 129 | .ac-help-modal-head .ac-sum { color: rgb(110,231,183); font-size: 12px; margin-top: 6px; font-weight: 600; letter-spacing: 0.02em; } | |
| 130 | a.ac-help-close { background: rgba(0,0,0,0.32); color: #fff; border: 1px solid rgba(110,231,183,0.32); width: 32px; height: 32px; border-radius: 8px; font-size: 18px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; flex-shrink: 0; transition: all 0.15s ease; } | |
| 131 | a.ac-help-close:hover { background: rgba(16,185,129,0.32); color: #fff; border-color: rgba(16,185,129,0.6); transform: scale(1.06); } | |
| 132 | .ac-help-modal-body { padding: 18px 24px 22px; overflow-y: auto; color: #cfd6e0; font-size: 13px; line-height: 1.65; } | |
| 133 | .ac-help-modal-body p { margin: 0 0 12px; } | |
| 134 | .ac-help-modal-body p:last-child { margin-bottom: 0; } | |
| 135 | .ac-help-modal-body code { background: rgba(255,255,255,0.06); padding: 1px 6px; border-radius: 4px; font-size: 11.5px; color: rgb(110,231,183); } | |
| 136 | .ac-help-modal-body strong { color: #fff; } | |
| 137 | .ac-help-modal-body em { color: #cbd5e1; font-style: italic; } | |
| 138 | </style> | |
| 139 | ||
| 140 | <div class="ac-wrap"> | |
| 141 | ||
| 142 | <div style="margin-bottom:14px"> | |
| 143 | <h1 style="font-size:22px;color:var(--col-text);margin:0 0 6px">Configuration</h1> | |
| 144 | <p class="text-sm text-dim" style="margin:0"> | |
| 145 | Edit platform-wide settings. Saved values land in <code>platform_settings</code> and override the hardcoded defaults in <code>MODS/RePricer/Config.pm</code>. | |
| 146 | </p> | |
| 147 | </div> | |
| 148 | ||
| 149 | <div class="ac-intro"> | |
| 150 | <h3>How this page works</h3> | |
| 151 | Each row below is one platform-wide setting. Paste the value you want, then click <strong>Save changes</strong> at the bottom. Changes take effect immediately on the next page render — no deploy, no restart. | |
| 152 | <ul> | |
| 153 | <li><strong>Source pills</strong> show where each value comes from right now: | |
| 154 | <span class="ac-pill src-db">db</span> = stored here in <code>platform_settings</code>; | |
| 155 | <span class="ac-pill src-file">file</span> = read from <code>/private/stripe.conf</code> (legacy bootstrap path); | |
| 156 | <span class="ac-pill src-default">default</span> = the hardcoded fallback in <code>MODS/RePricer/Config.pm</code>.</li> | |
| 157 | <li><strong>Secret fields</strong> (Stripe secret key, webhook signing secret) are masked once set — the actual value is never sent back to the browser. Leave the field blank to keep the existing value; type a new value to replace it.</li> | |
| 158 | <li><strong>Empty non-secret fields</strong> clear the override, so the lookup falls back to <code>stripe.conf</code> or the hardcoded default. Useful for resetting a field to its built-in value.</li> | |
| 159 | <li><strong>Stripe section first</strong> — paste the keys from your Stripe Dashboard there before anything else. Recurring charges and the buyer checkout flow both stay inert until those keys are real (the dashboard ships with <code>pk_test_REPLACE_ME</code> placeholders).</li> | |
| 160 | </ul> | |
| 161 | </div> | |
| 162 | ||
| 163 | [if:$has_flash] | |
| 164 | <div class="ac-flash $flash_kind">$flash_msg</div> | |
| 165 | [/if] | |
| 166 | ||
| 167 | <form method="POST" action="/admin_config_action.cgi"> | |
| 168 | ||
| 169 | [loop:@group_blocks] | |
| 170 | <div class="ac-sec-anchor" id="sec-$loop1.slug"></div> | |
| 171 | <div class="ac-group"> | |
| 172 | <div class="ac-section-head"> | |
| 173 | <h2>$loop1.name</h2> | |
| 174 | [if:$loop1.has_help]<a class="ac-help-toggle" href="#ac-help-$loop1.slug" aria-label="Open help">?<span class="ac-help-tip">$loop1.help_summary — click for details</span></a>[/if] | |
| 175 | </div> | |
| 176 | ||
| 177 | [loop:@loop1.rows] | |
| 178 | <div class="ac-field"> | |
| 179 | <label> | |
| 180 | $loop2.label | |
| 181 | <span class="k">$loop2.key</span> | |
| 182 | </label> | |
| 183 | <div> | |
| 184 | [if:$loop2.secret] | |
| 185 | <input type="password" name="cfg_$loop2.key" value="" placeholder="[if:$loop2.is_set]Already set ···· leave blank to keep[/if][if:!$loop2.is_set]Not set[/if]" autocomplete="off"> | |
| 186 | [/if] | |
| 187 | [if:!$loop2.secret] | |
| 188 | <input type="text" name="cfg_$loop2.key" value="$loop2.value" autocomplete="off"> | |
| 189 | [/if] | |
| 190 | <div class="desc">$loop2.description</div> | |
| 191 | </div> | |
| 192 | <div style="text-align:right"> | |
| 193 | [if:$loop2.is_set]<span class="ac-pill set">Set</span>[/if] | |
| 194 | [if:!$loop2.is_set]<span class="ac-pill empty">Empty</span>[/if] | |
| 195 | <div style="margin-top:4px"><span class="ac-pill src-$loop2.source">$loop2.source</span></div> | |
| 196 | </div> | |
| 197 | </div> | |
| 198 | [/loop] | |
| 199 | ||
| 200 | [if:$loop1.has_help] | |
| 201 | <div class="ac-help-content" id="ac-help-$loop1.slug"> | |
| 202 | <a class="ac-help-backdrop" href="#sec-$loop1.slug" aria-label="Close help"></a> | |
| 203 | <div class="ac-help-modal" role="dialog" aria-modal="true"> | |
| 204 | <div class="ac-help-modal-head"> | |
| 205 | <div> | |
| 206 | <h2>$loop1.name</h2> | |
| 207 | <div class="ac-sum">$loop1.help_summary</div> | |
| 208 | </div> | |
| 209 | <a href="#sec-$loop1.slug" class="ac-help-close" aria-label="Close">×</a> | |
| 210 | </div> | |
| 211 | <div class="ac-help-modal-body">$loop1.help_detail</div> | |
| 212 | </div> | |
| 213 | </div> | |
| 214 | [/if] | |
| 215 | </div> | |
| 216 | [/loop] | |
| 217 | ||
| 218 | <div class="ac-actions"> | |
| 219 | <button type="submit" class="btn btn-primary">Save changes</button> | |
| 220 | </div> | |
| 221 | </form> | |
| 222 | ||
| 223 | <div class="ac-codebox"> | |
| 224 | <h2>Lives in code (not editable here)</h2> | |
| 225 | <table> | |
| 226 | [loop:@code_only] | |
| 227 | <tr> | |
| 228 | <td class="k">$loop1.key</td> | |
| 229 | <td>$loop1.note</td> | |
| 230 | </tr> | |
| 231 | [/loop] | |
| 232 | </table> | |
| 233 | </div> | |
| 234 | ||
| 235 | </div> |