Files
anonymous_github/public/partials/admin/queues.htm
T

225 lines
13 KiB
HTML

<div class="container paper-page admin-page">
<div class="paper-crumbs">Admin &nbsp;/&nbsp; <span class="here">Queues</span></div>
<div class="q-header">
<h1 class="paper-page-title">Queues</h1>
<div class="q-header-actions">
<div class="q-range-btns">
<button class="btn btn-sm" :class="{active: range == &#x27;1h&#x27;}" @click="setRange(&#x27;1h&#x27;)">1h</button>
<button class="btn btn-sm" :class="{active: range == &#x27;6h&#x27;}" @click="setRange(&#x27;6h&#x27;)">6h</button>
<button class="btn btn-sm" :class="{active: range == &#x27;24h&#x27;}" @click="setRange(&#x27;24h&#x27;)">24h</button>
<button class="btn btn-sm" :class="{active: range == &#x27;7d&#x27;}" @click="setRange(&#x27;7d&#x27;)">7d</button>
</div>
<button class="btn btn-sm" @click="pauseAll()">Pause all</button>
<button class="btn btn-sm btn-dark" @click="drainSelected()">Drain {{ selectedQueue }}</button>
</div>
</div>
<nav class="admin-nav">
<a href="/admin/"><i class="fas fa-tachometer-alt"></i> Overview</a>
<a href="/admin/repositories"><i class="fas fa-code-branch"></i> Repositories</a>
<a href="/admin/users"><i class="fas fa-users"></i> Users</a>
<a href="/admin/conferences"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
<a href="/admin/queues" class="active"><i class="fas fa-tasks"></i> Queues</a>
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
</nav>
<!-- Queue overview cards -->
<div class="q-cards">
<div class="q-card" v-for="(q, index) in queueList" :class="{selected: selectedQueue == q?.key, paused: q?.paused}" @click="selectQueue(q.key)">
<div class="q-card-head">
<span class="q-dot" :class="{&#x27;q-dot-red&#x27;: q?.paused || q?.counts?.failed &gt; 0}"></span>
<span class="q-card-name" v-text="q?.label"></span>
</div>
<div class="q-card-count" v-text="(q?.counts?.waiting || 0) + (q?.counts?.active || 0) + (q?.counts?.delayed || 0)"></div>
<div class="q-card-sub">
<span>waiting &middot; {{ q?.counts?.active || 0 }} active</span>
<div class="q-card-bar" v-if="q?.counts?.active">
<div class="q-card-bar-fill" :style="{width: (q?.counts?.active / ((q?.counts?.waiting || 0) + (q?.counts?.active || 0) + (q?.counts?.delayed || 0) || 1) * 100) + &#x27;%&#x27;}"></div>
</div>
</div>
</div>
</div>
<!-- Detail: throughput chart + stats panel -->
<div class="q-detail" v-if="selectedStats">
<div class="q-throughput">
<div class="q-section-label">{{ selectedQueue }}&middot;throughput <span class="q-section-right"><span class="q-legend-completed">&#9679;</span> completed <span class="q-legend-failed">&#9679;</span> failed <span class="q-legend-exec">- -</span> avg time &middot; {{ fmt?.uppercase(range) }}</span></div>
<div class="q-chart-wrap">
<canvas id="q-throughput-chart" height="180"></canvas>
<div id="q-chart-tooltip" class="q-chart-tooltip" style="display:none;"></div>
<div id="q-chart-crosshair" class="q-chart-crosshair" style="display:none;"></div>
</div>
</div>
<div class="q-stats-panel">
<div class="q-section-label">{{ selectedQueue }}&middot;stats</div>
<div class="q-stats-grid">
<div class="q-stat">
<div class="q-stat-label">WAITING</div>
<div class="q-stat-value" v-text="selectedStats?.counts?.waiting || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">ACTIVE</div>
<div class="q-stat-value" v-text="selectedStats?.counts?.active || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">COMPLETED (24H)</div>
<div class="q-stat-value" v-text="fmt?.number(selectedStats?.completed24h)"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">FAILED (24H)</div>
<div class="q-stat-value" v-text="selectedStats?.failed24h || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">DELAYED</div>
<div class="q-stat-value" v-text="selectedStats?.counts?.delayed || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">WORKERS</div>
<div class="q-stat-value" v-text="selectedStats?.workers || 0"></div>
</div>
</div>
<div class="q-stats-actions">
<button class="btn btn-sm" @click="togglePause()">{{ selectedStats?.paused ? 'Resume' : 'Pause' }}</button>
<button class="btn btn-sm" @click="retryFailed()" :disabled="!selectedStats?.counts?.failed">Retry failed</button>
<button class="btn btn-sm" @click="emptyQueue()">Empty</button>
</div>
</div>
</div>
<div class="q-toast-error" v-if="actionError" @click="actionError = null">
<i class="fas fa-exclamation-circle"></i> {{ actionError }}
</div>
<!-- Jobs table -->
<div class="q-jobs">
<div class="q-jobs-header">
<div class="q-section-label">ALL JOBS &middot; {{ fmt?.uppercase(selectedQueue) }}</div>
<div class="q-state-filters">
<label class="q-state-toggle" v-for="(s, index) in allStates">
<input type="checkbox" v-field="{ state: viewState, set: value =&gt; { stateFilter[s] = value }, value: stateFilter[s], form: null, options: {} }">
<span :class="&quot;q-state-chip q-state-&quot; + (s)">{{ s }}</span>
</label>
</div>
</div>
<div class="q-search-row">
<input type="search" class="form-control" placeholder="Search by job/repo id…" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { query.search = value }, value: query?.search, form: null, options: {} }">
<label class="q-auto-refresh">
<input type="checkbox" v-field="{ state: viewState, set: value =&gt; { query.autoRefresh = value }, value: query?.autoRefresh, form: null, options: {} }">
Auto-refresh
</label>
<button class="btn btn-sm" type="button" title="Refresh now" @click="refreshNow()"><i class="fas fa-sync"></i></button>
</div>
<table class="q-table" v-if="filteredJobs().length &gt; 0">
<thead>
<tr>
<th>STATE</th>
<th>JOB ID</th>
<th>PAYLOAD</th>
<th>ATTEMPTS</th>
<th>DURATION</th>
<th>PROGRESS</th>
<th></th>
</tr>
</thead>
<tbody v-for="(job, index) in filteredJobs()">
<tr style="cursor:pointer;" :class="{&#x27;q-row-failed&#x27;: job?._state == &#x27;failed&#x27;, &#x27;q-row-expanded&#x27;: expanded[job?.id]}" @click="toggleJob(job)">
<td class="q-cell-state">
<span v-text="job?._state" :class="&quot;q-state-badge q-state-&quot; + (job?._state)"></span>
<span class="q-delay-hint" v-if="job?._state == &#x27;delayed&#x27; &amp;&amp; job?.delayUntil" v-text="delayCountdown(job?.delayUntil)"></span>
</td>
<td class="q-cell-id">
<i class="fas fa-chevron-right q-chevron" :class="{&#x27;q-chevron-open&#x27;: expanded[job?.id]}"></i>
<a target="_blank" v-text="&#x27;job:&#x27; + fmt.limitTo(job?.id, 6)" @click="$event.stopPropagation()" :href="safeUrl(&quot;/r/&quot; + (job?.id))"></a>
</td>
<td class="q-cell-payload">
<span v-text="job?.name || &#x27;anonymize&#x27;"></span>
<span class="q-payload-detail" v-if="job?.data?.repoId &amp;&amp; job?.data?.repoId !== job?.name"> &middot; {{ job?.data?.repoId }}</span>
</td>
<td class="q-cell-num" v-text="job?.attemptsMade || 1"></td>
<td class="q-cell-num" v-text="jobDuration(job)"></td>
<td class="q-cell-progress">
<div class="q-progress-wrap" v-if="jobProgressPct(job) !== null">
<div class="q-progress-bar" :style="{&#x27;--pct&#x27;: jobProgressPct(job) + &#x27;%&#x27;}"></div>
<span class="q-progress-label" v-text="jobProgressPct(job) + &#x27;%&#x27;"></span>
</div>
</td>
<td class="q-cell-actions">
<button class="btn btn-sm" title="Retry" v-if="job?._state == &#x27;failed&#x27;" @click="retryJob(job); $event.stopPropagation()"><i class="fas fa-sync"></i></button>
<button class="btn btn-sm" title="Remove" @click="removeJob(job); $event.stopPropagation()"><i class="fas fa-trash-alt"></i></button>
</td>
</tr>
<tr class="q-detail-row" v-if="expanded[job?.id]">
<td colspan="7">
<div class="q-job-detail">
<div class="q-job-detail-grid">
<div class="q-job-detail-item">
<span class="q-job-detail-label">JOB ID</span>
<span class="q-job-detail-value"><a target="_blank" v-text="job?.id" :href="safeUrl(&quot;/r/&quot; + (job?.id))"></a></span>
</div>
<div class="q-job-detail-item">
<span class="q-job-detail-label">STATE</span>
<span class="q-job-detail-value"><span v-text="job?._state" :class="&quot;q-state-badge q-state-&quot; + (job?._state)"></span></span>
</div>
<div class="q-job-detail-item" v-if="job?.data?.repoId">
<span class="q-job-detail-label">REPO ID</span>
<span class="q-job-detail-value" v-text="job?.data?.repoId"></span>
</div>
<div class="q-job-detail-item" v-if="job?.timestamp">
<span class="q-job-detail-label">CREATED</span>
<span class="q-job-detail-value" v-text="humanTime(job?.timestamp)"></span>
</div>
<div class="q-job-detail-item" v-if="job?._state == &#x27;delayed&#x27; &amp;&amp; job?.delayUntil">
<span class="q-job-detail-label">RETRY AT</span>
<span class="q-job-detail-value">{{ humanTime(job?.delayUntil) }} ({{ delayCountdown(job?.delayUntil) }})</span>
</div>
<div class="q-job-detail-item" v-if="job?.processedOn">
<span class="q-job-detail-label">PROCESSED</span>
<span class="q-job-detail-value" v-text="humanTime(job?.processedOn)"></span>
</div>
<div class="q-job-detail-item" v-if="job?.finishedOn">
<span class="q-job-detail-label">FINISHED</span>
<span class="q-job-detail-value" v-text="humanTime(job?.finishedOn)"></span>
</div>
<div class="q-job-detail-item" v-if="job?.attemptsMade">
<span class="q-job-detail-label">ATTEMPTS</span>
<span class="q-job-detail-value" v-text="job?.attemptsMade"></span>
</div>
<div class="q-job-detail-item" v-if="job?.progress &amp;&amp; job?.progress?.status">
<span class="q-job-detail-label">STATUS</span>
<span class="q-job-detail-value" v-text="job?.progress?.status"></span>
</div>
<div class="q-job-detail-item" v-if="jobProgressPct(job) !== null">
<span class="q-job-detail-label">PROGRESS</span>
<span class="q-job-detail-value" v-text="jobProgressPct(job) + &#x27;%&#x27;"></span>
</div>
</div>
<div class="q-job-detail-error" v-if="job?.failedReason">
<span class="q-job-detail-label">ERROR</span>
<div class="q-error-reason" v-text="job?.failedReason"></div>
</div>
<div v-if="job?.stacktrace?.length">
<span class="q-job-detail-label">STACKTRACE</span>
<pre class="q-error-stack" v-for="(stack, index) in job?.stacktrace" :key="index"><code v-text="stack"></code></pre>
</div>
<div class="q-job-detail-actions">
<button class="btn btn-sm" v-if="job?._state == &#x27;failed&#x27;" @click="retryJob(job)"><i class="fas fa-sync"></i> Retry</button>
<button class="btn btn-sm" @click="removeJob(job)"><i class="fas fa-trash-alt"></i> Remove</button>
<a class="btn btn-sm" target="_blank" :href="safeUrl(&quot;/r/&quot; + (job?.id))"><i class="fas fa-external-link-alt"></i> View repo</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<div class="paper-table-empty" style="border:1px solid var(--border-color);border-radius:10px;background:var(--paper-card);" v-if="filteredJobs().length == 0">
<i class="fas fa-check-circle"></i>
<span v-if="!query?.search">No jobs in the {{ selectedQueue }} queue.</span>
<span v-if="query?.search">No jobs match the current filters.</span>
</div>
</div>
</div>