added on local at 2026-07-01 13:47:57
| 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: 920px; margin: 0 auto; } | |
| 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(59,130,246,0.08); | |
| 60 | border: 1px solid rgba(59,130,246,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: #93c5fd; | |
| 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(124,58,237,0.18); color: #c4b5fd; } | |
| 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 | </style> | |
| 106 | ||
| 107 | <div class="ac-wrap"> | |
| 108 | ||
| 109 | <div style="margin-bottom:14px"> | |
| 110 | <h1 style="font-size:22px;color:var(--col-text);margin:0 0 6px">Software Configuration</h1> | |
| 111 | <p class="text-sm text-dim" style="margin:0"> | |
| 112 | Edit platform-wide settings. Saved values land in <code>platform_settings</code> and override the hardcoded defaults in <code>MODS/WebSTLs/Config.pm</code>. | |
| 113 | </p> | |
| 114 | </div> | |
| 115 | ||
| 116 | <div class="ac-intro"> | |
| 117 | <h3>How this page works</h3> | |
| 118 | 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. | |
| 119 | <ul> | |
| 120 | <li><strong>Source pills</strong> show where each value comes from right now: | |
| 121 | <span class="ac-pill src-db">db</span> = stored here in <code>platform_settings</code>; | |
| 122 | <span class="ac-pill src-file">file</span> = read from <code>/private/stripe.conf</code> (legacy bootstrap path); | |
| 123 | <span class="ac-pill src-default">default</span> = the hardcoded fallback in <code>MODS/WebSTLs/Config.pm</code>.</li> | |
| 124 | <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> | |
| 125 | <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> | |
| 126 | <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> | |
| 127 | </ul> | |
| 128 | </div> | |
| 129 | ||
| 130 | [if:$has_flash] | |
| 131 | <div class="ac-flash $flash_kind">$flash_msg</div> | |
| 132 | [/if] | |
| 133 | ||
| 134 | <form method="POST" action="/admin_config_action.cgi"> | |
| 135 | ||
| 136 | [loop:@group_blocks] | |
| 137 | <div class="ac-group"> | |
| 138 | <h2>$loop1.name</h2> | |
| 139 | [loop:@loop1.rows] | |
| 140 | <div class="ac-field"> | |
| 141 | <label> | |
| 142 | $loop2.label | |
| 143 | <span class="k">$loop2.key</span> | |
| 144 | </label> | |
| 145 | <div> | |
| 146 | [if:$loop2.secret] | |
| 147 | <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"> | |
| 148 | [/if] | |
| 149 | [if:!$loop2.secret] | |
| 150 | <input type="text" name="cfg_$loop2.key" value="$loop2.value" autocomplete="off"> | |
| 151 | [/if] | |
| 152 | <div class="desc">$loop2.description</div> | |
| 153 | </div> | |
| 154 | <div style="text-align:right"> | |
| 155 | [if:$loop2.is_set]<span class="ac-pill set">Set</span>[/if] | |
| 156 | [if:!$loop2.is_set]<span class="ac-pill empty">Empty</span>[/if] | |
| 157 | <div style="margin-top:4px"><span class="ac-pill src-$loop2.source">$loop2.source</span></div> | |
| 158 | </div> | |
| 159 | </div> | |
| 160 | [/loop] | |
| 161 | </div> | |
| 162 | [/loop] | |
| 163 | ||
| 164 | <div class="ac-actions"> | |
| 165 | <button type="submit" class="btn btn-primary">Save changes</button> | |
| 166 | </div> | |
| 167 | </form> | |
| 168 | ||
| 169 | <div class="ac-codebox"> | |
| 170 | <h2>Lives in code (not editable here)</h2> | |
| 171 | <table> | |
| 172 | [loop:@code_only] | |
| 173 | <tr> | |
| 174 | <td class="k">$loop1.key</td> | |
| 175 | <td>$loop1.note</td> | |
| 176 | </tr> | |
| 177 | [/loop] | |
| 178 | </table> | |
| 179 | </div> | |
| 180 | ||
| 181 | </div> |