mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-05-15 14:38:03 +02:00
improve styling
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
<h1 class="paper-page-title">Conferences</h1>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/"><i class="fas fa-code-branch"></i> Repositories</a>
|
||||
<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" class="active"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
|
||||
<a href="/admin/queues"><i class="fas fa-tasks"></i> Queues</a>
|
||||
@@ -12,11 +13,11 @@
|
||||
|
||||
<div class="admin-summary">
|
||||
<span class="summary-total">{{total >= 0 ? (total | number) : '…'}}</span>
|
||||
<span class="summary-pill ok" ng-class="{active: query.status == 'ready'}" ng-click="query.status = query.status == 'ready' ? '' : 'ready'; query.page = 1">Ready <span class="count">{{statusCountFor('ready') | number}}</span></span>
|
||||
<span class="summary-pill warn" ng-class="{active: query.status == 'preparing'}" ng-click="query.status = query.status == 'preparing' ? '' : 'preparing'; query.page = 1">Preparing <span class="count">{{statusCountFor('preparing') | number}}</span></span>
|
||||
<span class="summary-pill error" ng-class="{active: query.status == 'error'}" ng-click="query.status = query.status == 'error' ? '' : 'error'; query.page = 1">Errored <span class="count">{{statusCountFor('error') | number}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: query.status == 'expired'}" ng-click="query.status = query.status == 'expired' ? '' : 'expired'; query.page = 1">Expired <span class="count">{{statusCountFor('expired') | number}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: query.status == 'removed'}" ng-click="query.status = query.status == 'removed' ? '' : 'removed'; query.page = 1">Removed <span class="count">{{statusCountFor('removed') | number}}</span></span>
|
||||
<span class="summary-pill ok" ng-class="{active: query.ready}" ng-click="query.ready = !query.ready; query.page = 1" title="Toggle ready filter">Ready <span class="count">{{statusCountFor('ready') | number}}</span></span>
|
||||
<span class="summary-pill warn" ng-class="{active: query.preparing}" ng-click="query.preparing = !query.preparing; query.page = 1" title="Toggle preparing filter">Preparing <span class="count">{{statusCountFor('preparing') | number}}</span></span>
|
||||
<span class="summary-pill error" ng-class="{active: query.error}" ng-click="query.error = !query.error; query.page = 1" title="Toggle errored filter">Errored <span class="count">{{statusCountFor('error') | number}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: query.expired}" ng-click="query.expired = !query.expired; query.page = 1" title="Toggle expired filter">Expired <span class="count">{{statusCountFor('expired') | number}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: query.removed}" ng-click="query.removed = !query.removed; query.page = 1" title="Toggle removed filter">Removed <span class="count">{{statusCountFor('removed') | number}}</span></span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" ng-if="fetchError" style="margin: 8px 0;">
|
||||
@@ -60,7 +61,7 @@
|
||||
<div
|
||||
class="paper-table-row"
|
||||
role="row"
|
||||
ng-repeat="conference in conferences | filter:conferenceFiler | orderBy:orderBy as filteredConferences"
|
||||
ng-repeat="conference in conferences as filteredConferences"
|
||||
>
|
||||
<div class="cell-anon" role="cell">
|
||||
<span class="type-badge type-repo">Conf</span>
|
||||
@@ -110,7 +111,7 @@
|
||||
</div>
|
||||
<span class="admin-filter-inline">
|
||||
<label>Per page</label>
|
||||
<select class="form-control form-control-sm" ng-model="query.limit"><option value="10">10</option><option value="25">25</option><option value="50">50</option><option value="100">100</option></select>
|
||||
<select class="form-control form-control-sm" ng-model="query.limit"><option value="10">10</option><option value="25">25</option><option value="50">50</option><option value="100">100</option><option value="250">250</option></select>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
</header>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/"><i class="fas fa-code-branch"></i> Repositories</a>
|
||||
<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"><i class="fas fa-tasks"></i> Queues</a>
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
<div class="container paper-page admin-page overview-page">
|
||||
<div class="paper-crumbs">Admin · System Health</div>
|
||||
|
||||
<div class="ov-header">
|
||||
<h1 class="paper-page-title">Overview</h1>
|
||||
<div class="ov-header-actions">
|
||||
<div class="ov-range-btns">
|
||||
<button class="btn btn-sm" ng-class="{active: range === '1h'}" ng-click="setRange('1h')">1h</button>
|
||||
<button class="btn btn-sm" ng-class="{active: range === '6h'}" ng-click="setRange('6h')">6h</button>
|
||||
<button class="btn btn-sm" ng-class="{active: range === '24h'}" ng-click="setRange('24h')">24h</button>
|
||||
<button class="btn btn-sm" ng-class="{active: range === '7d'}" ng-click="setRange('7d')">7d</button>
|
||||
<button class="btn btn-sm" ng-class="{active: range === '30d'}" ng-click="setRange('30d')">30d</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/" class="active"><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"><i class="fas fa-tasks"></i> Queues</a>
|
||||
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
|
||||
</nav>
|
||||
|
||||
<div ng-if="loading" class="admin-empty">Loading overview…</div>
|
||||
<div ng-if="error" class="alert alert-danger" style="margin:12px 0"><i class="fas fa-exclamation-triangle"></i> {{error}}</div>
|
||||
|
||||
<div ng-if="data">
|
||||
|
||||
<!-- ── Top KPI row ──────────────────────────────────────────── -->
|
||||
<section class="ov-kpi-row">
|
||||
<div class="ov-kpi-card">
|
||||
<div class="ov-kpi-label">Repositories <span class="ov-dot ov-dot-ok"></span></div>
|
||||
<div class="ov-kpi-value">{{humanNum(data.repos.total)}}</div>
|
||||
<div class="ov-kpi-sub">+{{data.repos.newRepos24h}} · last 24h</div>
|
||||
</div>
|
||||
<div class="ov-kpi-card">
|
||||
<div class="ov-kpi-label">CPU</div>
|
||||
<div class="ov-kpi-value" ng-class="{'ov-val-warn': data.system.cpuPercent > 80}">{{data.system.cpuPercent}}%</div>
|
||||
<div class="ov-kpi-sub">{{data.system.cpuCount}} cores · load {{data.system.loadAvg[0].toFixed(1)}}</div>
|
||||
</div>
|
||||
<div class="ov-kpi-card">
|
||||
<div class="ov-kpi-label">Memory</div>
|
||||
<div class="ov-kpi-value" ng-class="{'ov-val-warn': data.system.memPercent > 85}">{{data.system.memPercent}}%</div>
|
||||
<div class="ov-kpi-sub">{{humanBytes(data.system.memUsed)}} / {{humanBytes(data.system.memTotal)}}</div>
|
||||
</div>
|
||||
<div class="ov-kpi-card">
|
||||
<div class="ov-kpi-label">Disk</div>
|
||||
<div class="ov-kpi-value" ng-class="{'ov-val-warn': data.system.diskPercent > 85}">{{data.system.diskPercent}}%</div>
|
||||
<div class="ov-kpi-sub">{{humanBytes(data.system.diskUsed)}} / {{humanBytes(data.system.diskTotal)}} · {{data.system.diskMount}}</div>
|
||||
</div>
|
||||
<div class="ov-kpi-card">
|
||||
<div class="ov-kpi-label">Uptime</div>
|
||||
<div class="ov-kpi-value">{{humanDuration(data.system.uptime)}}</div>
|
||||
<div class="ov-kpi-sub">{{data.system.nodeVersion}} · {{data.system.platform}}</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Two wide charts ──────────────────────────────────────── -->
|
||||
<section class="ov-chart-row">
|
||||
<div class="ov-chart-card">
|
||||
<div class="ov-chart-head">
|
||||
<span class="ov-chart-title">Page views · 30d</span>
|
||||
<span class="ov-chart-legend"><span class="ov-dot-legend ov-dot-accent"></span>views</span>
|
||||
</div>
|
||||
<div class="ov-spark-bars" ng-if="data.history.length">
|
||||
<div class="ov-spark-col" ng-repeat="d in data.history track by $index"
|
||||
title="{{historyLabel(d)}}: {{d.nbPageViews | number}} views">
|
||||
<span class="ov-spark-fill" ng-style="{height: historyBarH(d, 'nbPageViews') + 'px'}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-spark-x" ng-if="data.history.length">
|
||||
<span>{{historyLabel(data.history[0])}}</span>
|
||||
<span>{{historyLabel(data.history[Math.floor(data.history.length/2)])}}</span>
|
||||
<span>{{historyLabel(data.history[data.history.length - 1])}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-chart-card">
|
||||
<div class="ov-chart-head">
|
||||
<span class="ov-chart-title">New repos · 30d</span>
|
||||
<span class="ov-chart-legend"><span class="ov-dot-legend ov-dot-ok-fill"></span>repos</span>
|
||||
</div>
|
||||
<div class="ov-spark-bars" ng-if="data.history.length">
|
||||
<div class="ov-spark-col" ng-repeat="d in data.history track by $index"
|
||||
title="{{historyLabel(d)}}: {{d.nbRepositories | number}} repos">
|
||||
<span class="ov-spark-fill ov-spark-fill-alt" ng-style="{height: historyBarH(d, 'nbRepositories') + 'px'}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-spark-x" ng-if="data.history.length">
|
||||
<span>{{historyLabel(data.history[0])}}</span>
|
||||
<span>{{historyLabel(data.history[Math.floor(data.history.length/2)])}}</span>
|
||||
<span>{{historyLabel(data.history[data.history.length - 1])}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Three-panel row: Status / Errors / Queues ────────────── -->
|
||||
<section class="ov-triple-row">
|
||||
|
||||
<!-- Repo status breakdown -->
|
||||
<div class="ov-panel-card">
|
||||
<div class="ov-panel-head">
|
||||
<span class="ov-panel-title">Repo status</span>
|
||||
<span class="ov-panel-meta">{{data.repos.total | number}} total</span>
|
||||
</div>
|
||||
<div class="ov-stacked-bar">
|
||||
<span class="ov-bar-seg ov-bar-ready" ng-style="{width: barPct('ready') + '%'}" title="Ready"></span>
|
||||
<span class="ov-bar-seg ov-bar-preparing" ng-style="{width: barPct('preparing') + '%'}" title="Preparing"></span>
|
||||
<span class="ov-bar-seg ov-bar-error" ng-style="{width: barPct('error') + '%'}" title="Error"></span>
|
||||
<span class="ov-bar-seg ov-bar-expired" ng-style="{width: barPct('expired') + '%'}" title="Expired"></span>
|
||||
<span class="ov-bar-seg ov-bar-removed" ng-style="{width: barPct('removed') + '%'}" title="Removed"></span>
|
||||
</div>
|
||||
<div class="ov-bar-legend">
|
||||
<a href="/admin/repositories" class="ov-legend-item"><span class="ov-swatch ov-bar-ready"></span> ready <span class="ov-legend-n">{{statusCount('ready') | number}}</span></a>
|
||||
<a href="/admin/repositories" class="ov-legend-item"><span class="ov-swatch ov-bar-preparing"></span> preparing <span class="ov-legend-n">{{statusCount('preparing') + statusCount('download') | number}}</span></a>
|
||||
<a href="/admin/repositories" class="ov-legend-item"><span class="ov-swatch ov-bar-error"></span> error <span class="ov-legend-n">{{statusCount('error') | number}}</span></a>
|
||||
<a href="/admin/repositories" class="ov-legend-item"><span class="ov-swatch ov-bar-expired"></span> expired <span class="ov-legend-n">{{statusCount('expired') + statusCount('expiring') | number}}</span></a>
|
||||
<a href="/admin/repositories" class="ov-legend-item"><span class="ov-swatch ov-bar-removed"></span> removed <span class="ov-legend-n">{{statusCount('removed') + statusCount('removing') | number}}</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error breakdown -->
|
||||
<div class="ov-panel-card">
|
||||
<div class="ov-panel-head">
|
||||
<span class="ov-panel-title">Errors · 24h</span>
|
||||
<span class="ov-panel-meta">{{data.errors.last24h}} total</span>
|
||||
</div>
|
||||
<div class="ov-error-bars">
|
||||
<div class="ov-ebar-row">
|
||||
<span class="ov-ebar-label">5xx</span>
|
||||
<span class="ov-ebar-track"><span class="ov-ebar-fill ov-ebar-error" ng-style="{width: errPct('error') + '%'}"></span></span>
|
||||
<span class="ov-ebar-n">{{data.errors.severity.error}}</span>
|
||||
</div>
|
||||
<div class="ov-ebar-row">
|
||||
<span class="ov-ebar-label">4xx</span>
|
||||
<span class="ov-ebar-track"><span class="ov-ebar-fill ov-ebar-warn" ng-style="{width: errPct('warn') + '%'}"></span></span>
|
||||
<span class="ov-ebar-n">{{data.errors.severity.warn}}</span>
|
||||
</div>
|
||||
<div class="ov-ebar-row">
|
||||
<span class="ov-ebar-label">Info</span>
|
||||
<span class="ov-ebar-track"><span class="ov-ebar-fill ov-ebar-info" ng-style="{width: errPct('info') + '%'}"></span></span>
|
||||
<span class="ov-ebar-n">{{data.errors.severity.info}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-panel-foot" ng-if="data.repos.recentErrors24h">
|
||||
<a href="/admin/errors">{{data.repos.recentErrors24h}} repos in error state →</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top routes / queues -->
|
||||
<div class="ov-panel-card">
|
||||
<div class="ov-panel-head">
|
||||
<span class="ov-panel-title">Queues</span>
|
||||
<span class="ov-panel-meta">by state</span>
|
||||
</div>
|
||||
<div class="ov-routes-table">
|
||||
<div class="ov-route-row ov-route-head">
|
||||
<span class="ov-route-name">Queue</span>
|
||||
<span class="ov-route-n">Active</span>
|
||||
<span class="ov-route-n">Wait</span>
|
||||
<span class="ov-route-n ov-route-lat">Failed</span>
|
||||
</div>
|
||||
<a class="ov-route-row" href="/admin/queues">
|
||||
<span class="ov-route-name">download</span>
|
||||
<span class="ov-route-n">{{data.queues.download.active}}</span>
|
||||
<span class="ov-route-n">{{data.queues.download.waiting}}</span>
|
||||
<span class="ov-route-n ov-route-lat" ng-class="{'ov-n-bad': data.queues.download.failed > 0}">{{data.queues.download.failed}}</span>
|
||||
</a>
|
||||
<a class="ov-route-row" href="/admin/queues">
|
||||
<span class="ov-route-name">remove</span>
|
||||
<span class="ov-route-n">{{data.queues.remove.active}}</span>
|
||||
<span class="ov-route-n">{{data.queues.remove.waiting}}</span>
|
||||
<span class="ov-route-n ov-route-lat" ng-class="{'ov-n-bad': data.queues.remove.failed > 0}">{{data.queues.remove.failed}}</span>
|
||||
</a>
|
||||
<a class="ov-route-row" href="/admin/queues">
|
||||
<span class="ov-route-name">cache</span>
|
||||
<span class="ov-route-n">{{data.queues.cache.active}}</span>
|
||||
<span class="ov-route-n">{{data.queues.cache.waiting}}</span>
|
||||
<span class="ov-route-n ov-route-lat" ng-class="{'ov-n-bad': data.queues.cache.failed > 0}">{{data.queues.cache.failed}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ── Services bar ─────────────────────────────────────────── -->
|
||||
<section class="ov-services-card">
|
||||
<div class="ov-services-head">
|
||||
<span class="ov-panel-title">Services</span>
|
||||
<span class="ov-panel-meta">{{data.users.total | number}} users · {{data.conferences.total}} conferences</span>
|
||||
</div>
|
||||
<div class="ov-services-grid">
|
||||
<div class="ov-svc">
|
||||
<span class="ov-svc-dot ov-dot-ok"></span>
|
||||
<div class="ov-svc-info">
|
||||
<span class="ov-svc-name">web</span>
|
||||
<span class="ov-svc-meta">{{data.system.nodeVersion}}</span>
|
||||
</div>
|
||||
<span class="ov-svc-detail">uptime {{humanDuration(data.system.uptime)}}</span>
|
||||
</div>
|
||||
<div class="ov-svc">
|
||||
<span class="ov-svc-dot" ng-class="queueTotal(data.queues.download) > 0 ? 'ov-dot-ok' : 'ov-dot-idle'"></span>
|
||||
<div class="ov-svc-info">
|
||||
<span class="ov-svc-name">download</span>
|
||||
<span class="ov-svc-meta" ng-if="queueTotal(data.queues.download)">{{queueTotal(data.queues.download)}} jobs</span>
|
||||
<span class="ov-svc-meta" ng-if="!queueTotal(data.queues.download)">idle</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-svc">
|
||||
<span class="ov-svc-dot" ng-class="queueTotal(data.queues.cache) > 0 ? 'ov-dot-ok' : 'ov-dot-idle'"></span>
|
||||
<div class="ov-svc-info">
|
||||
<span class="ov-svc-name">cache</span>
|
||||
<span class="ov-svc-meta" ng-if="queueTotal(data.queues.cache)">{{queueTotal(data.queues.cache)}} jobs</span>
|
||||
<span class="ov-svc-meta" ng-if="!queueTotal(data.queues.cache)">idle</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-svc">
|
||||
<span class="ov-svc-dot" ng-class="queueTotal(data.queues.remove) > 0 ? 'ov-dot-ok' : 'ov-dot-idle'"></span>
|
||||
<div class="ov-svc-info">
|
||||
<span class="ov-svc-name">remove</span>
|
||||
<span class="ov-svc-meta" ng-if="queueTotal(data.queues.remove)">{{queueTotal(data.queues.remove)}} jobs</span>
|
||||
<span class="ov-svc-meta" ng-if="!queueTotal(data.queues.remove)">idle</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ov-svc">
|
||||
<span class="ov-svc-dot" ng-class="data.repos.recentErrors24h > 10 ? 'ov-dot-warn' : 'ov-dot-ok'"></span>
|
||||
<div class="ov-svc-info">
|
||||
<span class="ov-svc-name">errors</span>
|
||||
<span class="ov-svc-meta">{{data.errors.last24h}} / 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,102 +1,156 @@
|
||||
<div class="container paper-page admin-page">
|
||||
<div class="paper-crumbs">Admin / <span class="here">Queues</span></div>
|
||||
<h1 class="paper-page-title">Queues</h1>
|
||||
|
||||
<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" ng-class="{active: range == '1h'}" ng-click="setRange('1h')">1h</button>
|
||||
<button class="btn btn-sm" ng-class="{active: range == '6h'}" ng-click="setRange('6h')">6h</button>
|
||||
<button class="btn btn-sm" ng-class="{active: range == '24h'}" ng-click="setRange('24h')">24h</button>
|
||||
<button class="btn btn-sm" ng-class="{active: range == '7d'}" ng-click="setRange('7d')">7d</button>
|
||||
</div>
|
||||
<button class="btn btn-sm" ng-click="pauseAll()">Pause all</button>
|
||||
<button class="btn btn-sm btn-dark" ng-click="drainSelected()">Drain {{selectedQueue}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/"><i class="fas fa-code-branch"></i> Repositories</a>
|
||||
<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>
|
||||
|
||||
<div class="admin-summary">
|
||||
<span class="summary-pill warn" ng-class="{active: query.state == 'active'}" ng-click="query.state = query.state == 'active' ? '' : 'active'">Active <span class="count">{{(counts.download.active || 0) + (counts.remove.active || 0) + (counts.cache.active || 0)}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: query.state == 'waiting'}" ng-click="query.state = query.state == 'waiting' ? '' : 'waiting'">Waiting <span class="count">{{(counts.download.waiting || 0) + (counts.remove.waiting || 0) + (counts.cache.waiting || 0)}}</span></span>
|
||||
<span class="summary-pill error" ng-class="{active: query.state == 'failed'}" ng-click="query.state = query.state == 'failed' ? '' : 'failed'">Failed <span class="count">{{(counts.download.failed || 0) + (counts.remove.failed || 0) + (counts.cache.failed || 0)}}</span></span>
|
||||
<span class="summary-pill ok" ng-class="{active: query.state == 'completed'}" ng-click="query.state = query.state == 'completed' ? '' : 'completed'">Completed <span class="count">{{(counts.download.completed || 0) + (counts.remove.completed || 0) + (counts.cache.completed || 0)}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: query.state == 'delayed'}" ng-click="query.state = query.state == 'delayed' ? '' : 'delayed'">Delayed <span class="count">{{(counts.download.delayed || 0) + (counts.remove.delayed || 0) + (counts.cache.delayed || 0)}}</span></span>
|
||||
<!-- Queue overview cards -->
|
||||
<div class="q-cards">
|
||||
<div class="q-card" ng-repeat="q in queueList" ng-class="{selected: selectedQueue == q.key, paused: q.paused}" ng-click="selectQueue(q.key)">
|
||||
<div class="q-card-head">
|
||||
<span class="q-dot" ng-class="{'q-dot-red': q.paused || q.counts.failed > 0}"></span>
|
||||
<span class="q-card-name" ng-bind="q.label"></span>
|
||||
</div>
|
||||
<div class="q-card-count" ng-bind="(q.counts.waiting || 0) + (q.counts.active || 0) + (q.counts.delayed || 0)"></div>
|
||||
<div class="q-card-sub">
|
||||
<span>waiting · {{q.counts.active || 0}} active</span>
|
||||
<div class="q-card-bar" ng-if="q.counts.active">
|
||||
<div class="q-card-bar-fill" ng-style="{width: (q.counts.active / ((q.counts.waiting || 0) + (q.counts.active || 0) + (q.counts.delayed || 0) || 1) * 100) + '%'}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="w-100 admin-filter-toolbar" aria-label="Queue filters">
|
||||
<div class="admin-filter-row">
|
||||
<div class="search-wrap">
|
||||
<input type="search" class="form-control" placeholder="Search by job/repo id…" ng-model="query.search" autocomplete="off" />
|
||||
<span class="admin-search-hint" ng-if="!query.search">/</span>
|
||||
<!-- Detail: throughput chart + stats panel -->
|
||||
<div class="q-detail" ng-if="selectedStats">
|
||||
<div class="q-throughput">
|
||||
<div class="q-section-label">{{selectedQueue}}·throughput <span class="q-section-right">COMPLETED / MIN · {{range | uppercase}}</span></div>
|
||||
<canvas id="q-throughput-chart" height="180"></canvas>
|
||||
</div>
|
||||
<div class="q-stats-panel">
|
||||
<div class="q-section-label">{{selectedQueue}}·stats</div>
|
||||
<div class="q-stats-grid">
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">WAITING</div>
|
||||
<div class="q-stat-value" ng-bind="selectedStats.counts.waiting || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">ACTIVE</div>
|
||||
<div class="q-stat-value" ng-bind="selectedStats.counts.active || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">COMPLETED (24H)</div>
|
||||
<div class="q-stat-value" ng-bind="selectedStats.completed24h | number"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">FAILED (24H)</div>
|
||||
<div class="q-stat-value" ng-bind="selectedStats.failed24h || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">DELAYED</div>
|
||||
<div class="q-stat-value" ng-bind="selectedStats.counts.delayed || 0"></div>
|
||||
</div>
|
||||
<div class="q-stat">
|
||||
<div class="q-stat-label">WORKERS</div>
|
||||
<div class="q-stat-value" ng-bind="selectedStats.workers || 0"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="admin-filter-inline">
|
||||
<label>State</label>
|
||||
<select class="form-control form-control-sm" ng-model="query.state">
|
||||
<option value="">Any</option>
|
||||
<option value="waiting">Waiting</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="failed">Failed</option>
|
||||
<option value="delayed">Delayed</option>
|
||||
</select>
|
||||
</span>
|
||||
<span class="admin-filter-spacer"></span>
|
||||
<label class="admin-filter-inline" style="cursor:pointer;">
|
||||
<div class="q-stats-actions">
|
||||
<button class="btn btn-sm" ng-click="togglePause()">{{selectedStats.paused ? 'Resume' : 'Pause'}}</button>
|
||||
<button class="btn btn-sm" ng-click="retryFailed()" ng-disabled="!selectedStats.counts.failed">Retry failed</button>
|
||||
<button class="btn btn-sm" ng-click="emptyQueue()">Empty</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jobs table -->
|
||||
<div class="q-jobs">
|
||||
<div class="q-jobs-header">
|
||||
<div class="q-section-label">{{query.state | uppercase}} JOBS · {{selectedQueue | uppercase}}</div>
|
||||
<div class="q-tabs">
|
||||
<button class="q-tab" ng-class="{active: query.state == 'active'}" ng-click="query.state = 'active'">Active</button>
|
||||
<button class="q-tab" ng-class="{active: query.state == 'waiting'}" ng-click="query.state = 'waiting'">Waiting</button>
|
||||
<button class="q-tab" ng-class="{active: query.state == 'completed'}" ng-click="query.state = 'completed'">Completed</button>
|
||||
<button class="q-tab" ng-class="{active: query.state == 'failed'}" ng-click="query.state = 'failed'">Failed</button>
|
||||
<button class="q-tab" ng-class="{active: query.state == 'delayed'}" ng-click="query.state = 'delayed'">Delayed</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="q-search-row">
|
||||
<input type="search" class="form-control" placeholder="Search by job/repo id…" ng-model="query.search" autocomplete="off" />
|
||||
<label class="q-auto-refresh">
|
||||
<input type="checkbox" ng-model="query.autoRefresh" />
|
||||
Auto-refresh
|
||||
</label>
|
||||
<button class="btn btn-sm" type="button" ng-click="refreshNow()" title="Refresh now"><i class="fas fa-sync"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div ng-repeat="qInfo in [
|
||||
{key: 'download', label: 'Download jobs', icon: 'fa-download', jobs: downloadJobs, counts: counts.download},
|
||||
{key: 'remove', label: 'Remove jobs', icon: 'fa-trash', jobs: removeJobs, counts: counts.remove},
|
||||
{key: 'cache', label: 'Cache cleanup jobs', icon: 'fa-broom', jobs: removeCaches, counts: counts.cache}
|
||||
]">
|
||||
<div class="admin-section-header">
|
||||
<h2><i class="fas {{qInfo.icon}}"></i> {{qInfo.label}}</h2>
|
||||
<span class="section-count">{{qInfo.jobs.length || 0}}</span>
|
||||
<span class="queue-state-pills">
|
||||
<span class="pill pill-waiting" ng-if="qInfo.counts.waiting">{{qInfo.counts.waiting}} waiting</span>
|
||||
<span class="pill pill-active" ng-if="qInfo.counts.active">{{qInfo.counts.active}} active</span>
|
||||
<span class="pill pill-completed" ng-if="qInfo.counts.completed">{{qInfo.counts.completed}} done</span>
|
||||
<span class="pill pill-failed" ng-if="qInfo.counts.failed">{{qInfo.counts.failed}} failed</span>
|
||||
<span class="pill pill-delayed" ng-if="qInfo.counts.delayed">{{qInfo.counts.delayed}} delayed</span>
|
||||
</span>
|
||||
<span style="margin-left: auto; display: inline-flex; gap: 6px;">
|
||||
<button class="btn btn-sm" type="button" ng-click="bulkRetryFailed(qInfo.key)" ng-disabled="!qInfo.counts.failed"><i class="fas fa-redo"></i> Retry all failed</button>
|
||||
<button class="btn btn-sm text-danger" type="button" ng-click="bulkDrain(qInfo.key)"><i class="fas fa-eraser"></i> Drain</button>
|
||||
</span>
|
||||
<table class="q-table" ng-if="jobs.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>JOB ID</th>
|
||||
<th>PAYLOAD</th>
|
||||
<th>ATTEMPTS</th>
|
||||
<th>DURATION</th>
|
||||
<th>PROGRESS</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="job in jobs" ng-class="{'q-row-failed': job.failedReason || job.stacktrace.length}">
|
||||
<td class="q-cell-id">
|
||||
<a target="_blank" ng-href="/r/{{job.id}}" ng-bind="'job:' + (job.id | limitTo:6)"></a>
|
||||
</td>
|
||||
<td class="q-cell-payload">
|
||||
<span ng-bind="job.name || 'anonymize'"></span>
|
||||
<span class="q-payload-detail" ng-if="job.data.repoId"> · {{job.data.repoId}}</span>
|
||||
</td>
|
||||
<td class="q-cell-num" ng-bind="job.attemptsMade || 1"></td>
|
||||
<td class="q-cell-num" ng-bind="jobDuration(job)"></td>
|
||||
<td class="q-cell-progress">
|
||||
<div class="q-progress-wrap" ng-if="jobProgressPct(job) !== null">
|
||||
<div class="q-progress-bar" ng-style="{'--pct': jobProgressPct(job) + '%'}"></div>
|
||||
<span class="q-progress-label" ng-bind="jobProgressPct(job) + '%'"></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="q-cell-actions">
|
||||
<button class="btn btn-sm" ng-click="retryJob(job)" title="Retry"><i class="fas fa-sync"></i></button>
|
||||
<button class="btn btn-sm" ng-click="removeJob(job)" title="Remove"><i class="fas fa-trash-alt"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Expanded error detail -->
|
||||
<div ng-repeat="job in jobs" ng-if="job.failedReason || job.stacktrace.length" class="q-error-detail" style="display:none;">
|
||||
<div ng-if="job.failedReason" class="q-error-reason" ng-bind="job.failedReason"></div>
|
||||
<pre ng-repeat="stack in job.stacktrace track by $index" class="q-error-stack"><code ng-bind="stack"></code></pre>
|
||||
</div>
|
||||
|
||||
<div class="queue-job-card" ng-repeat="job in qInfo.jobs | filter:jobMatchesState as filteredJobs">
|
||||
<div class="job-header">
|
||||
<div class="job-id">
|
||||
<span class="status-dot" ng-class="{'status-ready': job.progress.status == 'ready', 'status-error': job.progress.status == 'error' || (job.stacktrace && job.stacktrace.length), 'status-preparing': job.progress.status == 'preparing' || (job.processedOn && !job.finishedOn), 'status-removed': job.progress.status == 'removed'}"></span>
|
||||
<a target="_blank" ng-href="/r/{{job.id}}" ng-bind="job.id"></a>
|
||||
<span ng-bind="job.progress.status | title" style="font-family: var(--font-sans); color: var(--ink-muted); font-size: 12px;"></span>
|
||||
</div>
|
||||
<div ng-if="jobProgressPct(job) !== null" class="job-progress" title="{{jobProgressPct(job)}}%">
|
||||
<div class="job-progress-bar" style="width: {{jobProgressPct(job)}}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-timestamps">
|
||||
<span ng-if="job.timestamp"><i class="fas fa-clock"></i> Created: {{job.timestamp | humanTime}}</span>
|
||||
<span ng-if="job.processedOn"><i class="fas fa-cog"></i> Processed: {{job.processedOn | humanTime}}</span>
|
||||
<span ng-if="job.finishedOn"><i class="fas fa-check"></i> Finished: {{job.finishedOn | humanTime}}</span>
|
||||
<span ng-if="job.attemptsMade"><i class="fas fa-redo"></i> Attempts: {{job.attemptsMade}}</span>
|
||||
</div>
|
||||
<div ng-if="job.failedReason" style="color:#B42318; font-size:0.8rem; margin-top:4px;">{{job.failedReason}}</div>
|
||||
<div ng-if="job.stacktrace.length">
|
||||
<pre ng-repeat="stack in job.stacktrace track by $index" style="font-size: 0.8rem; max-height: 100px; overflow: auto; margin: 6px 0 0 0"><code ng-bind="stack"></code></pre>
|
||||
</div>
|
||||
<div class="job-actions">
|
||||
<button class="btn btn-sm" ng-click="retryJob(qInfo.key, job)"><i class="fas fa-sync"></i> Retry</button>
|
||||
<button class="btn btn-sm" ng-click="removeJob(qInfo.key, job)"><i class="fas fa-trash-alt"></i> Remove</button>
|
||||
<a class="btn btn-sm" href="/anonymize/{{job.id}}"><i class="far fa-edit"></i> Edit</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="paper-table-empty" ng-if="filteredJobs.length == 0" style="border:1px solid var(--border-color);border-radius:10px;background:var(--paper-card);">
|
||||
<div class="paper-table-empty" ng-if="jobs.length == 0" style="border:1px solid var(--border-color);border-radius:10px;background:var(--paper-card);">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span ng-if="!query.search && !query.state">No {{qInfo.label | lowercase}} in the queue.</span>
|
||||
<span ng-if="query.search || query.state">No jobs match the current filters.</span>
|
||||
<span ng-if="!query.search">No {{query.state}} jobs in the {{selectedQueue}} queue.</span>
|
||||
<span ng-if="query.search">No jobs match the current filters.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<h1 class="paper-page-title">Repositories</h1>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/" class="active"><i class="fas fa-code-branch"></i> Repositories</a>
|
||||
<a href="/admin/"><i class="fas fa-tachometer-alt"></i> Overview</a>
|
||||
<a href="/admin/repositories" class="active"><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"><i class="fas fa-tasks"></i> Queues</a>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<h1 class="paper-page-title">{{userInfo.username || 'User'}}</h1>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/"><i class="fas fa-code-branch"></i> Repositories</a>
|
||||
<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" class="active"><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"><i class="fas fa-tasks"></i> Queues</a>
|
||||
@@ -25,6 +26,8 @@
|
||||
<div class="user-actions" style="margin-top: 4px;">
|
||||
<button class="btn btn-sm text-danger" ng-if="userInfo.status !== 'banned'" ng-click="banUser()"><i class="fas fa-ban"></i> Ban</button>
|
||||
<button class="btn btn-sm" ng-if="userInfo.status === 'banned' || userInfo.status === 'removed'" ng-click="activateUser()"><i class="fas fa-check-circle"></i> Activate</button>
|
||||
<button class="btn btn-sm" ng-if="!userInfo.isAdmin" ng-click="promoteUser()"><i class="fas fa-user-shield"></i> Promote to admin</button>
|
||||
<button class="btn btn-sm text-danger" ng-if="userInfo.isAdmin" ng-click="demoteUser()"><i class="fas fa-user-minus"></i> Remove admin</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,6 +49,15 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="detail-label">Created</div>
|
||||
<div class="detail-value">{{userInfo.dateOfEntry | humanTime}}</div>
|
||||
|
||||
<div class="detail-label">Last connection</div>
|
||||
<div class="detail-value">
|
||||
<span ng-if="userInfo.accessTokenDates.github">{{userInfo.accessTokenDates.github | humanTime}}</span>
|
||||
<span class="text-muted" ng-if="!userInfo.accessTokenDates.github">never</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-label">GitHub repos</div>
|
||||
<div class="detail-value">
|
||||
{{userInfo.repositories.length}} repositories
|
||||
@@ -122,86 +134,76 @@
|
||||
<span class="section-count">{{repositories.length}}</span>
|
||||
</div>
|
||||
|
||||
<form class="w-100 dashboard-filter-row" aria-label="Repositories" accept-charset="UTF-8">
|
||||
<div class="search-wrap">
|
||||
<input
|
||||
type="search"
|
||||
class="form-control"
|
||||
aria-label="Search repositories"
|
||||
placeholder="Search repositories…"
|
||||
autocomplete="off"
|
||||
ng-model="search"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap" style="gap: 8px;">
|
||||
<div class="dropdown">
|
||||
<button class="btn dropdown-toggle" type="button" id="dropdownSort" data-toggle="dropdown">Sort</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownSort">
|
||||
<h6 class="dropdown-header">Sort by</h6>
|
||||
<a class="dropdown-item" href="#" ng-click="orderBy = '-anonymizeDate'">
|
||||
<i class="fas fa-check" ng-show="orderBy == '-anonymizeDate'"></i> Anonymize date
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" ng-click="orderBy = 'repoId'">
|
||||
<i class="fas fa-check" ng-show="orderBy == 'repoId'"></i> ID
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" ng-click="orderBy = '-status'">
|
||||
<i class="fas fa-check" ng-show="orderBy == '-status'"></i> Status
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-summary">
|
||||
<span class="summary-total">{{repositories.length | number}}</span>
|
||||
<span class="summary-pill ok" ng-class="{active: filters.status.ready === false}" ng-click="filters.status.ready = !filters.status.ready" title="Toggle ready filter">Ready <span class="count">{{statusCountFor('ready') | number}}</span></span>
|
||||
<span class="summary-pill warn" ng-class="{active: filters.status.preparing === false}" ng-click="filters.status.preparing = !filters.status.preparing" title="Toggle preparing filter">Preparing <span class="count">{{statusCountFor('preparing') | number}}</span></span>
|
||||
<span class="summary-pill error" ng-class="{active: filters.status.error === false}" ng-click="filters.status.error = !filters.status.error" title="Toggle errored filter">Errored <span class="count">{{statusCountFor('error') | number}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: filters.status.expired === false}" ng-click="filters.status.expired = !filters.status.expired" title="Toggle expired filter">Expired <span class="count">{{statusCountFor('expired') | number}}</span></span>
|
||||
<span class="summary-pill" ng-class="{active: filters.status.removed === false}" ng-click="filters.status.removed = !filters.status.removed" title="Toggle removed filter">Removed <span class="count">{{statusCountFor('removed') | number}}</span></span>
|
||||
</div>
|
||||
|
||||
<div class="dropdown">
|
||||
<button class="btn dropdown-toggle" type="button" id="dropdownStatus" data-toggle="dropdown">Status</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
|
||||
<h6 class="dropdown-header">Filter by status</h6>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="checkbox" id="adminUserStatusReady" ng-model="filters.status.ready" />
|
||||
<label class="form-check-label" for="adminUserStatusReady">Ready</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="checkbox" id="adminUserStatusExpired" ng-model="filters.status.expired" />
|
||||
<label class="form-check-label" for="adminUserStatusExpired">Expired</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="checkbox" id="adminUserStatusRemoved" ng-model="filters.status.removed" />
|
||||
<label class="form-check-label" for="adminUserStatusRemoved">Removed</label>
|
||||
</div>
|
||||
</div>
|
||||
<form class="w-100 admin-filter-toolbar" aria-label="Repositories" accept-charset="UTF-8">
|
||||
<div class="admin-filter-row">
|
||||
<div class="search-wrap">
|
||||
<input
|
||||
type="search"
|
||||
class="form-control"
|
||||
aria-label="Search repositories"
|
||||
placeholder="Search repoId, source repo, error message…"
|
||||
autocomplete="off"
|
||||
ng-model="search"
|
||||
/>
|
||||
</div>
|
||||
<span class="admin-filter-spacer"></span>
|
||||
<button class="btn btn-sm" type="button" ng-click="exportCsv()" title="Export current view to CSV"><i class="fas fa-file-csv"></i> Export</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="paper-table paper-table-repos w-100" role="table" aria-label="Repositories">
|
||||
<div class="bulk-bar" ng-if="selectedCount() > 0">
|
||||
<span><strong>{{selectedCount()}}</strong> selected</span>
|
||||
<button class="btn btn-sm" type="button" ng-click="bulkRefresh()"><i class="fas fa-sync"></i> Force refresh</button>
|
||||
<button class="btn btn-sm text-danger" type="button" ng-click="bulkRemoveCache()"><i class="fas fa-broom"></i> Remove cache</button>
|
||||
<button class="btn btn-sm" type="button" ng-click="clearSelection()">Clear</button>
|
||||
</div>
|
||||
|
||||
<div class="paper-table paper-table-repos has-bulk w-100" role="table" aria-label="Repositories">
|
||||
<div class="paper-table-head" role="row">
|
||||
<div role="columnheader">Repository</div>
|
||||
<div role="columnheader">Status</div>
|
||||
<div role="columnheader" class="num">Views</div>
|
||||
<div role="columnheader">Anonymized</div>
|
||||
<div role="columnheader" style="width: 28px;">
|
||||
<input type="checkbox" ng-click="selectAllOnPage()" ng-checked="allSelected" aria-label="Select all on page" />
|
||||
</div>
|
||||
<div role="columnheader"><span class="sortable" ng-class="{active: query.sort == 'source.repositoryName'}" ng-click="sortBy('source.repositoryName')">Repository <i class="fas" ng-class="sortIcon('source.repositoryName')"></i></span></div>
|
||||
<div role="columnheader"><span class="sortable" ng-class="{active: query.sort == 'status'}" ng-click="sortBy('status')">Status <i class="fas" ng-class="sortIcon('status')"></i></span></div>
|
||||
<div role="columnheader" class="num"><span class="sortable" ng-class="{active: query.sort == 'pageView'}" ng-click="sortBy('pageView')">Views <i class="fas" ng-class="sortIcon('pageView')"></i></span></div>
|
||||
<div role="columnheader"><span class="sortable" ng-class="{active: query.sort == 'anonymizeDate'}" ng-click="sortBy('anonymizeDate')">Anonymized <i class="fas" ng-class="sortIcon('anonymizeDate')"></i></span></div>
|
||||
<div role="columnheader" aria-label="Actions"></div>
|
||||
</div>
|
||||
<div
|
||||
class="paper-table-row"
|
||||
role="row"
|
||||
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed', 'repo-error': repo.status == 'error'}"
|
||||
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed', 'repo-error': repo.status == 'error', 'row-selected': selected[repo.repoId]}"
|
||||
ng-repeat="repo in repositories | filter:repoFiler | orderBy:orderBy as filteredRepositories"
|
||||
>
|
||||
<div role="cell" style="width: 28px;">
|
||||
<input type="checkbox" ng-model="selected[repo.repoId]" aria-label="Select repository" />
|
||||
</div>
|
||||
<div class="cell-anon" role="cell">
|
||||
<span class="type-badge type-repo">Repo</span>
|
||||
<div class="anon-text">
|
||||
<a class="repo-name" ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
|
||||
<a class="repo-name" target="_blank" ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
|
||||
<div class="anon-sub">
|
||||
<a href="https://github.com/{{repo.source.fullName}}/" ng-bind="repo.source.fullName"></a><span ng-if="repo.options.update"> · <a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}" ng-bind="repo.source.branch"></a></span><span ng-if="!repo.options.update"> · @<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}" ng-bind="repo.source.commit.substring(0, 8)"></a></span><span> · {{::repo.size.storage | humanFileSize}}</span><span> · {{::repo.options.terms.length | number}} terms</span>
|
||||
<a href="https://github.com/{{repo.source.fullName}}/" ng-bind="repo.source.fullName"></a><span ng-if="repo.options.update"> · <a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}" ng-bind="repo.source.branch"></a></span><span ng-if="!repo.options.update"> · @<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}" ng-bind="repo.source.commit.substring(0, 8)"></a></span><span ng-if="::repo.conference"> · <i class="fas fa-chalkboard-teacher"></i> {{repo.conference}}</span><span> · {{::repo.size.storage | humanFileSize}}</span><span> · {{::repo.options.terms.length | number}} terms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell-status" role="cell">
|
||||
<span class="status-line">
|
||||
<span class="status-dot" ng-class="{'status-removed': repo.status == 'removed' || repo.status == 'expired', 'status-ready': repo.status == 'ready', 'status-error': repo.status == 'error'}"></span>
|
||||
<span class="status-dot" ng-class="{'status-removed': repo.status == 'removed' || repo.status == 'expired', 'status-ready': repo.status == 'ready', 'status-error': repo.status == 'error', 'status-preparing': repo.status == 'preparing'}"></span>
|
||||
<span ng-bind="repo.status | title"></span>
|
||||
</span>
|
||||
<span class="status-sub status-sub-error" ng-if="repo.status == 'error' && repo.statusMessage" title="{{repo.statusMessage}}" ng-bind="repo.statusMessage"></span>
|
||||
<span class="status-sub" ng-if="repo.statusMessage" title="{{repo.statusMessage}}" ng-bind="repo.statusMessage"></span>
|
||||
</div>
|
||||
<div class="cell-views num" role="cell" ng-bind="::repo.pageView | number"></div>
|
||||
<div class="cell-views num" role="cell" ng-bind="::repo.pageView || 0 | number"></div>
|
||||
<div class="cell-expires" role="cell" ng-bind="repo.anonymizeDate | humanTime"></div>
|
||||
<div class="cell-actions" role="cell">
|
||||
<div class="dropdown">
|
||||
@@ -212,9 +214,11 @@
|
||||
<a class="dropdown-item" href="/anonymize/{{repo.repoId}}"><i class="far fa-edit"></i> Edit</a>
|
||||
<a class="dropdown-item" href="/r/{{repo.repoId}}/"><i class="fa fa-eye"></i> View repo</a>
|
||||
<a class="dropdown-item" href="/w/{{repo.repoId}}/" target="_self" ng-if="repo.options.page && repo.status == 'ready'"><i class="fas fa-globe"></i> View page</a>
|
||||
<a class="dropdown-item" href="#" ng-click="fetchGithubInfo(repo)"><i class="fab fa-github"></i> Live GitHub info</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" ng-show="repo.status == 'ready' || repo.status == 'error'" ng-click="updateRepository(repo)"><i class="fas fa-sync"></i> Force update</a>
|
||||
<a class="dropdown-item" href="#" ng-show="repo.status == 'removed'" ng-click="updateRepository(repo)"><i class="fas fa-check-circle"></i> Enable</a>
|
||||
<a class="dropdown-item" href="#" ng-show="repo.statusMessage" ng-click="showStatusMessage(repo)"><i class="fas fa-exclamation-triangle"></i> View status message</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" ng-click="removeCache(repo)"><i class="fas fa-broom"></i> Remove cache</a>
|
||||
<a class="dropdown-item text-danger" href="#" ng-show="repo.status == 'ready'" ng-click="removeRepository(repo)"><i class="fas fa-trash-alt"></i> Remove</a>
|
||||
@@ -224,7 +228,7 @@
|
||||
</div>
|
||||
<div class="paper-table-empty" ng-if="filteredRepositories.length == 0">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<span>No repositories to display.</span>
|
||||
<span>No repositories match the current filters.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<h1 class="paper-page-title">Users</h1>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/"><i class="fas fa-code-branch"></i> Repositories</a>
|
||||
<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" class="active"><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"><i class="fas fa-tasks"></i> Queues</a>
|
||||
|
||||
@@ -16,6 +16,31 @@
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="leftCol-search">
|
||||
<div class="tree-search-box">
|
||||
<i class="fas tree-search-icon" ng-class="fileSearchLoading ? 'fa-spinner fa-spin' : 'fa-search'"></i>
|
||||
<input
|
||||
type="text"
|
||||
class="tree-search-input"
|
||||
placeholder="Search files"
|
||||
ng-model="fileSearchQuery"
|
||||
ng-model-options="{ debounce: 300 }"
|
||||
ng-change="onFileSearchChange()"
|
||||
aria-label="Search files"
|
||||
/>
|
||||
<kbd class="tree-search-kbd" ng-hide="fileSearchQuery">{{isMac ? '⌘' : 'Ctrl+'}}K</kbd>
|
||||
<button
|
||||
class="tree-search-clear"
|
||||
ng-show="fileSearchQuery"
|
||||
ng-click="fileSearchQuery = ''; onFileSearchChange()"
|
||||
aria-label="Clear search"
|
||||
>×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="leftCol-project-header">
|
||||
<span class="project-name" ng-bind="repoId"></span>
|
||||
<span class="project-file-count">{{fileCounts[''] || files.length}} files</span>
|
||||
</div>
|
||||
<div class="leftCol-body">
|
||||
<div
|
||||
ng-if="options.truncatedFolders.length > 0"
|
||||
@@ -25,7 +50,7 @@
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
{{ 'WARNINGS.repo_truncated' | translate }}
|
||||
</div>
|
||||
<tree class="files" file="files"></tree>
|
||||
<tree class="files" file="files" search-query="fileSearchQuery" search-results="fileSearchResults"></tree>
|
||||
</div>
|
||||
<div class="leftCol-foot">
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user