edit.twig 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. {% extends 'layout.twig' %}
  2. {% block title %}{{ reporter.name }} — Reporter — IRDB{% endblock %}
  3. {% block content %}
  4. <div class="mx-auto max-w-3xl">
  5. <a href="/app/reporters" class="text-sm text-slate-500 hover:underline dark:text-slate-400">← Back to reporters</a>
  6. <h1 class="mt-3 text-2xl font-semibold tracking-tight font-mono">{{ reporter.name }}</h1>
  7. <form method="post" action="/app/reporters/{{ reporter.id }}" class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm dark:border-slate-800 dark:bg-slate-900">
  8. <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
  9. <div class="grid grid-cols-1 gap-3 md:grid-cols-2 text-sm">
  10. <div>
  11. <label for="r-name" class="block text-xs font-medium text-slate-600 dark:text-slate-400">Name</label>
  12. <input type="text" id="r-name" name="name" value="{{ reporter.name }}" {% if not can_write %}readonly{% endif %}
  13. class="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 font-mono dark:border-slate-700 dark:bg-slate-950">
  14. </div>
  15. <div>
  16. <label for="r-tw" class="block text-xs font-medium text-slate-600 dark:text-slate-400">Trust weight</label>
  17. <input type="number" id="r-tw" name="trust_weight" step="0.01" min="0" max="2" value="{{ reporter.trust_weight }}" {% if not can_write %}readonly{% endif %}
  18. class="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 dark:border-slate-700 dark:bg-slate-950">
  19. <p class="mt-1 text-xs text-slate-400">0–2.0; 1.0 default; affects how heavily this reporter influences scores.</p>
  20. </div>
  21. <div class="md:col-span-2">
  22. <label for="r-desc" class="block text-xs font-medium text-slate-600 dark:text-slate-400">Description</label>
  23. <input type="text" id="r-desc" name="description" value="{{ reporter.description|default('') }}" {% if not can_write %}readonly{% endif %}
  24. class="mt-1 w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 dark:border-slate-700 dark:bg-slate-950">
  25. </div>
  26. <div class="md:col-span-2">
  27. <label class="flex items-center gap-2 text-xs text-slate-600 dark:text-slate-400">
  28. <input type="checkbox" name="is_active" value="1" {% if reporter.is_active %}checked{% endif %} {% if not can_write %}disabled{% endif %}>
  29. active
  30. </label>
  31. </div>
  32. <div class="md:col-span-2">
  33. {# Hidden 0 sent first so an unchecked box still posts a value; PHP's parser keeps the last occurrence. #}
  34. <input type="hidden" name="audit_enabled" value="0">
  35. <label class="flex items-start gap-2 text-xs text-slate-600 dark:text-slate-400">
  36. <input type="checkbox" name="audit_enabled" value="1" class="mt-0.5"
  37. {% if reporter.audit_enabled %}checked{% endif %} {% if not can_write %}disabled{% endif %}>
  38. <span>
  39. <span class="font-medium text-slate-700 dark:text-slate-200">Audit log: write a <code>report.received</code> entry per ingest</span>
  40. <span class="block text-[0.7rem] text-slate-500 dark:text-slate-400">Off silences this reporter even when the global toggle is on (Settings → Audit toggles).</span>
  41. </span>
  42. </label>
  43. </div>
  44. </div>
  45. {% if can_write %}
  46. <div class="mt-4 flex justify-end">
  47. <button type="submit" class="rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-indigo-500">Save</button>
  48. </div>
  49. {% endif %}
  50. </form>
  51. <section class="mt-8 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900">
  52. <header class="flex items-center justify-between gap-3 border-b border-slate-200 px-5 py-3 dark:border-slate-800">
  53. <div>
  54. <h2 class="text-sm font-semibold tracking-tight">Recent reports</h2>
  55. <p class="text-xs text-slate-500 dark:text-slate-400">10 most recent audit entries for this reporter.</p>
  56. </div>
  57. <a href="/app/audit?subject_kind=reporter&amp;subject_id={{ reporter.id }}" class="whitespace-nowrap text-xs font-medium text-indigo-600 hover:underline dark:text-indigo-400">View all in audit log →</a>
  58. </header>
  59. {% if audit_items|default([])|length > 0 %}
  60. <div x-data="rowExpander">
  61. <table class="w-full text-sm">
  62. <thead class="border-b border-slate-200 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">
  63. <tr>
  64. <th class="px-5 py-2 font-medium">When</th>
  65. <th class="px-5 py-2 font-medium">Actor</th>
  66. <th class="px-5 py-2 font-medium">Action</th>
  67. <th class="px-5 py-2 text-right font-medium">Payload</th>
  68. </tr>
  69. </thead>
  70. <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
  71. {% for ev in audit_items %}
  72. <tr>
  73. <td class="px-5 py-2 align-top"><time class="irdb-dt font-mono text-xs text-slate-600 dark:text-slate-300" datetime="{{ ev.occurred_at }}" title="{{ ev.occurred_at }}">{{ ev.occurred_at }}</time></td>
  74. <td class="px-5 py-2 align-top text-xs">
  75. <span class="rounded bg-slate-100 px-1.5 py-0.5 font-mono uppercase tracking-tight text-slate-700 dark:bg-slate-800 dark:text-slate-300">{{ ev.actor_kind }}</span>
  76. {% if ev.actor_id %}<span class="ml-1 font-mono text-slate-500">#{{ ev.actor_id }}</span>{% endif %}
  77. </td>
  78. <td class="px-5 py-2 align-top">
  79. <span class="inline-block rounded bg-blue-100 px-2 py-0.5 font-mono text-[0.7rem] uppercase tracking-tight text-blue-900 dark:bg-blue-900 dark:text-blue-100">{{ ev.action }}</span>
  80. </td>
  81. <td class="px-5 py-2 align-top text-right">
  82. {% if ev.details %}
  83. <button type="button" x-on:click="toggle({{ ev.id }})" class="rounded border border-slate-300 px-2 py-0.5 text-xs hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800">View</button>
  84. {% else %}
  85. <span class="text-xs text-slate-400">—</span>
  86. {% endif %}
  87. </td>
  88. </tr>
  89. {% if ev.details %}
  90. <tr x-show="isOpen({{ ev.id }})" x-cloak>
  91. <td colspan="4" class="bg-slate-50 px-5 py-3 dark:bg-slate-950">
  92. {% if ev.details.changes is defined and ev.details.changes is iterable and ev.details.changes|length > 0 %}
  93. <div class="mb-2 text-[0.7rem] font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">Changes</div>
  94. <table class="w-full text-xs">
  95. <thead class="text-left text-[0.7rem] uppercase text-slate-500 dark:text-slate-400">
  96. <tr>
  97. <th class="px-2 py-1 font-medium">Field</th>
  98. <th class="px-2 py-1 font-medium">Before</th>
  99. <th class="px-2 py-1 font-medium">After</th>
  100. </tr>
  101. </thead>
  102. <tbody class="divide-y divide-slate-200 bg-white dark:divide-slate-800 dark:bg-slate-900">
  103. {% for field, change in ev.details.changes %}
  104. <tr>
  105. <td class="px-2 py-1 font-mono text-slate-700 dark:text-slate-200">{{ field }}</td>
  106. <td class="px-2 py-1 align-top">
  107. {% if change.from is null %}
  108. <span class="text-slate-400">∅</span>
  109. {% elseif change.from is iterable %}
  110. <pre class="overflow-x-auto rounded bg-rose-50 px-2 py-1 font-mono text-[0.7rem] text-rose-900 dark:bg-rose-950 dark:text-rose-200">{{ change.from|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>
  111. {% else %}
  112. <span class="rounded bg-rose-50 px-1.5 py-0.5 font-mono text-rose-900 dark:bg-rose-950 dark:text-rose-200">{{ change.from }}</span>
  113. {% endif %}
  114. </td>
  115. <td class="px-2 py-1 align-top">
  116. {% if change.to is null %}
  117. <span class="text-slate-400">∅</span>
  118. {% elseif change.to is iterable %}
  119. <pre class="overflow-x-auto rounded bg-emerald-50 px-2 py-1 font-mono text-[0.7rem] text-emerald-900 dark:bg-emerald-950 dark:text-emerald-200">{{ change.to|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>
  120. {% else %}
  121. <span class="rounded bg-emerald-50 px-1.5 py-0.5 font-mono text-emerald-900 dark:bg-emerald-950 dark:text-emerald-200">{{ change.to }}</span>
  122. {% endif %}
  123. </td>
  124. </tr>
  125. {% endfor %}
  126. </tbody>
  127. </table>
  128. {% else %}
  129. <pre class="overflow-x-auto rounded bg-white p-3 text-xs dark:bg-slate-900">{{ ev.details|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>
  130. {% endif %}
  131. </td>
  132. </tr>
  133. {% endif %}
  134. {% endfor %}
  135. </tbody>
  136. </table>
  137. </div>
  138. {% else %}
  139. <p class="px-5 py-6 text-center text-sm text-slate-400">No activity yet.</p>
  140. {% endif %}
  141. </section>
  142. </div>
  143. {% endblock %}