HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //proc/thread-self/root/tmp/gpuq_out.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>GPUQ Dashboard | kiwerkzeuge.de</title>
    <style>
      :root {
        --bg: #0f172a;
        --panel: #111827;
        --line: #334155;
        --text: #e5e7eb;
        --muted: #94a3b8;
        --ok: #22c55e;
        --warn: #f59e0b;
        --bad: #ef4444;
      }
      body {
        margin: 0;
        font-family: "Inter", "Segoe UI", sans-serif;
        background: radial-gradient(circle at 15% 20%, #1e293b 0%, var(--bg) 55%);
        color: var(--text);
      }
      .page {
        max-width: 1200px;
        margin: 0 auto;
        padding: 1rem;
      }
      .row {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
        gap: 0.75rem;
      }
      .card {
        background: color-mix(in srgb, var(--panel) 92%, #000 8%);
        border: 1px solid var(--line);
        border-radius: 10px;
        padding: 0.9rem;
      }
      .title {
        margin: 0;
        font-size: 1.2rem;
      }
      .muted {
        color: var(--muted);
      }
      .stat {
        font-size: 1.7rem;
        margin-top: 0.2rem;
      }
      table {
        width: 100%;
        border-collapse: collapse;
        font-size: 0.9rem;
      }
      th,
      td {
        border-bottom: 1px solid var(--line);
        padding: 0.5rem 0.4rem;
        text-align: left;
      }
      .pill {
        border: 1px solid var(--line);
        border-radius: 999px;
        display: inline-block;
        padding: 0.1rem 0.45rem;
        font-size: 0.75rem;
      }
      .pill.high { color: #fb7185; }
      .pill.normal { color: #60a5fa; }
      .pill.low { color: #a3e635; }
      button, select, input {
        background: #0b1220;
        color: var(--text);
        border: 1px solid var(--line);
        border-radius: 6px;
        padding: 0.35rem 0.55rem;
      }
      button { cursor: pointer; }
      .ok { color: var(--ok); }
      .warn { color: var(--warn); }
      .bad { color: var(--bad); }
      .actions {
        display: flex;
        gap: 0.35rem;
        flex-wrap: wrap;
      }
      .mono { font-family: ui-monospace, Menlo, Consolas, monospace; }
      #statusLine {
        margin: 0.2rem 0 0.8rem;
      }
    </style>
  </head>
  <body>
    <main class="page">
      <h1 class="title">GPUQ Dashboard</h1>
      <p id="statusLine" class="muted">Loading queue state...</p>

      <section class="row">
        <article class="card">
          <div class="muted">Queued</div>
          <div id="queuedCount" class="stat">-</div>
        </article>
        <article class="card">
          <div class="muted">Running</div>
          <div id="runningCount" class="stat">-</div>
        </article>
        <article class="card">
          <div class="muted">Completed (24h)</div>
          <div id="completedCount" class="stat">-</div>
        </article>
        <article class="card">
          <div class="muted">Failed (24h)</div>
          <div id="failedCount" class="stat">-</div>
        </article>
      </section>

      <section class="card" style="margin-top: 0.8rem;">
        <h2 class="title" style="font-size: 1.05rem;">Live Queue</h2>
        <table id="queueTable">
          <thead>
            <tr>
              <th>Job</th>
              <th>App</th>
              <th>Type</th>
              <th>Priority</th>
              <th>Status</th>
              <th>Pos</th>
              <th>ETA</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody></tbody>
        </table>
      </section>

      <section class="card" style="margin-top: 0.8rem;">
        <h2 class="title" style="font-size: 1.05rem;">Recent History</h2>
        <table id="historyTable">
          <thead>
            <tr>
              <th>Finished At</th>
              <th>Job</th>
              <th>App</th>
              <th>Type</th>
              <th>Status</th>
              <th>Duration</th>
            </tr>
          </thead>
          <tbody></tbody>
        </table>
      </section>
    </main>

    <script>
      const API = '/service/gpuq-proxy.php';

      function escapeHtml(value) {
        const div = document.createElement('div');
        div.textContent = String(value ?? '');
        return div.innerHTML;
      }

      function fmtEta(seconds) {
        if (seconds == null || Number.isNaN(Number(seconds))) return '-';
        const total = Math.max(0, Math.round(Number(seconds)));
        const m = Math.floor(total / 60);
        const s = String(total % 60).padStart(2, '0');
        return `${m}:${s}`;
      }

      async function postAction(action, payload) {
        const res = await fetch(`${API}?action=${encodeURIComponent(action)}`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload || {}),
        });
        return res.json();
      }

      async function loadQueue() {
        const res = await fetch(`${API}?action=admin_queue`, { cache: 'no-store' });
        const json = await res.json();
        renderQueue(json);
      }

      async function loadHistory() {
        const res = await fetch(`${API}?action=admin_history&limit=20`, { cache: 'no-store' });
        const json = await res.json();
        renderHistory(json);
      }

      function renderQueue(json) {
        const statusLine = document.getElementById('statusLine');
        const tbody = document.querySelector('#queueTable tbody');
        const jobs = Array.isArray(json?.jobs) ? json.jobs : [];

        document.getElementById('queuedCount').textContent = String(json?.stats?.queued ?? 0);
        document.getElementById('runningCount').textContent = String(json?.stats?.running ?? 0);
        document.getElementById('completedCount').textContent = String(json?.stats?.completed_24h ?? 0);
        document.getElementById('failedCount').textContent = String(json?.stats?.failed_24h ?? 0);

        statusLine.textContent = json?.message || `Queue refreshed at ${new Date().toLocaleTimeString()}`;

        tbody.innerHTML = '';
        if (!jobs.length) {
          tbody.innerHTML = '<tr><td colspan="8" class="muted">No jobs in queue.</td></tr>';
          return;
        }

        for (const job of jobs) {
          const tr = document.createElement('tr');
          tr.innerHTML = `
            <td class="mono">${escapeHtml(job.job_id)}</td>
            <td>${escapeHtml(job.app_name)}</td>
            <td>${escapeHtml(job.job_type)}</td>
            <td><span class="pill ${escapeHtml(job.priority)}">${escapeHtml(job.priority)}</span></td>
            <td>${escapeHtml(job.status)}</td>
            <td>${escapeHtml(job.queue_position ?? '-')}</td>
            <td>${escapeHtml(fmtEta(job.eta_seconds))}</td>
            <td>
              <div class="actions">
                <button data-action="cancel" data-job="${escapeHtml(job.job_id)}">Cancel</button>
                <button data-action="kill" data-job="${escapeHtml(job.job_id)}">Kill</button>
                <select data-action="priority" data-job="${escapeHtml(job.job_id)}">
                  <option value="">Prio...</option>
                  <option value="high">high</option>
                  <option value="normal">normal</option>
                  <option value="low">low</option>
                </select>
              </div>
            </td>
          `;
          tbody.appendChild(tr);
        }
      }

      function renderHistory(json) {
        const tbody = document.querySelector('#historyTable tbody');
        const jobs = Array.isArray(json?.jobs) ? json.jobs : [];
        tbody.innerHTML = '';
        if (!jobs.length) {
          tbody.innerHTML = '<tr><td colspan="6" class="muted">No history entries yet.</td></tr>';
          return;
        }
        for (const job of jobs) {
          const tr = document.createElement('tr');
          tr.innerHTML = `
            <td>${escapeHtml(job.finished_at || '-')}</td>
            <td class="mono">${escapeHtml(job.job_id)}</td>
            <td>${escapeHtml(job.app_name)}</td>
            <td>${escapeHtml(job.job_type)}</td>
            <td>${escapeHtml(job.status)}</td>
            <td>${escapeHtml(fmtEta(job.runtime_seconds))}</td>
          `;
          tbody.appendChild(tr);
        }
      }

      document.addEventListener('click', async (e) => {
        const btn = e.target.closest('button[data-action]');
        if (!btn) return;
        const action = btn.dataset.action;
        const jobId = btn.dataset.job;
        if (!jobId) return;

        if (action === 'kill' && !confirm(`Hard kill for ${jobId}?`)) return;

        try {
          if (action === 'cancel') await postAction('cancel', { job_id: jobId });
          if (action === 'kill') await postAction('kill', { job_id: jobId });
          await loadQueue();
        } catch (err) {
          console.error(err);
          alert('Action failed.');
        }
      });

      document.addEventListener('change', async (e) => {
        const select = e.target.closest('select[data-action="priority"]');
        if (!select) return;
        const jobId = select.dataset.job;
        const priority = select.value;
        if (!jobId || !priority) return;
        try {
          await postAction('priority', { job_id: jobId, priority });
          await loadQueue();
        } catch (err) {
          console.error(err);
          alert('Priority change failed.');
        }
      });

      async function initialLoad() {
        await Promise.all([loadQueue(), loadHistory()]);
      }

      function startQueueStream() {
        const source = new EventSource(`${API}?action=stream_queue`);
        source.onmessage = () => loadQueue().catch(console.error);
        source.onerror = () => {
          source.close();
          setTimeout(startQueueStream, 3000);
        };
      }

      initialLoad().catch(console.error);
      startQueueStream();
      setInterval(() => loadHistory().catch(console.error), 10000);
    </script>
  </body>
</html>