Connor McCutcheon
/ SkyCode
admin.html
html
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SkyCode Admin</title>
  <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
  <link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
  <script src="https://unpkg.com/htmx.org@2.0.4"></script>
</head>
<body class="bg-base-200 min-h-screen">
  <!-- Navbar -->
  <div class="navbar bg-base-300 border-b border-base-100 h-12 min-h-12">
    <div class="flex-1">
      <a href="/" class="btn btn-ghost btn-sm text-lg font-bold">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
          <path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5" />
        </svg>
        SkyCode
      </a>
      <span class="badge badge-warning ml-2">Admin</span>
    </div>
    <div class="flex-none gap-2">
      <a href="/editor" class="btn btn-ghost btn-sm">Editor</a>
      {{with CurrentUser}}
      <div class="dropdown dropdown-end">
        <div tabindex="0" role="button" class="btn btn-ghost btn-sm gap-1">
          <div class="w-6 rounded-full overflow-hidden">
            <img alt="{{.Name}}" src="{{.Avatar}}" class="w-full h-full object-cover" />
          </div>
          <span class="text-xs text-base-content/70">@{{.Handle}}</span>
        </div>
        <ul tabindex="0" class="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow">
          <li><a href="https://theskyscape.com/user/{{.Handle}}" target="_blank">View Profile</a></li>
          <li><a href="/auth/logout">Logout</a></li>
        </ul>
      </div>
      {{end}}
    </div>
  </div>
  <!-- Main Content -->
  <div class="container mx-auto p-6 max-w-6xl">
    <h1 class="text-2xl font-bold mb-6">Database Administration</h1>
    <!-- Tabs -->
    <div class="tabs tabs-box mb-6">
      <a class="tab tab-active" onclick="showTab('users')" id="tab-users">Users</a>
      <a class="tab" onclick="showTab('files')" id="tab-files">Files</a>
      <a class="tab" onclick="showTab('errors')" id="tab-errors">Errors</a>
    </div>
    <!-- Users Panel -->
    <div id="panel-users">
      <div class="flex justify-between items-center mb-4">
        <h2 class="text-lg font-semibold">Users</h2>
        <div class="flex gap-2">
          <button class="btn btn-sm btn-warning"
                  hx-post="/admin/users/cleanup-duplicates"
                  hx-target="#users-table"
                  hx-swap="innerHTML"
                  hx-confirm="Delete all duplicate users and users with empty handles?">
            Cleanup Duplicates
          </button>
          <button class="btn btn-sm btn-ghost"
                  hx-get="/admin/users"
                  hx-target="#users-table"
                  hx-swap="innerHTML">
            Refresh
          </button>
        </div>
      </div>
      <div class="overflow-x-auto">
        <table class="table table-zebra w-full">
          <thead>
            <tr>
              <th>Avatar</th>
              <th>Handle</th>
              <th>Name</th>
              <th>Email</th>
              <th>Created</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody id="users-table" hx-get="/admin/users" hx-trigger="load" hx-swap="innerHTML">
            <tr><td colspan="6" class="text-center text-base-content/50">Loading...</td></tr>
          </tbody>
        </table>
      </div>
    </div>
    <!-- Files Panel -->
    <div id="panel-files" class="hidden">
      <div class="flex justify-between items-center mb-4">
        <h2 class="text-lg font-semibold">Files</h2>
        <button class="btn btn-sm btn-ghost"
                hx-get="/admin/files"
                hx-target="#files-table"
                hx-swap="innerHTML">
          Refresh
        </button>
      </div>
      <div class="overflow-x-auto">
        <table class="table table-zebra w-full">
          <thead>
            <tr>
              <th>Owner ID</th>
              <th>Path</th>
              <th>Language</th>
              <th>Size</th>
              <th>Created</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody id="files-table">
            <tr><td colspan="6" class="text-center text-base-content/50">Click Files tab to load...</td></tr>
          </tbody>
        </table>
      </div>
    </div>
    <!-- Errors Panel -->
    <div id="panel-errors" class="hidden">
      <div class="flex justify-between items-center mb-4">
        <h2 class="text-lg font-semibold">Error Logs</h2>
        <div class="flex gap-2">
          <button class="btn btn-sm btn-warning"
                  hx-post="/admin/errors/clear"
                  hx-target="#errors-table"
                  hx-swap="innerHTML"
                  hx-confirm="Clear all error logs?">
            Clear All
          </button>
          <button class="btn btn-sm btn-ghost"
                  hx-get="/admin/errors"
                  hx-target="#errors-table"
                  hx-swap="innerHTML">
            Refresh
          </button>
        </div>
      </div>
      <div class="overflow-x-auto">
        <table class="table table-zebra w-full">
          <thead>
            <tr>
              <th>Name</th>
              <th>Message</th>
              <th>Count</th>
              <th>Last Seen</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody id="errors-table">
            <tr><td colspan="5" class="text-center text-base-content/50">Click Errors tab to load...</td></tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>
  <!-- Edit User Modal Container -->
  <div id="edit-form-container" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center">
    <!-- Edit form loaded here by HTMX -->
  </div>
  <script>
    // Tab switching
    function showTab(tab) {
      document.getElementById('tab-users').classList.toggle('tab-active', tab === 'users');
      document.getElementById('tab-files').classList.toggle('tab-active', tab === 'files');
      document.getElementById('tab-errors').classList.toggle('tab-active', tab === 'errors');
      document.getElementById('panel-users').classList.toggle('hidden', tab !== 'users');
      document.getElementById('panel-files').classList.toggle('hidden', tab !== 'files');
      document.getElementById('panel-errors').classList.toggle('hidden', tab !== 'errors');
      // Load files on first switch to files tab
      if (tab === 'files') {
        const filesTable = document.getElementById('files-table');
        if (!filesTable.dataset.loaded) {
          htmx.ajax('GET', '/admin/files', {target: '#files-table', swap: 'innerHTML'});
          filesTable.dataset.loaded = 'true';
        }
      }
      // Load errors on first switch to errors tab
      if (tab === 'errors') {
        const errorsTable = document.getElementById('errors-table');
        if (!errorsTable.dataset.loaded) {
          htmx.ajax('GET', '/admin/errors', {target: '#errors-table', swap: 'innerHTML'});
          errorsTable.dataset.loaded = 'true';
        }
      }
    }
    // Show modal when edit form is loaded
    document.body.addEventListener('htmx:afterSwap', function(evt) {
      if (evt.detail.target.id === 'edit-form-container') {
        const container = document.getElementById('edit-form-container');
        if (container.innerHTML.trim()) {
          container.classList.remove('hidden');
          container.classList.add('flex');
        } else {
          container.classList.add('hidden');
          container.classList.remove('flex');
        }
      }
    });
    // Close modal when clicking backdrop
    document.getElementById('edit-form-container').addEventListener('click', function(e) {
      if (e.target === this) {
        this.innerHTML = '';
        this.classList.add('hidden');
        this.classList.remove('flex');
      }
    });
  </script>
</body>
</html>
No comments yet.