mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-19 00:52:17 +02:00
feat: make the dashboard readable at a glance
The dashboard showed the anonymize date under "Expired", a dash in the
Expires column for 54 of 60 rows, raw status codes such as
branch_not_found, and a chip reading "REMOVED" that actually meant
"removed items are hidden". Muted text sat at 3.5:1 contrast, keyboard
focus was invisible, numbers were left-aligned, and a legacy record
without an identifier rendered as an empty link to /pr/undefined/.
This rewrites the dashboard template and controller and tightens the
theme tokens:
- Status sub-line is labelled ("anonymized on Sep 6, 2026"); Expires
shows Never, the date, or "Expired on <date>".
- Conference folded into the name cell as a tag; Views right-aligned;
every column header is a keyboard-sortable button with aria-sort.
- Sort/Status buttons show their state; chips read "Hiding Removed";
result count and Clear filters; empty state with a way out.
- Status filter covers Ready, In progress, Error, Expired, Removed.
Stuck downloads say so; codes map to sentences; statuses to labels.
- Lists load in parallel and merge once behind a skeleton; broken
records are flagged instead of linking nowhere.
- Actions menu: View, View page, Edit, Force update, divider, Remove,
all as buttons rather than href="#" anchors.
- Theme: warm dark canvas, muted text >= 4.5:1 in both modes, semantic
status tokens, 11/13/14/16px type scale, mono only for identifiers,
6px/10px/pill radii, :focus-visible ring, quota bars in ink until a
limit is near, unlimited shown as such.
- Mobile: no 9px text, filter row wraps in two lines, meta on one line.
Adds scripts/dev-mock.js to serve the dashboard with fixture data and
test/dashboard-ui.test.js covering the filters, template invariants and
token contrast.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
9f00641529
commit
8e9b5b9a45
+189
-176
@@ -5,256 +5,269 @@
|
||||
<div class="d-flex align-items-end justify-content-between flex-wrap" style="gap: 12px;">
|
||||
<div>
|
||||
<h1 class="paper-page-title">Your <em>anonymizations</em></h1>
|
||||
<p class="paper-page-lede">Every repository and pull request you’ve mirrored, with live status and stats.</p>
|
||||
<p class="paper-page-lede">Every repository, pull request, and gist you’ve mirrored, with live status and stats.</p>
|
||||
</div>
|
||||
<a href="/anonymize" class="btn btn-ink">
|
||||
<i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> New anonymization
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Quota bars -->
|
||||
<div class="quota-row" ng-show="quota">
|
||||
<div class="quota-item">
|
||||
<!-- Quota -->
|
||||
<div class="quota-row" ng-if="quota">
|
||||
<div class="quota-item" ng-repeat="q in [
|
||||
{key: 'repository', label: 'Repositories', kind: 'count'},
|
||||
{key: 'storage', label: 'Storage', kind: 'bytes'},
|
||||
{key: 'file', label: 'Files', kind: 'count'}
|
||||
]">
|
||||
<div class="quota-header">
|
||||
<span class="quota-label">Repositories</span>
|
||||
<span class="quota-value" ng-show="quota">{{quota.repository.used | number}}/{{quota.repository.total}}</span>
|
||||
<span class="quota-label">{{q.label}}</span>
|
||||
<span class="quota-value" ng-if="q.kind === 'count'">
|
||||
{{quota[q.key].used | number}}<span ng-if="!quota[q.key].unlimited"> / {{quota[q.key].total | number}}</span>
|
||||
<span class="quota-unlimited-tag" ng-if="quota[q.key].unlimited">Unlimited</span>
|
||||
</span>
|
||||
<span class="quota-value" ng-if="q.kind === 'bytes'">
|
||||
{{quota[q.key].used | humanFileSize}}<span ng-if="!quota[q.key].unlimited"> / {{quota[q.key].total | humanFileSize}}</span>
|
||||
<span class="quota-unlimited-tag" ng-if="quota[q.key].unlimited">Unlimited</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="progress quota-progress">
|
||||
<div
|
||||
class="progress-bar"
|
||||
ng-class="{'bg-success': quota.repository.percent < 25 || quota.repository.total == 0, 'bg-danger': quota.repository.percent > 95 && quota.repository.total > 0, 'bg-warning': quota.repository.percent > 75 && quota.repository.total > 0 }"
|
||||
role="progressbar"
|
||||
style="width: {{quota.repository.percent}}%;"
|
||||
aria-valuenow="{{quota.repository.used}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{quota.repository.total}}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quota-item">
|
||||
<div class="quota-header">
|
||||
<span class="quota-label">Storage</span>
|
||||
<span class="quota-value" ng-show="quota">{{quota.storage.used | humanFileSize}}/{{quota.storage.total | humanFileSize}}</span>
|
||||
</div>
|
||||
<div class="progress quota-progress">
|
||||
<div
|
||||
class="progress-bar"
|
||||
ng-class="{'bg-success': quota.storage.percent < 25 || quota.storage.total == 0, 'bg-danger': quota.storage.percent > 95 && quota.storage.total > 0, 'bg-warning': quota.storage.percent > 75 && quota.storage.total > 0 }"
|
||||
role="progressbar"
|
||||
style="width: {{quota.storage.percent}}%;"
|
||||
aria-valuenow="{{quota.storage.used}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{quota.storage.total}}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quota-item">
|
||||
<div class="quota-header">
|
||||
<span class="quota-label">Files</span>
|
||||
<span class="quota-value" ng-show="quota">{{quota.file.used | number}}/{{quota.file.total || "∞"}}</span>
|
||||
</div>
|
||||
<div class="progress quota-progress">
|
||||
<div
|
||||
class="progress-bar"
|
||||
ng-class="{'bg-success': quota.file.percent < 25 || quota.file.total == 0, 'bg-danger': quota.file.percent > 95 && quota.file.total > 0, 'bg-warning': quota.file.percent > 75 && quota.file.total > 0 }"
|
||||
role="progressbar"
|
||||
style="width: {{quota.file.percent}}%;"
|
||||
aria-valuenow="{{quota.file.used}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{quota.file.total}}"
|
||||
></div>
|
||||
<div
|
||||
class="quota-track"
|
||||
ng-class="'quota-' + quota[q.key].level"
|
||||
role="progressbar"
|
||||
aria-label="{{q.label}} quota"
|
||||
aria-valuenow="{{quota[q.key].used}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="{{quota[q.key].unlimited ? quota[q.key].used : quota[q.key].total}}"
|
||||
aria-valuetext="{{quota[q.key].unlimited ? 'unlimited' : (quota[q.key].percent | number:0) + '% used'}}"
|
||||
>
|
||||
<div class="quota-fill" ng-if="!quota[q.key].unlimited" ng-style="{width: quota[q.key].percent + '%'}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search + filters row -->
|
||||
<form class="w-100 dashboard-filter-row" aria-label="Dashboard" accept-charset="UTF-8">
|
||||
<!-- Search + filters -->
|
||||
<form class="w-100 dashboard-filter-row" aria-label="Filter anonymizations" accept-charset="UTF-8" ng-submit="$event.preventDefault()">
|
||||
<div class="search-wrap">
|
||||
<input
|
||||
type="search"
|
||||
id="search"
|
||||
class="form-control"
|
||||
aria-label="Search..."
|
||||
placeholder="Search anonymizations…"
|
||||
aria-label="Search anonymizations"
|
||||
placeholder="Search by name, source, or conference…"
|
||||
autocomplete="off"
|
||||
ng-model="search"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap" style="gap: 8px">
|
||||
<!-- Type filter -->
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'all'}" ng-click="typeFilter = 'all'">All</button>
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'repo'}" ng-click="typeFilter = 'repo'">Repos</button>
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'pr'}" ng-click="typeFilter = 'pr'">PRs</button>
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'gist'}" ng-click="typeFilter = 'gist'">Gists</button>
|
||||
</div>
|
||||
<div class="dashboard-filter-controls">
|
||||
<div class="btn-group" role="group" aria-label="Type">
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'all'}" aria-pressed="{{typeFilter === 'all'}}" ng-click="typeFilter = 'all'">All</button>
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'repo'}" aria-pressed="{{typeFilter === 'repo'}}" ng-click="typeFilter = 'repo'">Repos</button>
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'pr'}" aria-pressed="{{typeFilter === 'pr'}}" ng-click="typeFilter = 'pr'">PRs</button>
|
||||
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'gist'}" aria-pressed="{{typeFilter === 'gist'}}" ng-click="typeFilter = 'gist'">Gists</button>
|
||||
</div>
|
||||
|
||||
<div class="dropdown">
|
||||
<button
|
||||
class="btn btn-sm dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownSort"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Sort
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownSort">
|
||||
<h6 class="dropdown-header">Select order</h6>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="radio" name="sort" id="sortFullName" value="_name" ng-model="orderBy" />
|
||||
<label class="form-check-label" for="sortFullName">Name</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="radio" name="sort" id="sortAnonymizeDate" value="-anonymizeDate" ng-model="orderBy" />
|
||||
<label class="form-check-label" for="sortAnonymizeDate">Anonymize Date</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="radio" name="sort" id="sortStatus" value="-status" ng-model="orderBy" />
|
||||
<label class="form-check-label" for="sortStatus">Status</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="radio" name="sort" id="sortLastView" value="-lastView" ng-model="orderBy" />
|
||||
<label class="form-check-label" for="sortLastView">Last View</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="radio" name="sort" id="sortPageView" value="-pageView" ng-model="orderBy" />
|
||||
<label class="form-check-label" for="sortPageView">Page View</label>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button
|
||||
class="btn dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownSort"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="filter-btn-label">Sort</span>
|
||||
<span class="filter-btn-value">{{sortLabel()}}</span>
|
||||
<i class="fas" ng-class="sortDesc() ? 'fa-arrow-down' : 'fa-arrow-up'" aria-hidden="true"></i>
|
||||
<span class="sr-only">{{sortDesc() ? 'descending' : 'ascending'}}</span>
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownSort">
|
||||
<h6 class="dropdown-header">Order by</h6>
|
||||
<div class="form-check dropdown-item" ng-repeat="(field, def) in sortFields">
|
||||
<input class="form-check-input" type="radio" name="sort" id="sort-{{$index}}" ng-checked="isSortedBy(field)" ng-click="setSort(field, def.defaultDesc)" />
|
||||
<label class="form-check-label" for="sort-{{$index}}">{{def.label}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropdown">
|
||||
<button
|
||||
class="btn btn-sm dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownStatus"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
Status
|
||||
<div class="dropdown-divider"></div>
|
||||
<button type="button" class="dropdown-item" ng-click="toggleSortDirection()">
|
||||
<i class="fas fa-exchange-alt fa-rotate-90" aria-hidden="true"></i>
|
||||
{{sortDesc() ? 'Switch to ascending' : 'Switch to descending'}}
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
|
||||
<h6 class="dropdown-header">Select status</h6>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="checkbox" id="statusReady" value="ready" ng-model="filters.status.ready" />
|
||||
<label class="form-check-label" for="statusReady">Ready</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="checkbox" id="statusExpired" value="expired" ng-model="filters.status.expired" />
|
||||
<label class="form-check-label" for="statusExpired">Expired</label>
|
||||
</div>
|
||||
<div class="form-check dropdown-item">
|
||||
<input class="form-check-input" type="checkbox" id="statusRemoved" value="removed" ng-model="filters.status.removed" />
|
||||
<label class="form-check-label" for="statusRemoved">Removed</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropdown">
|
||||
<button
|
||||
class="btn dropdown-toggle"
|
||||
type="button"
|
||||
id="dropdownStatus"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="filter-btn-label">Status</span>
|
||||
<span class="filter-btn-value" ng-if="!hasHiddenStatus()">All</span>
|
||||
<span class="filter-btn-value" ng-if="hasHiddenStatus()">{{hiddenStatusCount()}} hidden</span>
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
|
||||
<h6 class="dropdown-header">Show statuses</h6>
|
||||
<div class="form-check dropdown-item" ng-repeat="(key, label) in statusKeyLabels">
|
||||
<input class="form-check-input" type="checkbox" id="status-{{key}}" ng-model="filters.status[key]" />
|
||||
<label class="form-check-label" for="status-{{key}}">{{label}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Active filter chips -->
|
||||
<div class="d-flex flex-wrap mt-2" style="gap: 6px" ng-show="filters.status.ready === false || filters.status.expired === false || filters.status.removed === false">
|
||||
<span class="filter-chip" ng-show="!v" ng-repeat="(f, v) in filters.status">
|
||||
{{f | title}}
|
||||
<button type="button" class="filter-chip-close" aria-label="Remove filter" ng-click="filters.status[f] = true;">
|
||||
×
|
||||
</button>
|
||||
<!-- Result count + active filter chips -->
|
||||
<div class="dashboard-meta" ng-if="!loading">
|
||||
<span class="dashboard-count">
|
||||
<span ng-if="filteredItems.length === items.length">{{items.length | number}} anonymization<span ng-if="items.length !== 1">s</span></span>
|
||||
<span ng-if="filteredItems.length !== items.length">{{filteredItems.length | number}} of {{items.length | number}} shown</span>
|
||||
</span>
|
||||
<span class="filter-chip" ng-repeat="(f, v) in filters.status" ng-if="v === false">
|
||||
Hiding {{statusKeyLabels[f]}}
|
||||
<button type="button" class="filter-chip-close" aria-label="Show {{statusKeyLabels[f]}} again" ng-click="filters.status[f] = true;">×</button>
|
||||
</span>
|
||||
<button type="button" class="btn-link-inline" ng-if="hasActiveFilters()" ng-click="clearFilters()">Clear filters</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="paper-table w-100" role="table" aria-label="Anonymizations">
|
||||
<div class="paper-table paper-table-dashboard w-100" role="table" aria-label="Anonymizations" aria-busy="{{loading}}">
|
||||
<div class="paper-table-head" role="row">
|
||||
<div role="columnheader">Anonymization</div>
|
||||
<div role="columnheader">Conference</div>
|
||||
<div role="columnheader">Status</div>
|
||||
<div role="columnheader" class="num">Views</div>
|
||||
<div role="columnheader">Expires</div>
|
||||
<div role="columnheader" aria-label="Actions"></div>
|
||||
<div role="columnheader" aria-sort="{{isSortedBy('_name') ? (sortDesc() ? 'descending' : 'ascending') : 'none'}}">
|
||||
<button type="button" class="sortable" ng-class="{active: isSortedBy('_name')}" ng-click="setSort('_name')">
|
||||
Anonymization <i class="fas" ng-if="isSortedBy('_name')" ng-class="sortDesc() ? 'fa-arrow-down' : 'fa-arrow-up'" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div role="columnheader" aria-sort="{{isSortedBy('status') ? (sortDesc() ? 'descending' : 'ascending') : 'none'}}">
|
||||
<button type="button" class="sortable" ng-class="{active: isSortedBy('status')}" ng-click="setSort('status')">
|
||||
Status <i class="fas" ng-if="isSortedBy('status')" ng-class="sortDesc() ? 'fa-arrow-down' : 'fa-arrow-up'" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div role="columnheader" class="num" aria-sort="{{isSortedBy('pageView') ? (sortDesc() ? 'descending' : 'ascending') : 'none'}}">
|
||||
<button type="button" class="sortable" ng-class="{active: isSortedBy('pageView')}" ng-click="setSort('pageView')">
|
||||
Views <i class="fas" ng-if="isSortedBy('pageView')" ng-class="sortDesc() ? 'fa-arrow-down' : 'fa-arrow-up'" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div role="columnheader" aria-sort="{{isSortedBy('options.expirationDate') ? (sortDesc() ? 'descending' : 'ascending') : 'none'}}">
|
||||
<button type="button" class="sortable" ng-class="{active: isSortedBy('options.expirationDate')}" ng-click="setSort('options.expirationDate')">
|
||||
Expires <i class="fas" ng-if="isSortedBy('options.expirationDate')" ng-class="sortDesc() ? 'fa-arrow-down' : 'fa-arrow-up'" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div role="columnheader"><span class="sr-only">Actions</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Loading skeleton: keeps the layout stable while the three lists load -->
|
||||
<div class="paper-table-row paper-table-skeleton" role="row" aria-hidden="true" ng-if="loading" ng-repeat="i in [1, 2, 3, 4]">
|
||||
<div class="cell-anon" role="cell"><span class="skeleton skeleton-badge"></span><div class="anon-text"><span class="skeleton skeleton-line" style="width: 38%"></span><span class="skeleton skeleton-line skeleton-line-sm" style="width: 56%"></span></div></div>
|
||||
<div class="cell-status" role="cell"><span class="skeleton skeleton-line" style="width: 60%"></span></div>
|
||||
<div class="cell-views num" role="cell"><span class="skeleton skeleton-line" style="width: 40%"></span></div>
|
||||
<div class="cell-expires" role="cell"><span class="skeleton skeleton-line" style="width: 55%"></span></div>
|
||||
<div class="cell-actions" role="cell"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="paper-table-row"
|
||||
role="row"
|
||||
ng-class="{'repo-inactive': item.status == 'expired' || item.status == 'removed', 'repo-error': item.status == 'error'}"
|
||||
ng-repeat="item in items | filter:itemFilter | orderBy:orderBy as filteredItems"
|
||||
ng-if="!loading"
|
||||
ng-class="{'repo-inactive': item._statusKey == 'expired' || item._statusKey == 'removed', 'repo-error': item.status == 'error', 'row-clickable': !!item._viewUrl}"
|
||||
ng-repeat="item in items | filter:itemFilter | orderBy:orderBy as filteredItems track by (item._type + ':' + (item._id || $index))"
|
||||
ng-click="openItem(item, $event)"
|
||||
>
|
||||
<div class="cell-anon" role="cell">
|
||||
<span class="type-badge" ng-class="{'type-repo': item._type === 'repo', 'type-pr': item._type === 'pr', 'type-gist': item._type === 'gist'}">{{item._type === 'repo' ? 'Repo' : item._type === 'pr' ? 'PR' : 'Gist'}}</span>
|
||||
<span class="type-badge type-coauthor" ng-if="item.role === 'coauthor'" title="You are a co-author on this anonymization">Co-author</span>
|
||||
<div class="anon-text">
|
||||
<a ng-href="{{item._viewUrl}}" class="repo-name" ng-bind="item._name"></a>
|
||||
<div class="anon-sub">
|
||||
<a ng-if="item._type === 'repo'" href="https://github.com/{{item.source.fullName}}/" ng-bind="item.source.fullName"></a><span ng-if="item._type === 'repo' && item.options.update"> · <a href="https://github.com/{{item.source.fullName}}/tree/{{item.source.branch}}" ng-bind="item.source.branch"></a><span ng-if="item.source.commit"> · @<a href="https://github.com/{{item.source.fullName}}/tree/{{item.source.commit}}" ng-bind="item.source.commit.substring(0, 8)"></a></span></span><span ng-if="item._type === 'repo' && !item.options.update"> · @<a href="https://github.com/{{item.source.fullName}}/tree/{{item.source.commit}}" ng-bind="item.source.commit.substring(0, 8)"></a></span>
|
||||
<a ng-if="item._type === 'pr'" href="https://github.com/{{item.source.repositoryFullName}}/pull/{{item.source.pullRequestId}}" ng-bind="item._source"></a>
|
||||
<a ng-if="item._type === 'gist'" href="https://gist.github.com/{{item.source.gistId}}" ng-bind="item._source"></a>
|
||||
<div class="anon-title">
|
||||
<a ng-if="item._viewUrl" ng-href="{{item._viewUrl}}" class="repo-name" ng-bind="item._name"></a>
|
||||
<span ng-if="!item._viewUrl" class="repo-name repo-name-static" ng-bind="item._name"></span>
|
||||
<span class="type-badge type-coauthor" ng-if="item.role === 'coauthor'" title="You are a co-author on this anonymization">Co-author</span>
|
||||
</div>
|
||||
<div class="anon-sub" ng-if="!item._broken || item.conference">
|
||||
<span class="anon-source" ng-if="item._type === 'repo' && item.source.fullName && !item._broken">
|
||||
<a href="https://github.com/{{item.source.fullName}}/" target="_blank" rel="noopener" ng-bind="item.source.fullName"></a><span ng-if="item.options.update && item.source.branch"> · <a href="https://github.com/{{item.source.fullName}}/tree/{{item.source.branch}}" target="_blank" rel="noopener" ng-bind="item.source.branch"></a></span><span ng-if="item.source.commit"> · <a class="commit-hash" href="https://github.com/{{item.source.fullName}}/tree/{{item.source.commit}}" target="_blank" rel="noopener">@{{item.source.commit.substring(0, 8)}}</a></span>
|
||||
</span>
|
||||
<span class="anon-source" ng-if="item._type === 'pr' && item.source.repositoryFullName && !item._broken">
|
||||
<a href="https://github.com/{{item.source.repositoryFullName}}/pull/{{item.source.pullRequestId}}" target="_blank" rel="noopener" ng-bind="item._source"></a>
|
||||
</span>
|
||||
<span class="anon-source" ng-if="item._type === 'gist' && item.source.gistId && !item._broken">
|
||||
<a href="https://gist.github.com/{{item.source.gistId}}" target="_blank" rel="noopener" ng-bind="item._source"></a>
|
||||
</span>
|
||||
<span class="cell-conf conf-tag" ng-if="item.conference" title="{{item.conference}}"><i class="fas fa-university" aria-hidden="true"></i> <span ng-bind="item.conference"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell-conf" role="cell">
|
||||
<span ng-if="item.conference" title="{{item.conference}}" ng-bind="item.conference"></span>
|
||||
<span class="empty-dash" ng-if="!item.conference">—</span>
|
||||
</div>
|
||||
<div class="cell-status" role="cell">
|
||||
<div class="status-line">
|
||||
<span
|
||||
class="status-dot"
|
||||
ng-class="{'status-removed': item.status == 'removed' || item.status == 'expired' || item.status == 'removing' || item.status == 'expiring', 'status-preparing': item.status == 'preparing' || item.status == 'download', 'status-ready': item.status == 'ready', 'status-error': item.status == 'error'}"
|
||||
></span>
|
||||
<span ng-bind="item.status | title"></span>
|
||||
<span class="status-dot" ng-class="'status-' + item._statusKey" aria-hidden="true"></span>
|
||||
<span class="status-word" ng-bind="item.status | statusLabel"></span>
|
||||
</div>
|
||||
<div class="status-sub status-sub-error" ng-if="item.status == 'error' && item.statusMessage" title="{{item.statusMessage}}" ng-bind="item.statusMessage | statusMsg"></div>
|
||||
<div class="status-sub" ng-if="item.status != 'error' && item.anonymizeDate" title="Last anonymized {{item.anonymizeDate | humanTime}}" ng-bind="item.anonymizeDate | humanTime"></div>
|
||||
<div class="status-sub status-sub-warn" ng-if="item._stale" title="Last activity {{item.anonymizeDate || item.lastView | humanTime}}">
|
||||
Last activity {{item.anonymizeDate || item.lastView | humanTime}} · may be stuck
|
||||
</div>
|
||||
<div class="status-sub" ng-if="item.status != 'error' && !item._stale && item.anonymizeDate">
|
||||
anonymized {{item.anonymizeDate | humanTime}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell-views num" role="cell" ng-bind="item.pageView | number"></div>
|
||||
<div class="cell-expires" role="cell">
|
||||
<span ng-if="item.options.expirationMode !== 'never' && item.status == 'ready'" ng-bind="item.options.expirationDate | humanTime"></span>
|
||||
<span class="empty-dash" ng-if="!(item.options.expirationMode !== 'never' && item.status == 'ready')">—</span>
|
||||
<span ng-if="item._expiry.kind === 'never'" class="expires-never">Never</span>
|
||||
<span ng-if="item._expiry.kind === 'date'" ng-bind="item._expiry.date | humanTime"></span>
|
||||
<span ng-if="item._expiry.kind === 'expired' && item._expiry.date" class="expires-past">Expired {{item._expiry.date | humanTime}}</span>
|
||||
<span ng-if="item._expiry.kind === 'expired' && !item._expiry.date" class="expires-past">Expired</span>
|
||||
<span class="empty-dash" ng-if="item._expiry.kind === 'none'" aria-label="Not applicable">—</span>
|
||||
</div>
|
||||
<div class="cell-actions" role="cell">
|
||||
<div class="dropdown">
|
||||
<div class="dropdown" ng-if="!item._broken">
|
||||
<button
|
||||
class="btn btn-icon-dots"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label="Actions"
|
||||
aria-label="Actions for {{item._name}}"
|
||||
>
|
||||
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" ng-href="{{item._editUrl}}">
|
||||
<i class="far fa-edit" aria-hidden="true"></i> Edit
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" ng-show="item.status == 'ready' || item.status == 'error'" ng-click="refreshItem(item)">
|
||||
<i class="fas fa-sync"></i> Force update
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" ng-show="item.status == 'removed'" ng-click="refreshItem(item)">
|
||||
<i class="fas fa-check-circle"></i> Enable
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" ng-show="item.status == 'expired'" ng-click="extendItem(item)">
|
||||
<i class="fas fa-calendar-plus"></i> Extend 6 months
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" ng-show="(item.status == 'ready' || item.status == 'expired' || item.status == 'error') && item.role !== 'coauthor'" ng-click="removeItem(item)">
|
||||
<i class="fas fa-trash-alt"></i> Remove
|
||||
</a>
|
||||
<a class="dropdown-item" ng-href="{{item._viewUrl}}">
|
||||
<i class="fa fa-eye" aria-hidden="true"></i> View
|
||||
</a>
|
||||
<a class="dropdown-item" href="/w/{{item.repoId}}/" target="_self" ng-if="item._type === 'repo' && item.options.page && item.status == 'ready'">
|
||||
<i class="fas fa-globe"></i> View Page
|
||||
<i class="fas fa-globe" aria-hidden="true"></i> View page
|
||||
</a>
|
||||
<a class="dropdown-item" ng-href="{{item._editUrl}}">
|
||||
<i class="far fa-edit" aria-hidden="true"></i> Edit
|
||||
</a>
|
||||
<button type="button" class="dropdown-item" ng-if="item.status == 'ready' || item.status == 'error'" ng-click="refreshItem(item)">
|
||||
<i class="fas fa-sync" aria-hidden="true"></i> Force update
|
||||
</button>
|
||||
<button type="button" class="dropdown-item" ng-if="item.status == 'removed'" ng-click="refreshItem(item)">
|
||||
<i class="fas fa-check-circle" aria-hidden="true"></i> Enable
|
||||
</button>
|
||||
<button type="button" class="dropdown-item" ng-if="item.status == 'expired'" ng-click="extendItem(item)">
|
||||
<i class="fas fa-calendar-plus" aria-hidden="true"></i> Extend 6 months
|
||||
</button>
|
||||
<div ng-if="(item.status == 'ready' || item.status == 'expired' || item.status == 'error') && item.role !== 'coauthor'">
|
||||
<div class="dropdown-divider"></div>
|
||||
<button type="button" class="dropdown-item dropdown-item-danger" ng-click="removeItem(item)">
|
||||
<i class="fas fa-trash-alt" aria-hidden="true"></i> Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="paper-table-empty" ng-if="filteredItems.length == 0">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<span>Nothing to display.</span>
|
||||
|
||||
<div class="paper-table-empty" ng-if="!loading && filteredItems.length == 0">
|
||||
<i class="fas fa-inbox" aria-hidden="true"></i>
|
||||
<span ng-if="items.length == 0">You have no anonymizations yet.</span>
|
||||
<span ng-if="items.length > 0">Nothing matches the current filters.</span>
|
||||
<div class="paper-table-empty-actions">
|
||||
<button type="button" class="btn btn-outline-ink" ng-if="hasActiveFilters()" ng-click="clearFilters()">Clear filters</button>
|
||||
<a href="/anonymize" class="btn btn-ink" ng-if="items.length == 0"><i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> New anonymization</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user