index.twig 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. {% extends 'layout.twig' %}
  2. {% block title %}Settings — IRDB{% endblock %}
  3. {% block content %}
  4. {% import 'partials/sort.twig' as sort %}
  5. <div class="mx-auto max-w-5xl space-y-6">
  6. <div class="flex items-center justify-between">
  7. <h1 class="text-2xl font-semibold tracking-tight">Settings</h1>
  8. <span class="text-xs text-slate-500 dark:text-slate-400">Admin only · read-only · masked secrets</span>
  9. </div>
  10. {% if error %}
  11. <div class="rounded-md border border-red-300 bg-red-50 px-4 py-2 text-sm text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-300">{{ error }}</div>
  12. {% endif %}
  13. {# ------------------------- Configuration ------------------------- #}
  14. {% if config and config.sections %}
  15. <section class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  16. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Configuration</h2>
  17. <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">Effective values from the api's environment. Secrets are masked (<code>***</code>) or previewed (first 8 chars + …).</p>
  18. <div class="mt-4 grid gap-5 md:grid-cols-2">
  19. {% for section_name, items in config.sections %}
  20. <div class="rounded-lg border border-slate-100 dark:border-slate-800">
  21. <div class="border-b border-slate-100 bg-slate-50 px-4 py-2 text-xs font-semibold uppercase tracking-wider text-slate-500 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400">{{ section_name }}</div>
  22. <dl class="divide-y divide-slate-100 dark:divide-slate-800 text-sm">
  23. {% for key, value in items %}
  24. <div class="grid grid-cols-2 gap-2 px-4 py-2">
  25. <dt class="font-mono text-xs text-slate-500 dark:text-slate-400">{{ key }}</dt>
  26. <dd class="break-all font-mono text-xs text-slate-700 dark:text-slate-200">
  27. {%- if value is null -%}<span class="text-slate-400">—</span>
  28. {%- elseif value is same as(true) -%}true
  29. {%- elseif value is same as(false) -%}false
  30. {%- else -%}{{ value }}{%- endif -%}
  31. </dd>
  32. </div>
  33. {% endfor %}
  34. </dl>
  35. </div>
  36. {% endfor %}
  37. </div>
  38. </section>
  39. {% endif %}
  40. {# ------------------------------ Jobs ----------------------------- #}
  41. {% if jobs and jobs.jobs %}
  42. <section class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  43. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Jobs</h2>
  44. <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">Latest run, lock state, and manual-trigger buttons. Manual triggers run synchronously — wait for the response.</p>
  45. <div class="mt-4 overflow-hidden rounded-lg border border-slate-100 dark:border-slate-800">
  46. <table class="w-full text-sm" data-sortable-table="settings-jobs">
  47. <thead class="border-b border-slate-100 bg-slate-50 text-left text-xs uppercase tracking-wider text-slate-500 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-400">
  48. <tr>
  49. {{ sort.th('Name', 'name') }}
  50. {{ sort.th('Last status', 'status') }}
  51. {{ sort.th('Last finished', 'last_finished', 'date') }}
  52. {{ sort.th('Items', 'items', 'number') }}
  53. <th class="px-4 py-2 text-right font-medium">Trigger</th>
  54. </tr>
  55. </thead>
  56. <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
  57. {% for name, info in jobs.jobs %}
  58. <tr>
  59. <td class="px-4 py-2 align-top font-mono text-xs" data-sort-value="{{ name }}">
  60. {{ name }}
  61. {% if info.overdue %}
  62. <span class="ml-1 rounded bg-red-100 px-1.5 py-0.5 text-[0.65rem] font-mono uppercase text-red-800 dark:bg-red-950 dark:text-red-300">overdue</span>
  63. {% endif %}
  64. </td>
  65. <td class="px-4 py-2 align-top" data-sort-value="{{ info.last_run.status|default('') }}">
  66. {% if info.last_run %}
  67. {% set s = info.last_run.status %}
  68. {% set classes = {
  69. 'success': 'bg-emerald-100 text-emerald-900 dark:bg-emerald-900 dark:text-emerald-100',
  70. 'failure': 'bg-red-100 text-red-900 dark:bg-red-900 dark:text-red-100',
  71. 'skipped_locked': 'bg-amber-100 text-amber-900 dark:bg-amber-900 dark:text-amber-100',
  72. 'running': 'bg-blue-100 text-blue-900 dark:bg-blue-900 dark:text-blue-100',
  73. } %}
  74. <span class="rounded px-2 py-0.5 font-mono text-[0.65rem] uppercase {{ classes[s]|default('bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300') }}">{{ s }}</span>
  75. {% else %}
  76. <span class="text-xs text-slate-400">never run</span>
  77. {% endif %}
  78. </td>
  79. <td class="px-4 py-2 align-top font-mono text-xs text-slate-500" data-sort-value="{{ info.last_run.finished_at|default('') }}">
  80. {% if info.last_run.finished_at %}<time class="irdb-dt" datetime="{{ info.last_run.finished_at }}">{{ info.last_run.finished_at }}</time>{% else %}—{% endif %}
  81. </td>
  82. <td class="px-4 py-2 align-top font-mono text-xs text-slate-500" data-sort-value="{{ info.last_run.items_processed|default('') }}">
  83. {{ info.last_run.items_processed|default('—') }}
  84. </td>
  85. <td class="px-4 py-2 align-top text-right">
  86. {% if name != 'tick' %}
  87. <form method="post" action="/app/settings/jobs/trigger/{{ name }}" class="inline" x-data="{ submitting: false }" x-on:submit="submitting = true">
  88. <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
  89. <button type="submit" x-bind:disabled="submitting"
  90. class="rounded-md border border-slate-300 px-2 py-1 text-xs hover:bg-slate-50 disabled:opacity-50 dark:border-slate-700 dark:hover:bg-slate-800">
  91. <span x-show="!submitting">Run now</span>
  92. <span x-show="submitting" x-cloak>Running…</span>
  93. </button>
  94. </form>
  95. {% else %}
  96. <span class="text-xs text-slate-400">scheduled</span>
  97. {% endif %}
  98. </td>
  99. </tr>
  100. {% endfor %}
  101. </tbody>
  102. </table>
  103. </div>
  104. </section>
  105. {% endif %}
  106. {# -------------------- Demo & maintenance -------------------- #}
  107. <section class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  108. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Demo &amp; maintenance</h2>
  109. <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">Populate the database with sample data for screenshots and demos, or wipe operational data to start clean. Both actions are admin-only and audited.</p>
  110. <div class="mt-4 grid gap-4 md:grid-cols-2">
  111. <div class="rounded-lg border border-emerald-200 bg-emerald-50 p-4 dark:border-emerald-900 dark:bg-emerald-950/40"
  112. x-data="{ open: false, submitting: false }">
  113. <h3 class="text-sm font-semibold text-emerald-800 dark:text-emerald-200">Load demo data</h3>
  114. <p class="mt-1 text-xs text-emerald-900/80 dark:text-emerald-200/80">
  115. Inserts demo reporters, consumers, IPs, reports, manual blocks, allowlist entries, and synthetic GeoIP — then triggers a full score recompute. Returns "already seeded" if demo data is present.
  116. </p>
  117. <div class="mt-3">
  118. <button type="button" x-on:click="open = true"
  119. class="rounded-md bg-emerald-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-emerald-500">
  120. Load demo data…
  121. </button>
  122. </div>
  123. <div x-show="open" x-cloak
  124. class="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/70 px-4">
  125. <div class="w-full max-w-md rounded-2xl border border-emerald-300 bg-white p-6 shadow-2xl dark:border-emerald-700 dark:bg-slate-900">
  126. <h3 class="text-lg font-semibold text-emerald-700 dark:text-emerald-300">Load demo data?</h3>
  127. <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
  128. This will add sample reporters, consumers, IPs, and reports to the database, then run a full recompute. Existing real data is left untouched.
  129. </p>
  130. <form method="post" action="/app/settings/maintenance/seed-demo" class="mt-4 flex justify-end gap-2"
  131. x-on:submit="submitting = true">
  132. <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
  133. <button type="button" x-on:click="open = false"
  134. class="rounded-md border border-slate-300 px-3 py-1.5 text-sm hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800">Cancel</button>
  135. <button type="submit" x-bind:disabled="submitting"
  136. class="rounded-md bg-emerald-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-emerald-500 disabled:opacity-50">
  137. <span x-show="!submitting">Load demo data</span>
  138. <span x-show="submitting" x-cloak>Loading…</span>
  139. </button>
  140. </form>
  141. </div>
  142. </div>
  143. </div>
  144. <div class="rounded-lg border border-red-200 bg-red-50 p-4 dark:border-red-900 dark:bg-red-950/40"
  145. x-data="{ open: false, confirm: '', submitting: false }">
  146. <h3 class="text-sm font-semibold text-red-800 dark:text-red-200">Purge operational data</h3>
  147. <p class="mt-1 text-xs text-red-900/80 dark:text-red-200/80">
  148. Deletes all reports, scores, manual blocks, allowlist, audit log, reporters, consumers, and non-service tokens. Users, OIDC mappings, and categories are preserved.
  149. </p>
  150. <div class="mt-3">
  151. <button type="button" x-on:click="open = true"
  152. class="rounded-md border border-red-400 bg-white px-3 py-1.5 text-xs font-medium text-red-700 hover:bg-red-50 dark:border-red-700 dark:bg-slate-900 dark:text-red-300 dark:hover:bg-slate-800">
  153. Purge data…
  154. </button>
  155. </div>
  156. <div x-show="open" x-cloak
  157. class="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/70 px-4">
  158. <div class="w-full max-w-md rounded-2xl border border-red-400 bg-white p-6 shadow-2xl dark:border-red-700 dark:bg-slate-900">
  159. <h3 class="text-lg font-semibold text-red-700 dark:text-red-300">Purge operational data?</h3>
  160. <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
  161. This will <strong class="text-red-700 dark:text-red-300">permanently delete</strong> reports, scores, blocks, allowlist, audit log, reporters, consumers, and tokens. The service token, your user account, OIDC mappings, and abuse categories are preserved.
  162. </p>
  163. <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">
  164. Type <code class="rounded bg-slate-100 px-1 py-0.5 font-mono text-xs text-red-700 dark:bg-slate-800 dark:text-red-300">PURGE</code> to confirm:
  165. </p>
  166. <form method="post" action="/app/settings/maintenance/purge" class="mt-3"
  167. x-on:submit="submitting = true">
  168. <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
  169. <input type="text" name="confirm" autocomplete="off" x-model="confirm"
  170. class="w-full rounded-md border border-slate-300 bg-white px-3 py-2 font-mono text-sm dark:border-slate-700 dark:bg-slate-950"
  171. placeholder="PURGE">
  172. <div class="mt-4 flex justify-end gap-2">
  173. <button type="button" x-on:click="open = false; confirm = ''"
  174. class="rounded-md border border-slate-300 px-3 py-1.5 text-sm hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800">Cancel</button>
  175. <button type="submit" x-bind:disabled="confirm !== 'PURGE' || submitting"
  176. class="rounded-md bg-red-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-red-500 disabled:opacity-40">
  177. <span x-show="!submitting">Purge data</span>
  178. <span x-show="submitting" x-cloak>Purging…</span>
  179. </button>
  180. </div>
  181. </form>
  182. </div>
  183. </div>
  184. </div>
  185. </div>
  186. </section>
  187. {# ------------------------------ GeoIP ----------------------------- #}
  188. {% if config and config.sections.geoip %}
  189. <section class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  190. <h2 class="text-sm font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">GeoIP</h2>
  191. <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">Provider, on-disk paths, and credential state. DB freshness comes from healthz; the trigger button on <code>refresh-geoip</code> is in the Jobs section above.</p>
  192. <dl class="mt-3 grid grid-cols-3 gap-2 text-sm">
  193. <dt class="text-slate-500 dark:text-slate-400">Provider</dt>
  194. <dd class="col-span-2 font-mono text-xs">{{ config.sections.geoip.GEOIP_PROVIDER|default('—') }}</dd>
  195. <dt class="text-slate-500 dark:text-slate-400">Country DB</dt>
  196. <dd class="col-span-2 font-mono text-xs">{{ config.sections.geoip.GEOIP_COUNTRY_DB|default('—') }}</dd>
  197. <dt class="text-slate-500 dark:text-slate-400">ASN DB</dt>
  198. <dd class="col-span-2 font-mono text-xs">{{ config.sections.geoip.GEOIP_ASN_DB|default('—') }}</dd>
  199. <dt class="text-slate-500 dark:text-slate-400">MaxMind key</dt>
  200. <dd class="col-span-2 font-mono text-xs">{{ config.sections.geoip.MAXMIND_LICENSE_KEY ? config.sections.geoip.MAXMIND_LICENSE_KEY : '(unset)' }}</dd>
  201. <dt class="text-slate-500 dark:text-slate-400">IPinfo token</dt>
  202. <dd class="col-span-2 font-mono text-xs">{{ config.sections.geoip.IPINFO_TOKEN ? config.sections.geoip.IPINFO_TOKEN : '(unset)' }}</dd>
  203. </dl>
  204. </section>
  205. {% endif %}
  206. </div>
  207. {% endblock %}