dashboard.html 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="columns">
  4. <div class="column is-3">
  5. <aside class="menu">
  6. <p class="menu-label">Administration</p>
  7. <ul class="menu-list">
  8. <li><a href="{{ url_for('admin.dashboard') }}" class="is-active">Dashboard</a></li>
  9. <li><a href="{{ url_for('admin.user_search') }}">User Search</a></li>
  10. <li><a>Players</a></li>
  11. <li><a>Bans</a></li>
  12. <li><a href="{{ url_for('admin.event_settings') }}">Event Settings</a></li>
  13. </ul>
  14. </aside>
  15. </div>
  16. <div class="column">
  17. <h1 class="title">Live Feed</h1>
  18. <div class="box" style="height: 400px; overflow-y: auto; background-color: #111;" id="event-feed">
  19. <!-- Events will be appended here via WebSocket -->
  20. </div>
  21. <div class="field is-grouped">
  22. <div class="control">
  23. <div class="select">
  24. <select id="package-filter">
  25. <option value="ALL">All Packages</option>
  26. </select>
  27. </div>
  28. <div class="select">
  29. <select id="event-filter">
  30. <option value="ALL">All Events</option>
  31. </select>
  32. </div>
  33. </div>
  34. <div class="control">
  35. <button class="button is-info" onclick="clearFeed()">Clear Feed</button>
  36. </div>
  37. </div>
  38. </div>
  39. </div>
  40. {% endblock %}
  41. {% block scripts %}
  42. <script>
  43. const socket = io();
  44. const feed = document.getElementById('event-feed');
  45. const packageFilter = document.getElementById('package-filter');
  46. const eventFilter = document.getElementById('event-filter'); // Now dynamic
  47. let registeredEvents = []; // List of full class names
  48. socket.on('connect', () => {
  49. console.log('Connected to WebSocket');
  50. });
  51. socket.on('event_metadata', (data) => {
  52. console.log("Received metadata", data);
  53. registeredEvents = data.events;
  54. populateFilters();
  55. });
  56. socket.on('universal_event', (data) => {
  57. // data = {type: 'PlayerJoinEvent', package: 'org.bukkit...', event_data: {...}, ...}
  58. const div = document.createElement('div');
  59. div.className = 'notification is-small is-dark';
  60. div.style.marginBottom = '0.5rem';
  61. div.style.padding = '0.5rem';
  62. div.dataset.package = data.package;
  63. div.dataset.type = data.type;
  64. // Filtering
  65. if (!shouldShowEvent(data.package, data.type)) {
  66. div.style.display = 'none';
  67. }
  68. const time = new Date(data.timestamp).toLocaleTimeString();
  69. let content = `[${time}] <strong>${data.type}</strong> `;
  70. if (data.player) {
  71. content += `(${data.player}) `;
  72. }
  73. // Format event_data
  74. if (data.event_data) {
  75. content += `<br/><span style="font-size: 0.8em; color: #ccc;">${JSON.stringify(data.event_data).substring(0, 200)}</span>`;
  76. }
  77. div.innerHTML = content;
  78. feed.appendChild(div);
  79. if (div.style.display !== 'none') {
  80. feed.scrollTop = feed.scrollHeight;
  81. }
  82. });
  83. // Listen for filter changes
  84. packageFilter.addEventListener('change', () => {
  85. populateEventFilter(); // Update event dropdown based on package
  86. applyFilters();
  87. });
  88. eventFilter.addEventListener('change', applyFilters);
  89. function populateFilters() {
  90. // Get unique packages
  91. const packages = new Set(registeredEvents.map(e => e.substring(0, e.lastIndexOf('.'))));
  92. packageFilter.innerHTML = '<option value="ALL">All Packages</option>';
  93. [...packages].sort().forEach(pkg => {
  94. const opt = document.createElement('option');
  95. opt.value = pkg;
  96. opt.textContent = pkg.replace('org.bukkit.event.', '...'); // Shorten common prefix
  97. packageFilter.appendChild(opt);
  98. });
  99. populateEventFilter();
  100. }
  101. function populateEventFilter() {
  102. const selectedPackage = packageFilter.value;
  103. const currentSelection = eventFilter.value;
  104. eventFilter.innerHTML = '<option value="ALL">All Events</option>';
  105. registeredEvents
  106. .filter(e => selectedPackage === 'ALL' || e.startsWith(selectedPackage))
  107. .map(e => e.substring(e.lastIndexOf('.') + 1)) // Get simple name
  108. .sort()
  109. .forEach(simpleName => {
  110. const opt = document.createElement('option');
  111. opt.value = simpleName;
  112. opt.textContent = simpleName;
  113. eventFilter.appendChild(opt);
  114. });
  115. // restore selection if valid
  116. // simplistic restoration, might reset if package changed effectively
  117. }
  118. function applyFilters() {
  119. const pFilter = packageFilter.value;
  120. const eFilter = eventFilter.value;
  121. for (let div of feed.children) {
  122. const p = div.dataset.package;
  123. const t = div.dataset.type;
  124. let show = true;
  125. if (pFilter !== 'ALL' && p !== pFilter) show = false;
  126. if (eFilter !== 'ALL' && t !== eFilter) show = false;
  127. div.style.display = show ? '' : 'none';
  128. }
  129. }
  130. function shouldShowEvent(pkg, type) {
  131. const pFilter = packageFilter.value;
  132. const eFilter = eventFilter.value;
  133. if (pFilter !== 'ALL' && pkg !== pFilter) return false;
  134. if (eFilter !== 'ALL' && type !== eFilter) return false;
  135. return true;
  136. }
  137. function clearFeed() {
  138. feed.innerHTML = '';
  139. }
  140. </script>
  141. {% endblock %}