fix: make dashboard UI consistent and compact on mobile

This commit is contained in:
Thomas Durieux committed 2026-10-07 14:54:49 +00:00
1 parent adc9dbd439
commit 282efdc16e
52 files changed
+2177 -712

No files matched your search

+30 -30
View File
@@ -3,25 +3,25 @@
<h1 class="paper-page-title">Conferences</h1>
<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" class="active"><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>
<a href="/admin/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
<a href="/admin/repositories"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
<a href="/admin/users"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
<a href="/admin/conferences" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
<a href="/admin/queues"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
<a href="/admin/errors"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Errors</a>
</nav>
<div class="admin-summary">
<span class="summary-total">{{ total >= 0 ? fmt.number(total) : '…' }}</span>
<span class="summary-pill ok" title="Toggle ready filter" :class="{active: query?.ready}" @click="query.ready = !query.ready; query.page = 1">Ready <span class="count">{{ fmt?.number(statusCountFor('ready')) }}</span></span>
<span class="summary-pill warn" title="Toggle preparing filter" :class="{active: query?.preparing}" @click="query.preparing = !query.preparing; query.page = 1">Preparing <span class="count">{{ fmt?.number(statusCountFor('preparing')) }}</span></span>
<span class="summary-pill error" title="Toggle errored filter" :class="{active: query?.error}" @click="query.error = !query.error; query.page = 1">Errored <span class="count">{{ fmt?.number(statusCountFor('error')) }}</span></span>
<span class="summary-pill" title="Toggle expired filter" :class="{active: query?.expired}" @click="query.expired = !query.expired; query.page = 1">Expired <span class="count">{{ fmt?.number(statusCountFor('expired')) }}</span></span>
<span class="summary-pill" title="Toggle removed filter" :class="{active: query?.removed}" @click="query.removed = !query.removed; query.page = 1">Removed <span class="count">{{ fmt?.number(statusCountFor('removed')) }}</span></span>
<button type="button" class="summary-pill ok" title="Toggle ready filter" :class="{active: query?.ready}" @click="query.ready = !query.ready; query.page = 1" :aria-pressed="!!(query?.ready)">Ready <span class="count">{{ fmt?.number(statusCountFor('ready')) }}</span></button>
<button type="button" class="summary-pill warn" title="Toggle preparing filter" :class="{active: query?.preparing}" @click="query.preparing = !query.preparing; query.page = 1" :aria-pressed="!!(query?.preparing)">Preparing <span class="count">{{ fmt?.number(statusCountFor('preparing')) }}</span></button>
<button type="button" class="summary-pill error" title="Toggle errored filter" :class="{active: query?.error}" @click="query.error = !query.error; query.page = 1" :aria-pressed="!!(query?.error)">Errored <span class="count">{{ fmt?.number(statusCountFor('error')) }}</span></button>
<button type="button" class="summary-pill" title="Toggle expired filter" :class="{active: query?.expired}" @click="query.expired = !query.expired; query.page = 1" :aria-pressed="!!(query?.expired)">Expired <span class="count">{{ fmt?.number(statusCountFor('expired')) }}</span></button>
<button type="button" class="summary-pill" title="Toggle removed filter" :class="{active: query?.removed}" @click="query.removed = !query.removed; query.page = 1" :aria-pressed="!!(query?.removed)">Removed <span class="count">{{ fmt?.number(statusCountFor('removed')) }}</span></button>
</div>
<div class="alert alert-danger" style="margin: 8px 0;" v-if="fetchError">
<i class="fas fa-exclamation-triangle"></i> {{ fetchError }}
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg> {{ fetchError }}
</div>
<form class="w-100 admin-filter-toolbar" aria-label="Conferences" accept-charset="UTF-8" v-form="viewState">
@@ -31,11 +31,11 @@
<span class="admin-search-hint" v-if="!query?.search">/</span>
</div>
<span class="admin-filter-spacer"></span>
<button class="btn btn-sm" type="button" @click="exportCsv()"><i class="fas fa-file-csv"></i> Export</button>
<button class="btn btn-sm" type="button" @click="exportCsv()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-export"></use></svg> Export</button>
<span class="admin-filter-inline" aria-label="Pagination">
<button class="btn btn-sm" type="button" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1"><i class="fas fa-chevron-left"></i></button>
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1" aria-label="Previous page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg></button>
<span style="font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted);">{{ query?.page }}/{{ totalPage || 1 }}</span>
<button class="btn btn-sm" type="button" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage"><i class="fas fa-chevron-right"></i></button>
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage" aria-label="Next page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button>
</span>
</div>
@@ -44,7 +44,7 @@
<span class="admin-active-chip" v-for="(chip, index) in chips" :key="chip?.key">
<span class="key">{{ chip?.label }}</span>
<span>{{ chip?.value }}</span>
<button type="button" @click="clearFilter(chip.key)"><i class="fas fa-times"></i></button>
<button type="button" @click="clearFilter(chip.key)" aria-label="Clear filter"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-close"></use></svg></button>
</span>
</div>
</div>
@@ -52,15 +52,15 @@
<div class="paper-table paper-table-conferences w-100" role="table" aria-label="Conferences">
<div class="paper-table-head" role="row">
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;name&#x27;}" @click="sortBy(&#x27;name&#x27;)">Conference <i class="fas" :class="sortIcon(&#x27;name&#x27;)"></i></span></div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <i class="fas" :class="sortIcon(&#x27;status&#x27;)"></i></span></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;name&#x27;}" @click="sortBy(&#x27;name&#x27;)">Conference <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;name&#x27;)" :class="{ascending: sortIcon(&#x27;name&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;status&#x27;)" :class="{ascending: sortIcon(&#x27;status&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader" class="num">Repos</div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;startDate&#x27;}" @click="sortBy(&#x27;startDate&#x27;)">Window <i class="fas" :class="sortIcon(&#x27;startDate&#x27;)"></i></span></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;startDate&#x27;}" @click="sortBy(&#x27;startDate&#x27;)">Window <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;startDate&#x27;)" :class="{ascending: sortIcon(&#x27;startDate&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div class="paper-table-row" role="row" v-for="(conference, index) in filteredConferences">
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Conf</span>
<svg class="ui-icon record-type-icon" role="img" aria-label="Conference"><use href="#project-icon-conference"></use></svg>
<div class="anon-text">
<a class="repo-name" v-text="conference?.name" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID))"></a>
<div class="anon-sub">
@@ -72,27 +72,27 @@
<span class="status-dot" :class="{&#x27;status-removed&#x27;: conference?.status == &#x27;removed&#x27; || conference?.status == &#x27;expired&#x27;, &#x27;status-ready&#x27;: conference?.status == &#x27;ready&#x27;, &#x27;status-error&#x27;: conference?.status == &#x27;error&#x27;, &#x27;status-preparing&#x27;: conference?.status == &#x27;preparing&#x27;}"></span>
<span v-text="fmt?.title(conference?.status)"></span>
</div>
<div class="cell-views num" role="cell">
<div class="cell-views num" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg>
<a title="Show repositories in this conference" v-text="fmt?.number(conference?.repositories?.length || 0)" :href="safeUrl(&quot;/admin/?conference=&quot; + (conference?.conferenceID))"></a>
</div>
<div class="cell-expires" role="cell">{{ fmt?.date(conference?.startDate) }} &ndash; {{ fmt?.date(conference?.endDate) }}</div>
<div class="cell-expires" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg> {{ fmt?.date(conference?.startDate) }} &ndash; {{ fmt?.date(conference?.endDate) }}</div>
<div class="cell-actions" role="cell">
<div class="dropdown">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Actions">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/edit&quot;)"><i class="far fa-edit"></i> Edit</a>
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/&quot;)"><i class="fa fa-eye"></i> View</a>
<a class="dropdown-item" :href="safeUrl(&quot;/admin/?conference=&quot; + (conference?.conferenceID))"><i class="fas fa-code-branch"></i> View repositories</a>
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/edit&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit</a>
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View</a>
<a class="dropdown-item" :href="safeUrl(&quot;/admin/?conference=&quot; + (conference?.conferenceID))"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> View repositories</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="#" v-show="conference?.status != &#x27;removed&#x27;" @click.prevent="removeConference(conference)"><i class="fas fa-trash-alt"></i> Remove</a>
<a class="dropdown-item text-danger" href="#" v-show="conference?.status != &#x27;removed&#x27;" @click.prevent="removeConference(conference)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="filteredConferences?.length == 0">
<i class="fas fa-inbox"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span>No conferences match the current filters.</span>
</div>
</div>
@@ -100,10 +100,10 @@
<div class="admin-toolbar" style="justify-content: space-between; border-bottom: none;">
<span style="font-size: 12px; color: var(--ink-muted);">{{ fmt?.number(total) }} results</span>
<div class="pagination-compact" v-if="totalPage &gt; 1">
<button class="btn btn-sm" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1"><i class="fas fa-chevron-left"></i> Previous</button>
<button class="btn btn-sm" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg> Previous</button>
<input type="number" class="form-control form-control-sm" min="1" style="width: 56px;" v-field="{ state: viewState, set: value =&gt; { query.page = value }, value: query?.page, form: null, options: {} }" :max="(totalPage)">
<span>of {{ totalPage }}</span>
<button class="btn btn-sm" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage">Next <i class="fas fa-chevron-right"></i></button>
<button class="btn btn-sm" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage">Next <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button>
</div>
<span class="admin-filter-inline">
<label>Per page</label>
+12 -12
View File
@@ -4,18 +4,18 @@
<header class="errors-header">
<h1 class="paper-page-title">Errors</h1>
<div class="errors-actions">
<button class="btn btn-sm" type="button" @click="exportCsv()"><i class="fas fa-file-export"></i> Export CSV</button>
<button class="btn btn-sm btn-danger" type="button" @click="clearAll()"><i class="fas fa-trash"></i> Clear all</button>
<button class="btn btn-sm" type="button" @click="exportCsv()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-export"></use></svg> Export CSV</button>
<button class="btn btn-sm btn-danger" type="button" @click="clearAll()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Clear all</button>
</div>
</header>
<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"><i class="fas fa-tasks"></i> Queues</a>
<a href="/admin/errors" class="active"><i class="fas fa-bug"></i> Errors</a>
<a href="/admin/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
<a href="/admin/repositories"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
<a href="/admin/users"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
<a href="/admin/conferences"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
<a href="/admin/queues"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
<a href="/admin/errors" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Errors</a>
</nav>
<section class="kpi-grid">
@@ -78,7 +78,7 @@
<button type="button" :class="{active: query?.bucket === &#x27;info&#x27;}" @click="setBucket(&#x27;info&#x27;)">Info</button>
</div>
<div class="search-wrap">
<i class="fas fa-search search-icon"></i>
<svg class="ui-icon search-icon" aria-hidden="true"><use href="#ui-icon-search"></use></svg>
<input type="search" class="form-control" placeholder="code:repo_not_found module:route status:&gt;=400" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { query.search = value }, value: query?.search, form: null, options: {} }">
<span class="filter-count" v-if="parsedFilterCount">{{ parsedFilterCount }} filter{{ parsedFilterCount > 1 ? 's' : '' }}</span>
</div>
@@ -101,7 +101,7 @@
<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 btn-icon" type="button" title="Refresh now" @click="refreshNow()"><i class="fas fa-sync"></i></button>
<button class="btn btn-sm btn-icon" type="button" title="Refresh now" @click="refreshNow()" aria-label="Refresh"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg></button>
</form>
<div class="admin-empty" v-if="!visible?.length">No errors captured.</div>
@@ -164,8 +164,8 @@
</div>
</div>
<div class="detail-actions">
<button class="btn btn-sm" type="button" title="Copy a curl that reproduces the request" @click="copyCurl(row)"><i class="fas fa-terminal"></i> Copy curl</button>
<button class="btn btn-sm" type="button" @click="copyJson(row)"><i class="fas fa-clipboard"></i> Copy JSON</button>
<button class="btn btn-sm" type="button" title="Copy a curl that reproduces the request" @click="copyCurl(row)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-code"></use></svg> Copy curl</button>
<button class="btn btn-sm" type="button" @click="copyJson(row)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-clipboard"></use></svg> Copy JSON</button>
<span class="copy-hint" v-if="copyHint">{{ copyHint }}</span>
</div>
</div>
+7 -7
View File
@@ -6,16 +6,16 @@
</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>
<a href="/admin/" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
<a href="/admin/repositories"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
<a href="/admin/users"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
<a href="/admin/conferences"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
<a href="/admin/queues"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
<a href="/admin/errors"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Errors</a>
</nav>
<div class="admin-empty" v-if="loading">Loading overview&hellip;</div>
<div class="alert alert-danger" style="margin:12px 0" v-if="error"><i class="fas fa-exclamation-triangle"></i> {{ error }}</div>
<div class="alert alert-danger" style="margin:12px 0" v-if="error"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg> {{ error }}</div>
<div v-if="data">
+16 -16
View File
@@ -16,17 +16,17 @@
</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>
<a href="/admin/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
<a href="/admin/repositories"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
<a href="/admin/users"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
<a href="/admin/conferences"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
<a href="/admin/queues" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
<a href="/admin/errors"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> 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" role="button" tabindex="0" :aria-pressed="selectedQueue === q.key" @keydown.enter.prevent="selectQueue(q.key)" @keydown.space.prevent="selectQueue(q.key)" 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>
@@ -88,7 +88,7 @@
</div>
<div class="q-toast-error" v-if="actionError" @click="actionError = null">
<i class="fas fa-exclamation-circle"></i> {{ actionError }}
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> {{ actionError }}
</div>
<!-- Jobs table -->
@@ -109,7 +109,7 @@
<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>
<button class="btn btn-sm btn-icon" type="button" title="Refresh now" @click="refreshNow()" aria-label="Refresh"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg></button>
</div>
<table class="q-table" v-if="filteredJobs().length &gt; 0">
@@ -131,7 +131,7 @@
<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>
<svg class="ui-icon q-chevron" :class="{&#x27;q-chevron-open&#x27;: expanded[job?.id]}" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg>
<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">
@@ -147,8 +147,8 @@
</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>
<button class="btn btn-sm btn-icon" title="Retry" v-if="job?._state == &#x27;failed&#x27;" @click="retryJob(job); $event.stopPropagation()" aria-label="Retry job"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg></button>
<button class="btn btn-sm btn-icon" title="Remove" aria-label="Remove job" @click="removeJob(job); $event.stopPropagation()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg></button>
</td>
</tr>
<tr class="q-detail-row" v-if="expanded[job?.id]">
@@ -205,9 +205,9 @@
<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>
<button class="btn btn-sm" v-if="job?._state == &#x27;failed&#x27;" @click="retryJob(job)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Retry</button>
<button class="btn btn-sm" @click="removeJob(job)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove</button>
<a class="btn btn-sm" target="_blank" :href="safeUrl(&quot;/r/&quot; + (job?.id))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg> View repo</a>
</div>
</div>
</td>
@@ -216,7 +216,7 @@
</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>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check-circle"></use></svg>
<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>
+38 -38
View File
@@ -3,26 +3,26 @@
<h1 class="paper-page-title">Repositories</h1>
<nav class="admin-nav">
<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>
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
<a href="/admin/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
<a href="/admin/repositories" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
<a href="/admin/users"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
<a href="/admin/conferences"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
<a href="/admin/queues"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
<a href="/admin/errors"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Errors</a>
</nav>
<div class="admin-summary">
<span class="summary-total">{{ total >= 0 ? fmt.number(total) : '…' }}</span>
<span class="summary-meta">{{ fmt?.humanFileSize(totalSize) }} on disk</span>
<span class="summary-pill ok" title="Toggle ready filter" :class="{active: query?.ready}" @click="query.ready = !query.ready; query.page = 1">Ready <span class="count">{{ fmt?.number(statusCountFor('ready')) }}</span></span>
<span class="summary-pill warn" title="Toggle preparing filter" :class="{active: query?.preparing}" @click="query.preparing = !query.preparing; query.page = 1">Preparing <span class="count">{{ fmt?.number(statusCountFor('preparing') + statusCountFor('download')) }}</span></span>
<span class="summary-pill error" title="Toggle errored filter" :class="{active: query?.error}" @click="query.error = !query.error; query.page = 1">Errored <span class="count">{{ fmt?.number(statusCountFor('error')) }}</span></span>
<span class="summary-pill" title="Toggle expired filter" :class="{active: query?.expired}" @click="query.expired = !query.expired; query.page = 1">Expired <span class="count">{{ fmt?.number(statusCountFor('expired') + statusCountFor('expiring')) }}</span></span>
<span class="summary-pill" title="Toggle removed filter" :class="{active: query?.removed}" @click="query.removed = !query.removed; query.page = 1">Removed <span class="count">{{ fmt?.number(statusCountFor('removed') + statusCountFor('removing')) }}</span></span>
<button type="button" class="summary-pill ok" title="Toggle ready filter" :class="{active: query?.ready}" @click="query.ready = !query.ready; query.page = 1" :aria-pressed="!!(query?.ready)">Ready <span class="count">{{ fmt?.number(statusCountFor('ready')) }}</span></button>
<button type="button" class="summary-pill warn" title="Toggle preparing filter" :class="{active: query?.preparing}" @click="query.preparing = !query.preparing; query.page = 1" :aria-pressed="!!(query?.preparing)">Preparing <span class="count">{{ fmt?.number(statusCountFor('preparing') + statusCountFor('download')) }}</span></button>
<button type="button" class="summary-pill error" title="Toggle errored filter" :class="{active: query?.error}" @click="query.error = !query.error; query.page = 1" :aria-pressed="!!(query?.error)">Errored <span class="count">{{ fmt?.number(statusCountFor('error')) }}</span></button>
<button type="button" class="summary-pill" title="Toggle expired filter" :class="{active: query?.expired}" @click="query.expired = !query.expired; query.page = 1" :aria-pressed="!!(query?.expired)">Expired <span class="count">{{ fmt?.number(statusCountFor('expired') + statusCountFor('expiring')) }}</span></button>
<button type="button" class="summary-pill" title="Toggle removed filter" :class="{active: query?.removed}" @click="query.removed = !query.removed; query.page = 1" :aria-pressed="!!(query?.removed)">Removed <span class="count">{{ fmt?.number(statusCountFor('removed') + statusCountFor('removing')) }}</span></button>
</div>
<div class="alert alert-danger" style="margin: 8px 0;" v-if="fetchError">
<i class="fas fa-exclamation-triangle"></i> {{ fetchError }}
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg> {{ fetchError }}
</div>
<form class="w-100 admin-filter-toolbar" aria-label="Repositories" accept-charset="UTF-8" v-form="viewState">
@@ -35,11 +35,11 @@
<span class="admin-filter-inline"><label>Owner</label><input type="text" class="form-control form-control-sm" placeholder="username" v-field="{ state: viewState, set: value =&gt; { query.owner = value }, value: query?.owner, form: null, options: {} }"></span>
<span class="admin-filter-inline"><label>Conference</label><input type="text" class="form-control form-control-sm" placeholder="ID" v-field="{ state: viewState, set: value =&gt; { query.conference = value }, value: query?.conference, form: null, options: {} }"></span>
<span class="admin-filter-spacer"></span>
<button class="btn btn-sm" type="button" title="Export current view to CSV" @click="exportCsv()"><i class="fas fa-file-csv"></i> Export</button>
<button class="btn btn-sm" type="button" title="Export current view to CSV" @click="exportCsv()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-export"></use></svg> Export</button>
<span class="admin-filter-inline" aria-label="Pagination">
<button class="btn btn-sm" type="button" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1"><i class="fas fa-chevron-left"></i></button>
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1" aria-label="Previous page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg></button>
<span style="font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted);">{{ query?.page }}/{{ totalPage || 1 }}</span>
<button class="btn btn-sm" type="button" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage"><i class="fas fa-chevron-right"></i></button>
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage" aria-label="Next page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button>
</span>
</div>
@@ -49,7 +49,7 @@
<span class="admin-active-chip" v-for="(chip, index) in chips" :key="chip?.key">
<span class="key">{{ chip?.label }}</span>
<span>{{ chip?.value }}</span>
<button type="button" aria-label="Remove filter" @click="clearFilter(chip.key)"><i class="fas fa-times"></i></button>
<button type="button" aria-label="Remove filter" @click="clearFilter(chip.key)"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-close"></use></svg></button>
</span>
</div>
</div>
@@ -57,8 +57,8 @@
<div class="bulk-bar" v-if="selectedCount() &gt; 0">
<span><strong>{{ selectedCount() }}</strong> selected</span>
<button class="btn btn-sm" type="button" @click="bulkRefresh()"><i class="fas fa-sync"></i> Force refresh</button>
<button class="btn btn-sm text-danger" type="button" @click="bulkRemoveCache()"><i class="fas fa-broom"></i> Remove cache</button>
<button class="btn btn-sm" type="button" @click="bulkRefresh()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Force refresh</button>
<button class="btn btn-sm text-danger" type="button" @click="bulkRemoveCache()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-broom"></use></svg> Remove cache</button>
<button class="btn btn-sm" type="button" @click="clearSelection()">Clear</button>
</div>
@@ -67,10 +67,10 @@
<div role="columnheader" style="width: 28px;">
<input type="checkbox" aria-label="Select all on page" @click="selectAllOnPage()" :checked="allSelected">
</div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;source.repositoryName&#x27;}" @click="sortBy(&#x27;source.repositoryName&#x27;)">Repository <i class="fas" :class="sortIcon(&#x27;source.repositoryName&#x27;)"></i></span></div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <i class="fas" :class="sortIcon(&#x27;status&#x27;)"></i></span></div>
<div role="columnheader" class="num"><span class="sortable" :class="{active: query?.sort == &#x27;pageView&#x27;}" @click="sortBy(&#x27;pageView&#x27;)">Views <i class="fas" :class="sortIcon(&#x27;pageView&#x27;)"></i></span></div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;anonymizeDate&#x27;}" @click="sortBy(&#x27;anonymizeDate&#x27;)">Anonymized <i class="fas" :class="sortIcon(&#x27;anonymizeDate&#x27;)"></i></span></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;source.repositoryName&#x27;}" @click="sortBy(&#x27;source.repositoryName&#x27;)">Repository <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;source.repositoryName&#x27;)" :class="{ascending: sortIcon(&#x27;source.repositoryName&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;status&#x27;)" :class="{ascending: sortIcon(&#x27;status&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader" class="num"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;pageView&#x27;}" @click="sortBy(&#x27;pageView&#x27;)">Views <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;pageView&#x27;)" :class="{ascending: sortIcon(&#x27;pageView&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;anonymizeDate&#x27;}" @click="sortBy(&#x27;anonymizeDate&#x27;)">Anonymized <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;anonymizeDate&#x27;)" :class="{ascending: sortIcon(&#x27;anonymizeDate&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div class="paper-table-row" role="row" v-for="(repo, index) in filteredRepositories" :class="{&#x27;repo-inactive&#x27;: repo?.status == &#x27;expired&#x27; || repo?.status == &#x27;removed&#x27;, &#x27;repo-error&#x27;: repo?.status == &#x27;error&#x27;, &#x27;row-selected&#x27;: selected[repo?.repoId]}">
@@ -78,11 +78,11 @@
<input type="checkbox" aria-label="Select repository" v-field="{ state: viewState, set: value =&gt; { selected[repo.repoId] = value }, value: selected[repo?.repoId], form: null, options: {} }">
</div>
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<svg class="ui-icon record-type-icon" role="img" aria-label="Repository"><use href="#project-type-repo"></use></svg>
<div class="anon-text">
<a class="repo-name" target="_blank" v-text="repo?.repoId" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId))"></a>
<div class="anon-sub">
<a v-text="repo?.source?.repositoryName" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.repositoryName) + &quot;/&quot;)"></a><span v-if="repo?.options?.update">&nbsp;&middot;&nbsp;<a v-text="repo?.source?.branch" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update">&nbsp;&middot;&nbsp;@<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.commit))"></a></span><span v-if="repo?.conference">&nbsp;&middot;&nbsp;<i class="fas fa-chalkboard-teacher"></i> {{ repo?.conference }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.number(repo?.options?.terms?.length) }} terms</span>
<a v-text="repo?.source?.repositoryName" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.repositoryName) + &quot;/&quot;)"></a><span v-if="repo?.options?.update">&nbsp;&middot;&nbsp;<a v-text="repo?.source?.branch" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update">&nbsp;&middot;&nbsp;@<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.commit))"></a></span><span v-if="repo?.conference">&nbsp;&middot;&nbsp;<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> {{ repo?.conference }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.number(repo?.options?.terms?.length) }} terms</span>
</div>
</div>
</div>
@@ -93,31 +93,31 @@
</span>
<span class="status-sub" v-if="repo?.statusMessage" v-text="fmt?.statusMsg(repo?.statusMessage)" :title="(repo?.statusMessage)"></span>
</div>
<div class="cell-views num" role="cell" v-text="fmt?.number(repo?.pageView || 0)"></div>
<div class="cell-expires" role="cell" v-text="fmt?.humanTime(repo?.anonymizeDate)"></div>
<div class="cell-views num" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> <span v-text="fmt?.number(repo?.pageView || 0)"></span></div>
<div class="cell-expires" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg> <span v-text="fmt?.humanTime(repo?.anonymizeDate)"></span></div>
<div class="cell-actions" role="cell">
<div class="dropdown">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Actions">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/anonymize/&quot; + (repo?.repoId))"><i class="far fa-edit"></i> Edit</a>
<a class="dropdown-item" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId) + &quot;/&quot;)"><i class="fa fa-eye"></i> View repo</a>
<a class="dropdown-item" target="_self" v-if="repo?.options?.page &amp;&amp; repo?.status == &#x27;ready&#x27;" :href="safeUrl(&quot;/w/&quot; + (repo?.repoId) + &quot;/&quot;)"><i class="fas fa-globe"></i> View page</a>
<a class="dropdown-item" :href="safeUrl(&quot;/anonymize/&quot; + (repo?.repoId))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit</a>
<a class="dropdown-item" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId) + &quot;/&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View repo</a>
<a class="dropdown-item" target="_self" v-if="repo?.options?.page &amp;&amp; repo?.status == &#x27;ready&#x27;" :href="safeUrl(&quot;/w/&quot; + (repo?.repoId) + &quot;/&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-globe"></use></svg> View page</a>
<a class="dropdown-item" href="#" @click.prevent="fetchGithubInfo(repo)"><i class="fab fa-github"></i> Live GitHub info</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;ready&#x27; || repo?.status == &#x27;error&#x27;" @click.prevent="updateRepository(repo)"><i class="fas fa-sync"></i> Force update</a>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;removed&#x27;" @click.prevent="updateRepository(repo)"><i class="fas fa-check-circle"></i> Enable</a>
<a class="dropdown-item" href="#" v-show="repo?.statusMessage" @click.prevent="showStatusMessage(repo)"><i class="fas fa-exclamation-triangle"></i> View status message</a>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;ready&#x27; || repo?.status == &#x27;error&#x27;" @click.prevent="updateRepository(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Force update</a>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;removed&#x27;" @click.prevent="updateRepository(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check-circle"></use></svg> Enable</a>
<a class="dropdown-item" href="#" v-show="repo?.statusMessage" @click.prevent="showStatusMessage(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg> View status message</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" @click.prevent="removeCache(repo)"><i class="fas fa-broom"></i> Remove cache</a>
<a class="dropdown-item text-danger" href="#" v-show="repo?.status == &#x27;ready&#x27;" @click.prevent="removeRepository(repo)"><i class="fas fa-trash-alt"></i> Remove</a>
<a class="dropdown-item" href="#" @click.prevent="removeCache(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-broom"></use></svg> Remove cache</a>
<a class="dropdown-item text-danger" href="#" v-show="repo?.status == &#x27;ready&#x27;" @click.prevent="removeRepository(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="filteredRepositories?.length == 0">
<i class="fas fa-inbox"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span>No repositories match the current filters.</span>
</div>
</div>
@@ -126,12 +126,12 @@
<span style="font-size: 12px; color: var(--ink-muted);">{{ fmt?.number(total) }} results</span>
<div class="pagination-compact" v-if="totalPage &gt; 1">
<button class="btn btn-sm" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1">
<i class="fas fa-chevron-left"></i> Previous
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg> Previous
</button>
<input type="number" class="form-control form-control-sm" min="1" style="width: 56px;" aria-label="Page" v-field="{ state: viewState, set: value =&gt; { query.page = value }, value: query?.page, form: null, options: {} }" :max="(totalPage)">
<span>of {{ totalPage }}</span>
<button class="btn btn-sm" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage">
Next <i class="fas fa-chevron-right"></i>
Next <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg>
</button>
</div>
<span class="admin-filter-inline">
+44 -44
View File
@@ -3,31 +3,31 @@
<h1 class="paper-page-title">{{ userInfo?.username || 'User' }}</h1>
<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" 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>
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
<a href="/admin/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
<a href="/admin/repositories"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
<a href="/admin/users" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
<a href="/admin/conferences"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
<a href="/admin/queues"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
<a href="/admin/errors"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Errors</a>
</nav>
<div class="user-detail-card" v-if="userInfo">
<div class="user-header">
<img width="56" height="56" v-if="userInfo?.photo" :src="safeUrl((userInfo?.photo))">
<div>
<h1>
<h2>
{{ userInfo?.username }}
<span class="status-dot-wrap">
<span class="status-dot" :class="{&#x27;status-ready&#x27;: userInfo?.status == &#x27;active&#x27;, &#x27;status-removed&#x27;: userInfo?.status != &#x27;active&#x27;}"></span>
<span v-text="fmt?.title(userInfo?.status)"></span>
</span>
<span class="type-badge type-repo" v-if="userInfo?.isAdmin">Admin</span>
</h1>
</h2>
<div class="user-actions" style="margin-top: 4px;">
<button class="btn btn-sm text-danger" v-if="userInfo?.status !== &#x27;banned&#x27;" @click="banUser()"><i class="fas fa-ban"></i> Ban</button>
<button class="btn btn-sm" v-if="userInfo?.status === &#x27;banned&#x27; || userInfo?.status === &#x27;removed&#x27;" @click="activateUser()"><i class="fas fa-check-circle"></i> Activate</button>
<button class="btn btn-sm" v-if="!userInfo?.isAdmin" @click="promoteUser()"><i class="fas fa-user-shield"></i> Promote to admin</button>
<button class="btn btn-sm text-danger" v-if="userInfo?.isAdmin" @click="demoteUser()"><i class="fas fa-user-minus"></i> Remove admin</button>
<button class="btn btn-sm text-danger" v-if="userInfo?.status !== &#x27;banned&#x27;" @click="banUser()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-ban"></use></svg> Ban</button>
<button class="btn btn-sm" v-if="userInfo?.status === &#x27;banned&#x27; || userInfo?.status === &#x27;removed&#x27;" @click="activateUser()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check-circle"></use></svg> Activate</button>
<button class="btn btn-sm" v-if="!userInfo?.isAdmin" @click="promoteUser()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user-shield"></use></svg> Promote to admin</button>
<button class="btn btn-sm text-danger" v-if="userInfo?.isAdmin" @click="demoteUser()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user-minus"></use></svg> Remove admin</button>
</div>
</div>
</div>
@@ -65,7 +65,7 @@
{{ showRepos ? 'Hide' : 'Show' }}
</button>
<button class="btn btn-sm ml-1" @click="getGitHubRepositories()">
<i class="fas fa-sync"></i> Refresh
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Refresh
</button>
</div>
</div>
@@ -75,13 +75,13 @@
<div class="paper-table w-100" style="margin-top: 10px;">
<div class="paper-table-row" style="grid-template-columns: 1fr 160px;" v-for="(repo, index) in userInfo?.repositories">
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<svg class="ui-icon record-type-icon" role="img" aria-label="Repository"><use href="#project-type-repo"></use></svg>
<div class="anon-text">
<span class="repo-name" v-text="repo?.name"></span>
</div>
</div>
<div class="cell-expires" role="cell">
<i class="fas fa-database"></i> {{ fmt?.humanFileSize(repo?.size) }}
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-database"></use></svg> {{ fmt?.humanFileSize(repo?.size) }}
</div>
</div>
</div>
@@ -89,7 +89,7 @@
</div>
<div class="admin-section-header" v-if="userInfo &amp;&amp; userInfo?.isAdmin &amp;&amp; user &amp;&amp; user?.username == userInfo?.username">
<h2><i class="fas fa-key"></i> API tokens</h2>
<h2><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-key"></use></svg> API tokens</h2>
<span class="section-count">{{ tokens?.length }}</span>
</div>
@@ -98,7 +98,7 @@
<form class="d-flex" style="gap: 8px; margin-bottom: 12px;" v-form="viewState" @submit.prevent="submitForm($event, () =&gt; { createToken() })">
<input type="text" class="form-control" placeholder="Token name (e.g. dev-laptop)" required v-field="{ state: viewState, set: value =&gt; { tokenForm.name = value }, value: tokenForm?.name, form: null, options: {} }">
<button type="submit" class="btn btn-primary"><i class="fas fa-plus"></i> Generate</button>
<button type="submit" class="btn btn-primary"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-plus"></use></svg> Generate</button>
</form>
<div class="alert alert-warning" role="alert" v-if="tokenForm?.plaintext">
@@ -119,28 +119,28 @@
<div role="cell" v-text="fmt?.humanTime(t?.createdAt)"></div>
<div role="cell"><span v-if="t?.lastUsedAt">{{ fmt?.humanTime(t?.lastUsedAt) }}</span><span class="text-muted" v-if="!t?.lastUsedAt">never</span></div>
<div role="cell">
<button class="btn btn-sm text-danger" title="Revoke" @click="revokeToken(t)"><i class="fas fa-trash-alt"></i></button>
<button class="btn btn-sm text-danger btn-icon" title="Revoke" aria-label="Revoke token" @click="revokeToken(t)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg></button>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="!tokens?.length">
<i class="fas fa-inbox"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span>No tokens yet.</span>
</div>
</div>
<div class="admin-section-header">
<h2><i class="fas fa-code-branch"></i> Anonymized repositories</h2>
<h2><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Anonymized repositories</h2>
<span class="section-count">{{ repositories?.length }}</span>
</div>
<div class="admin-summary">
<span class="summary-total">{{ fmt?.number(repositories?.length) }}</span>
<span class="summary-pill ok" title="Toggle ready filter" :class="{active: filters?.status?.ready === false}" @click="filters.status.ready = !filters.status.ready">Ready <span class="count">{{ fmt?.number(statusCountFor('ready')) }}</span></span>
<span class="summary-pill warn" title="Toggle preparing filter" :class="{active: filters?.status?.preparing === false}" @click="filters.status.preparing = !filters.status.preparing">Preparing <span class="count">{{ fmt?.number(statusCountFor('preparing')) }}</span></span>
<span class="summary-pill error" title="Toggle errored filter" :class="{active: filters?.status?.error === false}" @click="filters.status.error = !filters.status.error">Errored <span class="count">{{ fmt?.number(statusCountFor('error')) }}</span></span>
<span class="summary-pill" title="Toggle expired filter" :class="{active: filters?.status?.expired === false}" @click="filters.status.expired = !filters.status.expired">Expired <span class="count">{{ fmt?.number(statusCountFor('expired')) }}</span></span>
<span class="summary-pill" title="Toggle removed filter" :class="{active: filters?.status?.removed === false}" @click="filters.status.removed = !filters.status.removed">Removed <span class="count">{{ fmt?.number(statusCountFor('removed')) }}</span></span>
<button type="button" class="summary-pill ok" title="Toggle ready filter" :class="{active: filters?.status?.ready === false}" @click="filters.status.ready = !filters.status.ready" :aria-pressed="!!(filters?.status?.ready === false)">Ready <span class="count">{{ fmt?.number(statusCountFor('ready')) }}</span></button>
<button type="button" class="summary-pill warn" title="Toggle preparing filter" :class="{active: filters?.status?.preparing === false}" @click="filters.status.preparing = !filters.status.preparing" :aria-pressed="!!(filters?.status?.preparing === false)">Preparing <span class="count">{{ fmt?.number(statusCountFor('preparing')) }}</span></button>
<button type="button" class="summary-pill error" title="Toggle errored filter" :class="{active: filters?.status?.error === false}" @click="filters.status.error = !filters.status.error" :aria-pressed="!!(filters?.status?.error === false)">Errored <span class="count">{{ fmt?.number(statusCountFor('error')) }}</span></button>
<button type="button" class="summary-pill" title="Toggle expired filter" :class="{active: filters?.status?.expired === false}" @click="filters.status.expired = !filters.status.expired" :aria-pressed="!!(filters?.status?.expired === false)">Expired <span class="count">{{ fmt?.number(statusCountFor('expired')) }}</span></button>
<button type="button" class="summary-pill" title="Toggle removed filter" :class="{active: filters?.status?.removed === false}" @click="filters.status.removed = !filters.status.removed" :aria-pressed="!!(filters?.status?.removed === false)">Removed <span class="count">{{ fmt?.number(statusCountFor('removed')) }}</span></button>
</div>
<form class="w-100 admin-filter-toolbar" aria-label="Repositories" accept-charset="UTF-8" v-form="viewState">
@@ -149,14 +149,14 @@
<input type="search" class="form-control" aria-label="Search repositories" placeholder="Search repoId, source repo, error message…" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { search = value }, value: search, form: null, options: {} }">
</div>
<span class="admin-filter-spacer"></span>
<button class="btn btn-sm" type="button" title="Export current view to CSV" @click="exportCsv()"><i class="fas fa-file-csv"></i> Export</button>
<button class="btn btn-sm" type="button" title="Export current view to CSV" @click="exportCsv()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-export"></use></svg> Export</button>
</div>
</form>
<div class="bulk-bar" v-if="selectedCount() &gt; 0">
<span><strong>{{ selectedCount() }}</strong> selected</span>
<button class="btn btn-sm" type="button" @click="bulkRefresh()"><i class="fas fa-sync"></i> Force refresh</button>
<button class="btn btn-sm text-danger" type="button" @click="bulkRemoveCache()"><i class="fas fa-broom"></i> Remove cache</button>
<button class="btn btn-sm" type="button" @click="bulkRefresh()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Force refresh</button>
<button class="btn btn-sm text-danger" type="button" @click="bulkRemoveCache()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-broom"></use></svg> Remove cache</button>
<button class="btn btn-sm" type="button" @click="clearSelection()">Clear</button>
</div>
@@ -165,10 +165,10 @@
<div role="columnheader" style="width: 28px;">
<input type="checkbox" aria-label="Select all on page" @click="selectAllOnPage()" :checked="allSelected">
</div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;source.repositoryName&#x27;}" @click="sortBy(&#x27;source.repositoryName&#x27;)">Repository <i class="fas" :class="sortIcon(&#x27;source.repositoryName&#x27;)"></i></span></div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <i class="fas" :class="sortIcon(&#x27;status&#x27;)"></i></span></div>
<div role="columnheader" class="num"><span class="sortable" :class="{active: query?.sort == &#x27;pageView&#x27;}" @click="sortBy(&#x27;pageView&#x27;)">Views <i class="fas" :class="sortIcon(&#x27;pageView&#x27;)"></i></span></div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;anonymizeDate&#x27;}" @click="sortBy(&#x27;anonymizeDate&#x27;)">Anonymized <i class="fas" :class="sortIcon(&#x27;anonymizeDate&#x27;)"></i></span></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;source.repositoryName&#x27;}" @click="sortBy(&#x27;source.repositoryName&#x27;)">Repository <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;source.repositoryName&#x27;)" :class="{ascending: sortIcon(&#x27;source.repositoryName&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;status&#x27;)" :class="{ascending: sortIcon(&#x27;status&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader" class="num"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;pageView&#x27;}" @click="sortBy(&#x27;pageView&#x27;)">Views <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;pageView&#x27;)" :class="{ascending: sortIcon(&#x27;pageView&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;anonymizeDate&#x27;}" @click="sortBy(&#x27;anonymizeDate&#x27;)">Anonymized <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;anonymizeDate&#x27;)" :class="{ascending: sortIcon(&#x27;anonymizeDate&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div class="paper-table-row" role="row" v-for="(repo, index) in filteredRepositories" :class="{&#x27;repo-inactive&#x27;: repo?.status == &#x27;expired&#x27; || repo?.status == &#x27;removed&#x27;, &#x27;repo-error&#x27;: repo?.status == &#x27;error&#x27;, &#x27;row-selected&#x27;: selected[repo?.repoId]}">
@@ -176,11 +176,11 @@
<input type="checkbox" aria-label="Select repository" v-field="{ state: viewState, set: value =&gt; { selected[repo.repoId] = value }, value: selected[repo?.repoId], form: null, options: {} }">
</div>
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<svg class="ui-icon record-type-icon" role="img" aria-label="Repository"><use href="#project-type-repo"></use></svg>
<div class="anon-text">
<a class="repo-name" target="_blank" v-text="repo?.repoId" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId))"></a>
<div class="anon-sub">
<a v-text="repo?.source?.fullName" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/&quot;)"></a><span v-if="repo?.options?.update">&nbsp;&middot;&nbsp;<a v-text="repo?.source?.branch" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update">&nbsp;&middot;&nbsp;@<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.commit))"></a></span><span v-if="repo?.conference">&nbsp;&middot;&nbsp;<i class="fas fa-chalkboard-teacher"></i> {{ repo?.conference }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.number(repo?.options?.terms?.length) }} terms</span>
<a v-text="repo?.source?.fullName" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/&quot;)"></a><span v-if="repo?.options?.update">&nbsp;&middot;&nbsp;<a v-text="repo?.source?.branch" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.branch))"></a></span><span v-if="!repo?.options?.update">&nbsp;&middot;&nbsp;@<a v-text="repo?.source?.commit?.substring(0, 8)" :href="safeUrl(&quot;https://github.com/&quot; + (repo?.source?.fullName) + &quot;/tree/&quot; + (repo?.source?.commit))"></a></span><span v-if="repo?.conference">&nbsp;&middot;&nbsp;<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> {{ repo?.conference }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.humanFileSize(repo?.size?.storage) }}</span><span>&nbsp;&middot;&nbsp;{{ fmt?.number(repo?.options?.terms?.length) }} terms</span>
</div>
</div>
</div>
@@ -196,26 +196,26 @@
<div class="cell-actions" role="cell">
<div class="dropdown">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Actions">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/anonymize/&quot; + (repo?.repoId))"><i class="far fa-edit"></i> Edit</a>
<a class="dropdown-item" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId) + &quot;/&quot;)"><i class="fa fa-eye"></i> View repo</a>
<a class="dropdown-item" target="_self" v-if="repo?.options?.page &amp;&amp; repo?.status == &#x27;ready&#x27;" :href="safeUrl(&quot;/w/&quot; + (repo?.repoId) + &quot;/&quot;)"><i class="fas fa-globe"></i> View page</a>
<a class="dropdown-item" :href="safeUrl(&quot;/anonymize/&quot; + (repo?.repoId))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit</a>
<a class="dropdown-item" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId) + &quot;/&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View repo</a>
<a class="dropdown-item" target="_self" v-if="repo?.options?.page &amp;&amp; repo?.status == &#x27;ready&#x27;" :href="safeUrl(&quot;/w/&quot; + (repo?.repoId) + &quot;/&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-globe"></use></svg> View page</a>
<a class="dropdown-item" href="#" @click.prevent="fetchGithubInfo(repo)"><i class="fab fa-github"></i> Live GitHub info</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;ready&#x27; || repo?.status == &#x27;error&#x27;" @click.prevent="updateRepository(repo)"><i class="fas fa-sync"></i> Force update</a>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;removed&#x27;" @click.prevent="updateRepository(repo)"><i class="fas fa-check-circle"></i> Enable</a>
<a class="dropdown-item" href="#" v-show="repo?.statusMessage" @click.prevent="showStatusMessage(repo)"><i class="fas fa-exclamation-triangle"></i> View status message</a>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;ready&#x27; || repo?.status == &#x27;error&#x27;" @click.prevent="updateRepository(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Force update</a>
<a class="dropdown-item" href="#" v-show="repo?.status == &#x27;removed&#x27;" @click.prevent="updateRepository(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check-circle"></use></svg> Enable</a>
<a class="dropdown-item" href="#" v-show="repo?.statusMessage" @click.prevent="showStatusMessage(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg> View status message</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" @click.prevent="removeCache(repo)"><i class="fas fa-broom"></i> Remove cache</a>
<a class="dropdown-item text-danger" href="#" v-show="repo?.status == &#x27;ready&#x27;" @click.prevent="removeRepository(repo)"><i class="fas fa-trash-alt"></i> Remove</a>
<a class="dropdown-item" href="#" @click.prevent="removeCache(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-broom"></use></svg> Remove cache</a>
<a class="dropdown-item text-danger" href="#" v-show="repo?.status == &#x27;ready&#x27;" @click.prevent="removeRepository(repo)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="filteredRepositories?.length == 0">
<i class="fas fa-inbox"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span>No repositories match the current filters.</span>
</div>
</div>
+26 -26
View File
@@ -3,23 +3,23 @@
<h1 class="paper-page-title">Users</h1>
<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" 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>
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
<a href="/admin/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chart"></use></svg> Overview</a>
<a href="/admin/repositories"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Repositories</a>
<a href="/admin/users" class="active"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-users"></use></svg> Users</a>
<a href="/admin/conferences"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conferences</a>
<a href="/admin/queues"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-list"></use></svg> Queues</a>
<a href="/admin/errors"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Errors</a>
</nav>
<div class="admin-summary">
<span class="summary-total">{{ total >= 0 ? fmt.number(total) : '…' }}</span>
<span class="summary-pill ok" :class="{active: query?.status == &#x27;active&#x27;}" @click="query.status = query.status == &#x27;active&#x27; ? &#x27;&#x27; : &#x27;active&#x27;; query.page = 1">Active <span class="count">{{ fmt?.number(statusCountFor('active')) }}</span></span>
<span class="summary-pill error" :class="{active: query?.status == &#x27;banned&#x27;}" @click="query.status = query.status == &#x27;banned&#x27; ? &#x27;&#x27; : &#x27;banned&#x27;; query.page = 1">Banned <span class="count">{{ fmt?.number(statusCountFor('banned')) }}</span></span>
<span class="summary-pill" :class="{active: query?.status == &#x27;removed&#x27;}" @click="query.status = query.status == &#x27;removed&#x27; ? &#x27;&#x27; : &#x27;removed&#x27;; query.page = 1">Removed <span class="count">{{ fmt?.number(statusCountFor('removed')) }}</span></span>
<button type="button" class="summary-pill ok" :class="{active: query?.status == &#x27;active&#x27;}" @click="query.status = query.status == &#x27;active&#x27; ? &#x27;&#x27; : &#x27;active&#x27;; query.page = 1" :aria-pressed="!!(query?.status == &#x27;active&#x27;)">Active <span class="count">{{ fmt?.number(statusCountFor('active')) }}</span></button>
<button type="button" class="summary-pill error" :class="{active: query?.status == &#x27;banned&#x27;}" @click="query.status = query.status == &#x27;banned&#x27; ? &#x27;&#x27; : &#x27;banned&#x27;; query.page = 1" :aria-pressed="!!(query?.status == &#x27;banned&#x27;)">Banned <span class="count">{{ fmt?.number(statusCountFor('banned')) }}</span></button>
<button type="button" class="summary-pill" :class="{active: query?.status == &#x27;removed&#x27;}" @click="query.status = query.status == &#x27;removed&#x27; ? &#x27;&#x27; : &#x27;removed&#x27;; query.page = 1" :aria-pressed="!!(query?.status == &#x27;removed&#x27;)">Removed <span class="count">{{ fmt?.number(statusCountFor('removed')) }}</span></button>
</div>
<div class="alert alert-danger" style="margin: 8px 0;" v-if="fetchError">
<i class="fas fa-exclamation-triangle"></i> {{ fetchError }}
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg> {{ fetchError }}
</div>
<form class="w-100 admin-filter-toolbar" aria-label="Users" accept-charset="UTF-8" v-form="viewState">
@@ -32,11 +32,11 @@
<select class="form-control form-control-sm" v-field="{ state: viewState, set: value =&gt; { query.role = value }, value: query?.role, form: null, options: {} }"><option value="">Any</option><option value="admin">Admin</option></select>
</span>
<span class="admin-filter-spacer"></span>
<button class="btn btn-sm" type="button" @click="exportCsv()"><i class="fas fa-file-csv"></i> Export</button>
<button class="btn btn-sm" type="button" @click="exportCsv()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-export"></use></svg> Export</button>
<span class="admin-filter-inline" aria-label="Pagination">
<button class="btn btn-sm" type="button" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1"><i class="fas fa-chevron-left"></i></button>
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1" aria-label="Previous page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg></button>
<span style="font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted);">{{ query?.page }}/{{ totalPage || 1 }}</span>
<button class="btn btn-sm" type="button" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage"><i class="fas fa-chevron-right"></i></button>
<button class="btn btn-sm btn-icon" type="button" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage" aria-label="Next page"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button>
</span>
</div>
@@ -45,7 +45,7 @@
<span class="admin-active-chip" v-for="(chip, index) in chips" :key="chip?.key">
<span class="key">{{ chip?.label }}</span>
<span>{{ chip?.value }}</span>
<button type="button" @click="clearFilter(chip.key)"><i class="fas fa-times"></i></button>
<button type="button" @click="clearFilter(chip.key)" aria-label="Clear filter"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-close"></use></svg></button>
</span>
</div>
</div>
@@ -53,7 +53,7 @@
<div class="bulk-bar" v-if="selectedCount() &gt; 0">
<span><strong>{{ selectedCount() }}</strong> selected</span>
<button class="btn btn-sm text-danger" type="button" @click="bulkBan()"><i class="fas fa-ban"></i> Ban</button>
<button class="btn btn-sm text-danger" type="button" @click="bulkBan()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-ban"></use></svg> Ban</button>
<button class="btn btn-sm" type="button" @click="selected = {}; allSelected = false">Clear</button>
</div>
@@ -62,9 +62,9 @@
<div role="columnheader" style="width: 28px;">
<input type="checkbox" aria-label="Select all on page" @click="selectAllOnPage()" :checked="allSelected">
</div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;username&#x27;}" @click="sortBy(&#x27;username&#x27;)">User <i class="fas" :class="sortIcon(&#x27;username&#x27;)"></i></span></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;username&#x27;}" @click="sortBy(&#x27;username&#x27;)">User <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;username&#x27;)" :class="{ascending: sortIcon(&#x27;username&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader" class="num">Repos</div>
<div role="columnheader"><span class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <i class="fas" :class="sortIcon(&#x27;status&#x27;)"></i></span></div>
<div role="columnheader"><button type="button" class="sortable" :class="{active: query?.sort == &#x27;status&#x27;}" @click="sortBy(&#x27;status&#x27;)">Status <svg class="ui-icon sort-direction" aria-hidden="true" v-if="sortIcon(&#x27;status&#x27;)" :class="{ascending: sortIcon(&#x27;status&#x27;) === 'fa-arrow-up'}"><use href="#project-icon-arrow"></use></svg></button></div>
<div role="columnheader">Role</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
@@ -81,7 +81,7 @@
</div>
</div>
</div>
<div class="cell-views num" role="cell">
<div class="cell-views num" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg>
<a title="Show this user&#x27;s repositories" v-text="fmt?.number((u?.repoCount || 0))" :href="safeUrl(&quot;/admin/?owner=&quot; + (u?.username))"></a>
</div>
<div class="cell-status" role="cell">
@@ -95,20 +95,20 @@
<div class="cell-actions" role="cell">
<div class="dropdown">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="Actions">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/admin/users/&quot; + (u?.username))"><i class="far fa-eye"></i> View details</a>
<a class="dropdown-item" :href="safeUrl(&quot;/admin/?owner=&quot; + (u?.username))"><i class="fas fa-code-branch"></i> View repositories</a>
<a class="dropdown-item" :href="safeUrl(&quot;/admin/users/&quot; + (u?.username))"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View details</a>
<a class="dropdown-item" :href="safeUrl(&quot;/admin/?owner=&quot; + (u?.username))"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> View repositories</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="#" v-show="u?.status == &#x27;active&#x27;" @click.prevent="banUser(u)"><i class="fas fa-ban"></i> Ban</a>
<a class="dropdown-item" href="#" v-show="u?.status == &#x27;removed&#x27; || u?.status == &#x27;banned&#x27;" @click.prevent="activateUser(u)"><i class="fas fa-check-circle"></i> Activate</a>
<a class="dropdown-item text-danger" href="#" v-show="u?.status == &#x27;active&#x27;" @click.prevent="banUser(u)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-ban"></use></svg> Ban</a>
<a class="dropdown-item" href="#" v-show="u?.status == &#x27;removed&#x27; || u?.status == &#x27;banned&#x27;" @click.prevent="activateUser(u)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check-circle"></use></svg> Activate</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="filteredUsers?.length == 0">
<i class="fas fa-inbox"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span>No users match the current filters.</span>
</div>
</div>
@@ -116,10 +116,10 @@
<div class="admin-toolbar" style="justify-content: space-between; border-bottom: none;">
<span style="font-size: 12px; color: var(--ink-muted);">{{ fmt?.number(total) }} results</span>
<div class="pagination-compact" v-if="totalPage &gt; 1">
<button class="btn btn-sm" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1"><i class="fas fa-chevron-left"></i> Previous</button>
<button class="btn btn-sm" @click="query.page = Math.max(1, query.page - 1)" :disabled="query?.page &lt;= 1"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-left"></use></svg> Previous</button>
<input type="number" class="form-control form-control-sm" min="1" style="width: 56px;" v-field="{ state: viewState, set: value =&gt; { query.page = value }, value: query?.page, form: null, options: {} }" :max="(totalPage)">
<span>of {{ totalPage }}</span>
<button class="btn btn-sm" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage">Next <i class="fas fa-chevron-right"></i></button>
<button class="btn btn-sm" @click="query.page = Math.min(totalPage, query.page + 1)" :disabled="query?.page &gt;= totalPage">Next <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-chevron-right"></use></svg></button>
</div>
<span class="admin-filter-inline">
<label>Per page</label>
+80 -46
View File
@@ -1,19 +1,20 @@
<div class="anonymize-page h-100">
<div class="anonymize-page h-100" :class="{'mobile-preview-active': mobilePane === 'preview'}">
<!-- ===== STATE 1: No URL — centered input ===== -->
<div class="anonymize-landing" v-show="!(sourceUrl)">
<div class="anonymize-landing" v-show="!workspaceOpen &amp;&amp; !isUpdate">
<div class="anonymize-landing-inner">
<div class="paper-crumbs">My work &nbsp;/&nbsp; <span class="here">New anonymization</span></div>
<h1 class="paper-page-title">New <em>anonymization</em></h1>
<p class="paper-page-lede">
Paste a GitHub repository, pull-request, or gist URL. We&rsquo;ll fetch
it, strip every trace of identity, and hand you back a stable link.
it, redact the terms you choose in text files, and give you a share link.
Images and PDFs are served unchanged when enabled. Review them before sharing.
</p>
<div class="form-group mt-4 mb-2">
<div class="source-url-label-row">
<label class="paper-field-label" for="sourceUrl-landing">Source URL</label>
<aside class="repo-access" aria-label="GitHub access" v-if="!sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<div class="repo-access-switch" role="group" aria-label="Repository connection" v-if="!isUpdate &amp;&amp; detectedType !== 'gist'">
<button type="button" :aria-pressed="githubConnection === 'github-app'" :disabled="githubConnection === 'github-app'" @click="chooseGitHubConnection('github-app')"><i class="fas fa-shield-alt" aria-hidden="true"></i> <span class="sr-only">Read-only </span>GitHub App</button>
<button type="button" :aria-pressed="githubConnection === 'github-app'" :disabled="githubConnection === 'github-app'" @click="chooseGitHubConnection('github-app')"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> <span class="sr-only">Read-only </span>GitHub App</button>
<button type="button" :aria-pressed="githubConnection === 'oauth'" :disabled="githubConnection === 'oauth'" @click="chooseGitHubConnection('oauth')">Legacy OAuth</button>
</div>
<span class="connection-badge" v-if="isUpdate || detectedType === 'gist'">{{ detectedType !== 'gist' &amp;&amp; githubConnection === 'github-app' ? 'Read-only GitHub App' : 'Legacy OAuth' }}</span>
@@ -21,6 +22,8 @@
</div>
<input id="sourceUrl-landing" type="text" class="form-control form-control-lg" placeholder="https://github.com/owner/repository" v-field="{ state: viewState, set: value =&gt; { sourceUrl = value }, value: sourceUrl, form: null, options: { debounce: {default: 1000, blur: 0, click: 0}, updateOn: &#x27;default blur click&#x27; }, change: () =&gt; { urlSelected() } }">
</div>
<div class="draft-status" role="status" v-if="draftRestored">Your unsaved draft was restored. <button type="button" class="draft-discard" @click="discardDraft()">Discard draft</button></div>
<p class="text-danger" role="alert" v-if="sourceUrlError">{{ sourceUrlError }}</p>
<small class="form-text" style="color: var(--ink-muted);">
Repository, pull request (…/pull/42) and gist (gist.github.com/…) URLs are all accepted.
</small>
@@ -33,17 +36,17 @@
<aside class="repo-access-details" aria-label="GitHub access" v-if="!sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<div class="repo-access-main">
<div class="repo-access-controls">
<button type="button" class="btn btn-outline-ink" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'" @click="grantGitHubAccess()">Manage access <i class="fas fa-external-link-alt" aria-hidden="true"></i></button>
<button type="button" class="btn btn-outline-ink" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'" @click="grantGitHubAccess()">Manage access <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></button>
<a class="connection-link" href="/connections" v-if="isUpdate || detectedType === 'gist' || githubConnection === 'oauth'">{{ isUpdate ? 'Change connection' : 'Manage connections' }} &rarr;</a>
</div>
</div>
<div class="repo-access-note"><span v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'">Select repositories on GitHub, then return here.</span><span><i class="fas fa-info-circle" aria-hidden="true"></i> Selected content will be published, including content from private repositories.</span></div>
<div class="repo-access-note"><span v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'">Select repositories on GitHub, then return here.</span><span><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Selected content will be published, including content from private repositories.</span></div>
</aside>
</div>
</div>
<!-- ===== STATE 2: URL provided — form (left) + preview (right) ===== -->
<div class="anonymize-workspace" v-show="sourceUrl">
<div class="anonymize-workspace" v-show="workspaceOpen || isUpdate">
<header class="anonymize-topbar">
<div class="anonymize-topbar-inner">
<div class="paper-crumbs">
@@ -62,9 +65,14 @@
</div>
</header>
<nav class="anonymize-mobile-tabs" role="tablist" aria-label="Anonymization workspace">
<button id="workspace-settings-tab" role="tab" type="button" :aria-selected="mobilePane === 'settings'" aria-controls="workspace-settings" :tabindex="mobilePane === 'settings' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="selectMobilePane('settings')">Settings</button>
<button id="workspace-preview-tab" role="tab" type="button" :aria-selected="mobilePane === 'preview'" aria-controls="workspace-preview" :tabindex="mobilePane === 'preview' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="selectMobilePane('preview')">Reviewer preview</button>
</nav>
<div class="anonymize-preview-empty" id="workspace-preview" v-if="detectedType !== 'repo' &amp;&amp; !details" role="status">{{ sourceLoading ? 'Loading source…' : 'Choose an accessible source to see its preview.' }}</div>
<div class="anonymize-split">
<!-- Form column (left) -->
<div class="anonymize-form-col overflow-auto">
<div id="workspace-settings" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-settings-tab' : null" class="anonymize-form-col overflow-auto">
<form class="form needs-validation paper-settings-main" name="anonymize" novalidate v-form="viewState">
<section class="paper-settings-section">
@@ -75,7 +83,7 @@
<label class="paper-field-label" for="sourceUrl">GitHub URL</label>
<aside class="repo-access" aria-label="GitHub access" v-if="sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<div class="repo-access-switch" role="group" aria-label="Repository connection" v-if="!isUpdate &amp;&amp; detectedType !== 'gist'">
<button type="button" :aria-pressed="githubConnection === 'github-app'" :disabled="githubConnection === 'github-app'" @click="chooseGitHubConnection('github-app')"><i class="fas fa-shield-alt" aria-hidden="true"></i> <span class="sr-only">Read-only </span>GitHub App</button>
<button type="button" :aria-pressed="githubConnection === 'github-app'" :disabled="githubConnection === 'github-app'" @click="chooseGitHubConnection('github-app')"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> <span class="sr-only">Read-only </span>GitHub App</button>
<button type="button" :aria-pressed="githubConnection === 'oauth'" :disabled="githubConnection === 'oauth'" @click="chooseGitHubConnection('oauth')">Legacy OAuth</button>
</div>
<span class="connection-badge" v-if="isUpdate || detectedType === 'gist'">{{ detectedType !== 'gist' &amp;&amp; githubConnection === 'github-app' ? 'Read-only GitHub App' : 'Legacy OAuth' }}</span>
@@ -99,11 +107,11 @@
<aside class="repo-access-details" aria-label="GitHub access" v-if="sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<div class="repo-access-main">
<div class="repo-access-controls">
<button type="button" class="btn btn-outline-ink" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'" @click="grantGitHubAccess()">Manage access <i class="fas fa-external-link-alt" aria-hidden="true"></i></button>
<button type="button" class="btn btn-outline-ink" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'" @click="grantGitHubAccess()">Manage access <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></button>
<a class="connection-link" href="/connections" v-if="isUpdate || detectedType === 'gist' || githubConnection === 'oauth'">{{ isUpdate ? 'Change connection' : 'Manage connections' }} &rarr;</a>
</div>
</div>
<div class="repo-access-note"><span v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'">Select repositories on GitHub, then return here.</span><span><i class="fas fa-info-circle" aria-hidden="true"></i> Selected content will be published, including content from private repositories.</span></div>
<div class="repo-access-note"><span v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'">Select repositories on GitHub, then return here.</span><span><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Selected content will be published, including content from private repositories.</span></div>
</aside>
<aside class="repo-access-details" v-if="isUpdate &amp;&amp; detectedType === 'repo' &amp;&amp; role === 'owner' &amp;&amp; sourceUrl === 'https://github.com/' + _originalFullName">
@@ -120,8 +128,8 @@
<option v-for="(b, index) in branches" v-text="b?.name" :value="(b?.name)"></option>
</select>
<div class="input-group-append">
<button class="btn" type="button" title="Refresh" data-toggle="tooltip" data-placement="bottom" @click="getBranches(true)">
<i class="fas fa-sync" aria-hidden="true"></i>
<button class="btn btn-icon" type="button" title="Refresh" data-toggle="tooltip" data-placement="bottom" @click="getBranches(true)" aria-label="Refresh">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg>
</button>
</div>
</div>
@@ -207,10 +215,12 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" id="image" name="image" v-field="{ state: viewState, set: value =&gt; { options.image = value }, value: options?.image, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="image">Display images</label>
<small class="form-text text-muted">Images are served unchanged and may contain names or affiliations.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="pdf" name="pdf" v-field="{ state: viewState, set: value =&gt; { options.pdf = value }, value: options?.pdf, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="pdf">Display PDFs</label>
<small class="form-text text-muted">PDF contents and metadata are served unchanged. <a href="/faq#formats">About supported formats</a></small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notebook" name="notebook" v-field="{ state: viewState, set: value =&gt; { options.notebook = value }, value: options?.notebook, form: &quot;anonymize&quot;, options: {} }">
@@ -219,10 +229,10 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" id="page" name="page" v-field="{ state: viewState, set: value =&gt; { options.page = value }, value: options?.page, form: &quot;anonymize&quot;, options: {} }" :disabled="!details?.hasPage || (details?.pageSource &amp;&amp; details?.pageSource?.branch !== source?.branch)">
<label class="form-check-label" for="page">GitHub Pages</label>
<small class="form-text text-muted d-block" v-show="!details?.hasPage">
<small class="form-text text-muted" v-if="!details?.hasPage">
{{ fmt?.translate('WARNINGS.page_not_enabled_on_repo') }}
</small>
<small class="form-text text-muted d-block" v-show="details?.hasPage &amp;&amp; details?.pageSource &amp;&amp; details?.pageSource?.branch !== source?.branch">
<small class="form-text text-muted" v-if="details?.hasPage &amp;&amp; details?.pageSource?.branch &amp;&amp; source?.branch &amp;&amp; details.pageSource.branch !== source.branch">
{{ fmt?.translate('WARNINGS.page_branch_mismatch', { pageBranch: details?.pageSource?.branch, selectedBranch: source?.branch }) }}
</small>
</div>
@@ -335,8 +345,8 @@
<img alt="" style="width: 24px; height: 24px; border-radius: 50%;" v-if="c?.photo" :src="safeUrl((c?.photo))">
<a target="_blank" v-text="c?.username" :href="safeUrl(&quot;https://github.com/&quot; + (c?.username))"></a>
<span class="type-badge type-coauthor">Co-author</span>
<button type="button" class="btn btn-sm" style="margin-left: auto;" title="Remove co-author" v-show="role === &#x27;owner&#x27; || role === &#x27;admin&#x27;" @click="removeCoauthor(c)">
<i class="fas fa-times"></i>
<button type="button" class="btn btn-sm btn-icon" style="margin-left: auto;" title="Remove co-author" v-show="role === &#x27;owner&#x27; || role === &#x27;admin&#x27;" @click="removeCoauthor(c)" aria-label="Remove co-author">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-close"></use></svg>
</button>
</div>
<div class="form-text text-muted" v-show="!coauthors || coauthors?.length === 0">
@@ -347,44 +357,61 @@
<div class="alert alert-danger" role="alert" v-if="error" v-text="error"></div>
<div class="draft-status" role="status" v-if="draftRestored">Your unsaved draft was restored. <button type="button" class="draft-discard" @click="discardDraft()">Discard draft</button></div>
<div class="draft-status" v-else-if="unsavedChanges">{{ draftSaved ? "Draft saved in this tab for 30 minutes." : "Unsaved changes. Keep this tab open until you save." }} <button type="button" class="draft-discard" @click="discardDraft()">{{ isUpdate ? "Restore saved settings" : "Discard changes" }}</button></div>
<p class="anonymize-review-note" v-if="detectedType === 'repo' &amp;&amp; (options.image || options.pdf)">Review enabled images and PDFs before sharing. Their contents are not anonymized.</p>
<button type="button" class="btn btn-outline-ink mobile-preview-jump" @click="selectMobilePane('preview')">Review preview <span aria-hidden="true">→</span></button>
<div class="anonymize-submit-bar" v-show="detectedType">
<button type="submit" class="btn btn-ink" v-if="detectedType === &#x27;repo&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeRepo($event) })">
<i class="fas fa-user-secret mr-1"></i> Anonymize Repository
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;repo&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeRepo($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymize Repository
</button>
<button type="submit" class="btn btn-ink" v-if="detectedType === &#x27;repo&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeRepo($event) })">
<i class="fas fa-save mr-1"></i> Update Repository
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;repo&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeRepo($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-save"></use></svg> Update Repository
</button>
<button type="submit" class="btn btn-ink" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizePullRequest($event) })">
<i class="fas fa-user-secret mr-1"></i> Anonymize Pull Request
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizePullRequest($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymize Pull Request
</button>
<button type="submit" class="btn btn-ink" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizePullRequest($event) })">
<i class="fas fa-save mr-1"></i> Update Pull Request
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizePullRequest($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-save"></use></svg> Update Pull Request
</button>
<button type="submit" class="btn btn-ink" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeGist($event) })">
<i class="fas fa-user-secret mr-1"></i> Anonymize Gist
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeGist($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymize Gist
</button>
<button type="submit" class="btn btn-ink" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeGist($event) })">
<i class="fas fa-save mr-1"></i> Update Gist
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeGist($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-save"></use></svg> Update Gist
</button>
</div>
</form>
</div>
<!-- Preview column (right) -->
<div class="anonymize-preview-col" v-if="detectedType === &#x27;repo&#x27; &amp;&amp; html_readme">
<div id="workspace-preview" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-preview-tab' : null" class="anonymize-preview-col" v-if="detectedType === &#x27;repo&#x27;">
<div class="anonymize-preview-head">
<span class="paper-eyebrow">Live preview</span>
<span class="anonymize-preview-sub">README with redactions applied</span>
<span class="anonymize-preview-sub">{{ previewStatus === 'ready' ? 'README with redactions applied' : 'README preview' }}</span>
</div>
<div class="anonymize-preview-body markdown-body body" v-html="sanitize(html_readme)"></div>
<div class="anonymize-preview-status" role="status" v-if="sourceLoading &amp;&amp; previewStatus === 'idle'">Loading source…</div>
<div class="anonymize-preview-status" role="status" v-if="previewStatus === 'loading'">Updating the anonymized preview…</div>
<div class="anonymize-preview-status" role="alert" v-if="previewStatus === 'error'">
<p>Unable to generate the anonymized preview. The latest redactions have not been verified.</p>
<button class="btn btn-outline-ink" type="button" @click="retryPreview()">Retry preview</button>
</div>
<div class="anonymize-preview-status" v-if="!sourceLoading &amp;&amp; !readme &amp;&amp; previewStatus === 'idle'">No README is available to preview. Review the published files before sharing.</div>
<div class="anonymize-preview-body markdown-body body" v-if="previewStatus === 'ready'" v-html="sanitize(html_readme)"></div>
</div>
<div class="anonymize-preview-col" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; details">
<div id="workspace-preview" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-preview-tab' : null" class="anonymize-preview-col" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; details">
<div class="anonymize-preview-head">
<span class="paper-eyebrow">Live preview</span>
<span class="anonymize-preview-sub">Gist with redactions applied</span>
<span class="anonymize-preview-sub">{{ previewStatus === 'ready' ? 'Gist with redactions applied' : 'Gist preview' }}</span>
</div>
<div class="anonymize-preview-body">
<div class="anonymize-preview-status" role="status" v-if="sourceLoading &amp;&amp; previewStatus === 'idle'">Loading source…</div>
<div class="anonymize-preview-status" role="status" v-if="previewStatus === 'loading'">Updating the anonymized preview…</div>
<div class="anonymize-preview-status" role="alert" v-if="previewStatus === 'error'">
<p>Unable to generate the anonymized preview. The latest redactions have not been verified.</p>
<button class="btn btn-outline-ink" type="button" @click="retryPreview()">Retry preview</button>
</div>
<div class="anonymize-preview-body" v-if="previewStatus === 'ready'">
<div class="d-flex w-100 justify-content-between align-items-center flex-wrap">
<h2 class="pr-title mb-1">
<span v-if="options?.title">{{ anonymizeGistContent(details?.gist?.description) || 'Untitled gist' }}</span>
@@ -413,7 +440,7 @@
<li class="pr-comment" v-for="(comment, index) in details?.gist?.comments">
<div class="pr-comment-head">
<span class="pr-comment-author" v-if="options?.username">
<i class="far fa-user"></i> @<span v-text="anonymizeGistContent(comment?.author)"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user"></use></svg> @<span v-text="anonymizeGistContent(comment?.author)"></span>
</span>
<span class="pr-comment-date" v-if="options?.date" v-text="fmt?.date(comment?.updatedDate)"></span>
</div>
@@ -426,12 +453,18 @@
</div>
</div>
<div class="anonymize-preview-col" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; details">
<div id="workspace-preview" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-preview-tab' : null" class="anonymize-preview-col" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; details">
<div class="anonymize-preview-head">
<span class="paper-eyebrow">Live preview</span>
<span class="anonymize-preview-sub">Pull request with redactions applied</span>
<span class="anonymize-preview-sub">{{ previewStatus === 'ready' ? 'Pull request with redactions applied' : 'Pull request preview' }}</span>
</div>
<div class="anonymize-preview-body">
<div class="anonymize-preview-status" role="status" v-if="sourceLoading &amp;&amp; previewStatus === 'idle'">Loading source…</div>
<div class="anonymize-preview-status" role="status" v-if="previewStatus === 'loading'">Updating the anonymized preview…</div>
<div class="anonymize-preview-status" role="alert" v-if="previewStatus === 'error'">
<p>Unable to generate the anonymized preview. The latest redactions have not been verified.</p>
<button class="btn btn-outline-ink" type="button" @click="retryPreview()">Retry preview</button>
</div>
<div class="anonymize-preview-body" v-if="previewStatus === 'ready'">
<div class="d-flex w-100 justify-content-between align-items-center flex-wrap">
<h2 class="pr-title mb-1">
<span v-if="options?.title">{{ anonymizePrContent(details?.pullRequest?.title) }}</span>
@@ -446,24 +479,25 @@
<markdown :content="anonymizePrContent(details?.pullRequest?.body)" :options="options" :terms="terms"></markdown>
</div>
<nav class="paper-tabs" role="tablist" v-if="options?.diff || options?.comments">
<button class="paper-tab" type="button" role="tab" v-if="options?.diff" :class="{&#x27;active&#x27;: prTabState?.active == &#x27;diff&#x27;}" @click="prTabState.active = &#x27;diff&#x27;">
<i class="fas fa-code"></i> Diff
<button class="paper-tab" type="button" role="tab" v-if="options?.diff" :class="{&#x27;active&#x27;: prTabState?.active == &#x27;diff&#x27;}" id="preview-pr-diff-tab" :aria-selected="prTabState?.active === 'diff'" aria-controls="preview-pr-diff-panel" :tabindex="prTabState?.active === 'diff' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="prTabState.active = &#x27;diff&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-code"></use></svg> Diff
</button>
<button class="paper-tab" type="button" role="tab" v-if="options?.comments" :class="{&#x27;active&#x27;: prTabState?.active == &#x27;comments&#x27;}" @click="prTabState.active = &#x27;comments&#x27;">
<i class="far fa-comment-dots"></i>
<button class="paper-tab" type="button" role="tab" v-if="options?.comments" :class="{&#x27;active&#x27;: prTabState?.active == &#x27;comments&#x27;}" id="preview-pr-comments-tab" :aria-selected="prTabState?.active === 'comments'" aria-controls="preview-pr-comments-panel" :tabindex="prTabState?.active === 'comments' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="prTabState.active = &#x27;comments&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-comment"></use></svg>
<span v-text="fmt.plural(details?.pullRequest?.comments?.length, {&#x27;0&#x27;: &#x27;No comments&#x27;, &#x27;one&#x27;: &#x27;1 comment&#x27;, &#x27;other&#x27;: &#x27;{} comments&#x27;})"></span>
</button>
</nav>
<div class="anonymize-preview-status" v-if="!options?.diff &amp;&amp; !options?.comments">Diff and comments are hidden by your display settings.</div>
<div class="paper-tab-content">
<div v-if="options?.diff &amp;&amp; prTabState?.active == &#x27;diff&#x27;">
<div role="tabpanel" id="preview-pr-diff-panel" aria-labelledby="preview-pr-diff-tab" v-if="options?.diff &amp;&amp; prTabState?.active == &#x27;diff&#x27;">
<div class="pr-diff" v-html="sanitize(fmt?.diff(anonymizePrContent(details?.pullRequest?.diff)))"></div>
</div>
<div v-if="options?.comments &amp;&amp; prTabState?.active == &#x27;comments&#x27;">
<div role="tabpanel" id="preview-pr-comments-panel" aria-labelledby="preview-pr-comments-tab" v-if="options?.comments &amp;&amp; prTabState?.active == &#x27;comments&#x27;">
<ul class="pr-comments">
<li class="pr-comment" v-for="(comment, index) in details?.pullRequest?.comments">
<div class="pr-comment-head">
<span class="pr-comment-author" v-if="options?.username">
<i class="far fa-user"></i> @<span v-text="anonymizePrContent(comment?.author)"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user"></use></svg> @<span v-text="anonymizePrContent(comment?.author)"></span>
</span>
<span class="pr-comment-date" v-if="options?.date" v-text="fmt?.date(comment?.updatedDate)"></span>
</div>
+5 -5
View File
@@ -39,7 +39,7 @@
</div>
<div class="paper-section-eyebrow anonymize-section-title">
<i class="fas fa-chalkboard-teacher"></i> Conference ID
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> Conference ID
</div>
<!-- Conference -->
<div class="form-group">
@@ -59,7 +59,7 @@
</div>
<div class="paper-section-eyebrow anonymize-section-title">
<i class="fas fa-shield-alt"></i> Anonymization Options
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymization Options
</div>
<!-- Pull Request ID -->
<div class="form-group">
@@ -112,7 +112,7 @@
<div class="card-header" id="headingOne">
<h2 class="mb-0">
<button class="btn btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<i class="fas fa-cog mr-1"></i> Advanced options
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#project-icon-filter"></use></svg> Advanced options
</button>
</h2>
</div>
@@ -169,10 +169,10 @@
<div class="alert alert-danger" role="alert" v-if="error" v-text="error"></div>
<div class="anonymize-submit-bar" v-show="pullRequestUrl">
<button id="submit" type="submit" class="btn btn-ink btn-block" v-if="!isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizePullRequest($event) })">
<i class="fas fa-user-secret mr-1"></i> Anonymize
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymize
</button>
<button id="submit" type="submit" class="btn btn-ink btn-block" v-if="isUpdate" @click.prevent="submitForm($event, () =&gt; { updatePullRequest($event) })">
<i class="fas fa-save mr-1"></i> Update
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-save"></use></svg> Update
</button>
</div>
</form>
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="container paper-page">
<div class="container paper-page claim-page">
<div class="paper-crumbs"><a href="/dashboard">My work</a> &nbsp;/&nbsp; <span class="here">Claim</span></div>
<h1 class="paper-page-title">Claim an <em>anonymization</em></h1>
<p class="paper-page-lede">
+9 -9
View File
@@ -1,4 +1,4 @@
<div class="container page paper-page">
<div class="container page paper-page conference-page">
<div>
<div class="paper-crumbs">
<a href="/dashboard">My work</a> &nbsp;/&nbsp; <a href="/conferences">Conferences</a> &nbsp;/&nbsp; <span class="here">{{ conference?.conferenceID }}</span>
@@ -15,7 +15,7 @@
<span class="status-dot" aria-hidden="true" :class="&#x27;status-&#x27; + conference?.status"></span>
<span v-text="fmt?.statusLabel(conference?.status)"></span>
</span>
<a class="btn btn-outline-ink" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/edit&quot;)"><i class="far fa-edit mr-1" aria-hidden="true"></i> Edit conference</a>
<a class="btn btn-outline-ink" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/edit&quot;)"><svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit conference</a>
</div>
</div>
<div class="paper-meta-rule">
@@ -99,7 +99,7 @@
</div>
<div class="paper-table-row" role="row" v-for="(repo, index) in filteredRepositories" :key="repo?.repoId" :class="{&#x27;repo-inactive&#x27;: repo?.status == &#x27;expired&#x27; || repo?.status == &#x27;removed&#x27;, &#x27;repo-error&#x27;: repo?.status == &#x27;error&#x27;}">
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<svg class="ui-icon record-type-icon" role="img" aria-label="Repository"><use href="#project-type-repo"></use></svg>
<div class="anon-text">
<a class="repo-name" v-text="repo?.repoId" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId) + &quot;/&quot;)"></a>
<div class="anon-sub">
@@ -116,8 +116,8 @@
</div>
<div class="status-sub" v-if="repo?.anonymizeDate">anonymized {{ fmt?.humanTime(repo?.anonymizeDate) }}</div>
</div>
<div class="cell-views num" role="cell" v-text="fmt?.number(repo?.pageView)"></div>
<div class="cell-expires" role="cell">
<div class="cell-views num" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> <span v-text="fmt?.number(repo?.pageView)"></span></div>
<div class="cell-expires" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>
<span class="expires-never" v-if="repo?.status == &#x27;ready&#x27; &amp;&amp; (repo?.options?.expirationMode === &#x27;never&#x27; || !repo?.options?.expirationDate)">Never</span>
<span v-if="repo?.status == &#x27;ready&#x27; &amp;&amp; repo?.options?.expirationMode !== &#x27;never&#x27; &amp;&amp; repo?.options?.expirationDate" v-text="fmt?.humanTime(repo?.options?.expirationDate)"></span>
<span class="expires-past" v-if="repo?.status == &#x27;expired&#x27;">Expired<span v-if="repo?.options?.expirationDate"> {{ fmt?.humanTime(repo?.options?.expirationDate) }}</span></span>
@@ -126,21 +126,21 @@
<div class="cell-actions" role="cell">
<div class="dropdown">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :aria-label="&quot;Actions for &quot; + (repo?.repoId)">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/r/&quot; + (repo?.repoId) + &quot;/&quot;)">
<i class="fa fa-eye" aria-hidden="true"></i> View
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View
</a>
<a class="dropdown-item" :href="safeUrl(&quot;/anonymize/&quot; + (repo?.repoId))">
<i class="far fa-edit" aria-hidden="true"></i> Edit
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit
</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="filteredRepositories?.length == 0">
<i class="fas fa-inbox" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span v-if="!conference?.repositories?.length">No repository has been submitted to this conference yet.</span>
<span v-if="conference?.repositories?.length">Nothing matches the current filters.</span>
<div class="paper-table-empty-actions" v-if="conference?.repositories?.length">
+15 -13
View File
@@ -1,4 +1,4 @@
<div class="container page paper-page">
<div class="container page paper-page conferences-page">
<div class="row">
<div class="w-100">
<div class="paper-crumbs"><a href="/dashboard">My work</a> &nbsp;/&nbsp; <span class="here">Conferences</span></div>
@@ -8,7 +8,7 @@
<p class="paper-page-lede">Group anonymizations by venue, give chairs a shared dashboard, and set one expiry for every submission.</p>
</div>
<a href="/conference/new" class="btn btn-ink">
<i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> New conference
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#project-icon-plus"></use></svg> New conference
</a>
</div>
@@ -70,6 +70,8 @@
</div>
</div>
<div class="conference-feedback" role="alert" v-if="error"><span>{{ error }}</span><button type="button" class="btn btn-outline-ink" @click="loadConferences()">Retry</button></div>
<div class="conference-feedback" role="status" v-if="loading">Loading conferences…</div>
<div class="paper-table paper-table-conferences w-100" role="table" aria-label="Conferences">
<div class="paper-table-head" role="row">
<div role="columnheader">Conference</div>
@@ -80,7 +82,7 @@
</div>
<div class="paper-table-row row-clickable" role="row" v-for="(conference, index) in filteredConferences" :key="conference?.conferenceID" :class="{&#x27;repo-inactive&#x27;: conference?.status == &#x27;expired&#x27; || conference?.status == &#x27;removed&#x27;}">
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Conf</span>
<svg class="ui-icon record-type-icon" role="img" aria-label="Conference"><use href="#project-icon-conference"></use></svg>
<div class="anon-text">
<a class="repo-name" v-text="conference?.name" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID))"></a>
<div class="anon-sub">
@@ -94,40 +96,40 @@
<span class="status-word" v-text="fmt?.statusLabel(conference?.status)"></span>
</div>
</div>
<div class="cell-views num" role="cell" v-text="fmt?.number(conference?.nbRepositories || 0)"></div>
<div class="cell-expires" role="cell">
<div class="cell-views num" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> <span v-text="fmt?.number(conference?.nbRepositories || 0)"></span></div>
<div class="cell-expires" role="cell"><svg class="ui-icon mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>
<span v-if="conference?.startDate">{{ fmt?.date(conference?.startDate, 'mediumDate') }} &ndash; {{ fmt?.date(conference?.endDate, 'mediumDate') }}</span>
<span class="empty-dash" aria-label="No review window" v-if="!conference?.startDate">&mdash;</span>
</div>
<div class="cell-actions" role="cell">
<div class="dropdown">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :aria-label="&quot;Actions for &quot; + (conference?.name)">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :disabled="conference?.removing" :aria-label="&quot;Actions for &quot; + (conference?.name)">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/&quot;)">
<i class="fa fa-eye" aria-hidden="true"></i> View
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View
</a>
<a class="dropdown-item" :href="safeUrl(&quot;/conference/&quot; + (conference?.conferenceID) + &quot;/edit&quot;)">
<i class="far fa-edit" aria-hidden="true"></i> Edit
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit
</a>
<div v-if="conference?.status != &#x27;removed&#x27;">
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item dropdown-item-danger" @click="removeConference(conference)">
<i class="fas fa-trash-alt" aria-hidden="true"></i> Remove
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove
</button>
</div>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" v-if="filteredConferences?.length == 0">
<i class="fas fa-inbox" aria-hidden="true"></i>
<div class="paper-table-empty" v-if="!loading &amp;&amp; !error &amp;&amp; filteredConferences?.length == 0">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span v-if="conferences?.length == 0">You have not created a conference yet.</span>
<span v-if="conferences?.length &gt; 0">Nothing matches the current filters.</span>
<div class="paper-table-empty-actions">
<button type="button" class="btn btn-outline-ink" v-if="conferences?.length &gt; 0" @click="search = &#x27;&#x27;; filters.status.ready = true; filters.status.expired = true; filters.status.removed = true;">Clear filters</button>
<a href="/conference/new" class="btn btn-ink" v-if="conferences?.length == 0"><i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> New conference</a>
<a href="/conference/new" class="btn btn-ink" v-if="conferences?.length == 0"><svg class="ui-icon mr-1" aria-hidden="true"><use href="#project-icon-plus"></use></svg> New conference</a>
</div>
</div>
</div>
+5 -5
View File
@@ -15,17 +15,17 @@
<p>Read-only access to the repositories you choose.</p>
<p class="paper-inline-alert paper-inline-alert-error" v-if="connections.appError" role="alert">{{ connections.appError }}. Reconnect to restore access.</p>
<div class="connection-actions">
<a class="btn btn-ink" v-if="connections.appConnected &amp;&amp; !connections.appError" target="_self" href="/github/app/install">Manage repository access <i class="fas fa-external-link-alt" aria-hidden="true"></i></a>
<a class="btn btn-ink" v-if="connections.appConnected &amp;&amp; !connections.appError" target="_self" href="/github/app/install">Manage repository access <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></a>
<a :class="connections.appConnected &amp;&amp; !connections.appError ? 'connection-link' : 'btn btn-ink'" target="_self" href="/github/app/login">{{ connections.appConnected ? 'Reconnect account' : 'Connect GitHub account' }}</a>
</div>
<p class="connection-help">Choose repositories on GitHub. Organization access may need an administrator's approval.</p>
<ul class="connection-installations" v-if="connections.installations?.length">
<li v-for="installation in connections.installations" :key="installation.id"><div><strong>{{ installation.account }}</strong><span v-if="installation.suspended"> · Suspended</span></div><a target="_self" :href="'/github/app/install?installationId=' + installation.id">Manage access <span class="sr-only">for {{ installation.account }}</span> &rarr;</a></li>
</ul>
<button class="connection-link" :disabled="busy" @click="loadConnections()"><i class="fas fa-sync-alt" aria-hidden="true"></i> Refresh access</button>
<button class="connection-link" :disabled="busy" @click="loadConnections()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Refresh access</button>
</section>
<section>
<div class="connection-top"><i class="fas fa-key" aria-hidden="true"></i><span class="connection-badge connection-badge-muted">Legacy</span></div>
<div class="connection-top"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-key"></use></svg><span class="connection-badge connection-badge-muted">Legacy</span></div>
<div class="connection-title"><h2>GitHub OAuth</h2><span class="connection-status" :class="{'is-connected': connections.oauthConnected}">{{ connections.oauthConnected ? 'Connected' : 'Not connected' }}</span></div>
<p>For existing OAuth anonymizations and gists.</p>
<p class="connection-help">This connection uses GitHub's broader repository permissions. Existing work keeps using it until you switch below.</p>
@@ -40,7 +40,7 @@
<section class="connection-resources">
<div class="connection-title"><h2>Repository connections</h2><span class="connection-count">{{ connections.resources?.length || 0 }} resources</span></div>
<p>Check access, then switch each resource to the GitHub App. After reinstalling the App, reconnect existing resources here to keep their anonymous URLs and published snapshots.</p>
<div class="connection-empty" v-if="!connections.resources?.length"><i class="fas fa-code-branch" aria-hidden="true"></i><div><h3>No repository connections yet</h3><p>Your repositories and pull requests will appear here once you anonymize them.</p></div><a class="connection-link" href="/anonymize">New anonymization &rarr;</a></div>
<div class="connection-empty" v-if="!connections.resources?.length"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg><div><h3>No repository connections yet</h3><p>Your repositories and pull requests will appear here once you anonymize them.</p></div><a class="connection-link" href="/anonymize">New anonymization &rarr;</a></div>
<div class="connection-resource" v-for="resource in connections.resources" :key="resource.type + resource.id">
<div class="connection-resource-name"><strong>{{ resource.name || resource.id }}</strong><small>{{ resource.type }} · {{ resource.id }}</small></div>
<span class="connection-badge" :class="{'connection-badge-muted': resource.connection === 'oauth'}">{{ resource.connection === 'github-app' ? 'Read-only App' : 'Legacy OAuth' }}</span>
@@ -51,7 +51,7 @@
</div>
<button v-if="!['archived', 'removed', 'preparing', 'queue', 'removing', 'expiring'].includes(resource.status) &amp;&amp; (resource.status !== 'download' || (resource.type === 'pull-request' &amp;&amp; (!resource.statusDate || new Date(resource.statusDate).getTime() &lt;= Date.now() - 300000))) &amp;&amp; resource.connection === 'github-app' &amp;&amp; connections.oauthConnected" class="connection-link" :disabled="busy" @click="changeConnection(resource, 'oauth', false)">Use OAuth</button>
</div>
<p class="connection-footnote"><i class="fas fa-info-circle" aria-hidden="true"></i> Removing access stops new fetches from GitHub. Published content keeps its current expiration and removal settings.</p>
<p class="connection-footnote"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Removing access stops new fetches from GitHub. Published content keeps its current expiration and removal settings.</p>
</section>
</div>
</div>
+103 -79
View File
@@ -1,64 +1,59 @@
<div class="container page dashboard-page paper-page">
<div class="row">
<div class="w-100">
<div class="paper-crumbs">My work &nbsp;/&nbsp; <span class="here">Dashboard</span></div>
<div class="d-flex align-items-end justify-content-between flex-wrap" style="gap: 12px;">
<div class="dashboard-heading">
<div>
<h1 class="paper-page-title">Your <em>anonymizations</em></h1>
<p class="paper-page-lede">Every repository, pull request, and gist you&rsquo;ve mirrored, with live status and stats.</p>
<h1 class="paper-page-title">Projects</h1>
<p class="paper-page-lede">Manage your anonymous links.</p>
<details class="dashboard-quota" :open="quotaExpanded" @toggle="quotaExpanded = $event.target.open" @keydown.esc.prevent.stop="closeQuota()">
<summary aria-controls="dashboard-usage" :aria-expanded="quotaExpanded">
<svg class="quota-summary-ring" viewBox="0 0 24 24" aria-hidden="true" :class="quota ? 'quota-' + quota.repository.level : ''">
<circle class="quota-ring-track" cx="12" cy="12" r="9"></circle>
<circle class="quota-ring-fill" cx="12" cy="12" r="9" pathLength="100" v-if="quota &amp;&amp; !quota.repository.unlimited" :stroke-dasharray="quota.repository.percent + ' 100'"></circle>
<path v-if="quota?.repository.unlimited" d="M7 12c0-4 4-4 5 0s5 4 5 0-4-4-5 0-5 4-5 0" class="quota-ring-unlimited"></path>
</svg>
<span class="quota-summary-label">Usage</span>
<span class="quota-summary-value" v-if="quota">{{ fmt.number(quota.repository.used) }}<span v-if="!quota.repository.unlimited"> / {{ fmt.number(quota.repository.total) }}</span> repos</span>
<svg class="project-type-svg quota-summary-chevron" aria-hidden="true"><use href="#project-icon-chevron"></use></svg>
</summary>
<div class="quota-panel" id="dashboard-usage">
<div class="quota-panel-heading"><h2>Account usage</h2><button type="button" class="project-close" aria-label="Close usage" @click="closeQuota()"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button></div>
<quota-usage :quota="quota" :fmt="fmt" v-if="quota"></quota-usage>
<p class="quota-loading" role="status" v-else>Loading usage…</p>
</div>
</details>
</div>
<div class="dashboard-heading-actions">
<a href="/anonymize" class="btn btn-ink">
<i class="fa fa-plus-circle" aria-hidden="true"></i> New anonymization
<a href="/anonymize" class="btn btn-ink" aria-label="New anonymization">
<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-plus"></use></svg><span class="dashboard-new-label">New link</span><span class="dashboard-new-mobile" aria-hidden="true">New</span>
</a>
<a href="/connections" class="dashboard-connections-link">
<i class="fab fa-github" aria-hidden="true"></i> GitHub connections <span aria-hidden="true">&rarr;</span>
</a>
</div>
</div>
<!-- Quota -->
<div class="quota-row" v-if="quota">
<div class="quota-item" v-for="(q, index) in [
{key: &#x27;repository&#x27;, label: &#x27;Repositories&#x27;, kind: &#x27;count&#x27;},
{key: &#x27;storage&#x27;, label: &#x27;Storage&#x27;, kind: &#x27;bytes&#x27;},
{key: &#x27;file&#x27;, label: &#x27;Files&#x27;, kind: &#x27;count&#x27;}
]">
<div class="quota-header">
<span class="quota-label">{{ q?.label }}</span>
<span class="quota-value" v-if="q?.kind === &#x27;count&#x27;">
{{ fmt?.number(quota[q?.key].used) }}<span v-if="!quota[q?.key].unlimited"> / {{ fmt?.number(quota[q?.key].total) }}</span>
<span class="quota-unlimited-tag" v-if="quota[q?.key].unlimited">Unlimited</span>
</span>
<span class="quota-value" v-if="q?.kind === &#x27;bytes&#x27;">
{{ fmt?.humanFileSize(quota[q?.key].used) }}<span v-if="!quota[q?.key].unlimited"> / {{ fmt?.humanFileSize(quota[q?.key].total) }}</span>
<span class="quota-unlimited-tag" v-if="quota[q?.key].unlimited">Unlimited</span>
</span>
</div>
<div class="quota-track" role="progressbar" aria-valuemin="0" :class="&#x27;quota-&#x27; + quota[q?.key].level" :aria-label="(q?.label) + &quot; quota&quot;" :aria-valuenow="(quota[q?.key].used)" :aria-valuemax="(quota[q?.key].unlimited ? quota[q?.key].used : quota[q?.key].total)" :aria-valuetext="(quota[q?.key].unlimited ? &#x27;unlimited&#x27; : fmt.number(quota[q?.key].percent, 0) + &#x27;% used&#x27;)">
<div class="quota-fill" v-if="!quota[q?.key].unlimited" :style="{width: quota[q?.key].percent + &#x27;%&#x27;}"></div>
</div>
</div>
</div>
<!-- Search + filters -->
<form class="w-100 dashboard-filter-row" aria-label="Filter anonymizations" accept-charset="UTF-8" v-form="viewState" @submit.prevent="submitForm($event, () =&gt; { $event.preventDefault() })">
<div class="search-wrap">
<input type="search" id="search" class="form-control" aria-label="Search anonymizations" placeholder="Search by name, source, or conference…" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { search = value }, value: search, form: null, options: {} }">
<input type="search" id="search" class="form-control" aria-label="Search anonymizations" placeholder="Search projects, IDs, or conferences…" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { search = value }, value: search, form: null, options: {} }">
</div>
<div class="dashboard-filter-controls">
<button class="btn dashboard-filters-toggle" type="button" :aria-expanded="filtersExpanded" aria-controls="dashboard-filter-controls" @click="filtersExpanded = !filtersExpanded">Filters<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-filter"></use></svg></button>
<div id="dashboard-filter-controls" class="dashboard-filter-controls" :class="{'expanded': filtersExpanded}">
<div class="btn-group" role="group" aria-label="Type">
<button type="button" class="btn" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;all&#x27;}" @click="typeFilter = &#x27;all&#x27;" :aria-pressed="(typeFilter === &#x27;all&#x27;)">All</button>
<button type="button" class="btn" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;repo&#x27;}" @click="typeFilter = &#x27;repo&#x27;" :aria-pressed="(typeFilter === &#x27;repo&#x27;)">Repos</button>
<button type="button" class="btn" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;pr&#x27;}" @click="typeFilter = &#x27;pr&#x27;" :aria-pressed="(typeFilter === &#x27;pr&#x27;)">PRs</button>
<button type="button" class="btn" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;gist&#x27;}" @click="typeFilter = &#x27;gist&#x27;" :aria-pressed="(typeFilter === &#x27;gist&#x27;)">Gists</button>
<button type="button" class="btn dashboard-type-filter" title="Repositories" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;repo&#x27;}" @click="typeFilter = &#x27;repo&#x27;" :aria-pressed="(typeFilter === &#x27;repo&#x27;)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-type-repo"></use></svg><span class="type-filter-label">Repos</span></button>
<button type="button" class="btn dashboard-type-filter" title="Pull requests" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;pr&#x27;}" @click="typeFilter = &#x27;pr&#x27;" :aria-pressed="(typeFilter === &#x27;pr&#x27;)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-type-pr"></use></svg><span class="type-filter-label">PRs</span></button>
<button type="button" class="btn dashboard-type-filter" title="Gists" :class="{&#x27;btn-primary&#x27;: typeFilter === &#x27;gist&#x27;}" @click="typeFilter = &#x27;gist&#x27;" :aria-pressed="(typeFilter === &#x27;gist&#x27;)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-type-gist"></use></svg><span class="type-filter-label">Gists</span></button>
</div>
<div id="dashboard-extra-filters" class="dashboard-extra-filters" :class="{'expanded': filtersExpanded}">
<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" aria-hidden="true" :class="sortDesc() ? &#x27;fa-arrow-down&#x27; : &#x27;fa-arrow-up&#x27;"></i>
<svg class="project-type-svg sort-direction" aria-hidden="true" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
<span class="sr-only">{{ sortDesc() ? 'descending' : 'ascending' }}</span>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
@@ -69,7 +64,6 @@
</div>
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item" @click="toggleSortDirection()">
<i class="fas fa-exchange-alt fa-rotate-90" aria-hidden="true"></i>
{{ sortDesc() ? 'Switch to ascending' : 'Switch to descending' }}
</button>
</div>
@@ -90,43 +84,48 @@
</div>
</div>
</div>
</div>
</form>
<!-- Result count + active filter chips -->
<div class="dashboard-meta" v-if="!loading">
<div class="dashboard-view-tabs" role="group" aria-label="Project view">
<button type="button" :aria-pressed="projectView === 'all'" @click="setProjectView('all')" aria-label="All projects"><span class="dashboard-view-long">All projects</span><span class="dashboard-view-short" aria-hidden="true">All</span></button>
<button type="button" :aria-pressed="projectView === 'attention'" @click="setProjectView('attention')" :aria-label="'Needs attention ' + attentionCount()"><span class="dashboard-attention-prefix">Needs </span><span>attention</span> <span class="attention-count" :class="{'has-attention': attentionCount() > 0}">{{ attentionCount() }}</span></button>
</div>
<span class="dashboard-count">
<span v-if="filteredItems?.length === items?.length">{{ fmt?.number(items?.length) }} anonymization<span v-if="items?.length !== 1">s</span></span>
<span v-if="filteredItems?.length === items?.length">{{ fmt?.number(items?.length) }} project<span v-if="items?.length !== 1">s</span></span>
<span v-if="filteredItems?.length !== items?.length">{{ fmt?.number(filteredItems?.length) }} of {{ fmt?.number(items?.length) }} shown</span>
</span>
<template v-for="(v, f, index) in filters?.status"><span class="filter-chip" v-if="v === false">
Hiding {{ statusKeyLabels[f] }}
<button type="button" class="filter-chip-close" @click="filters.status[f] = true;" :aria-label="&quot;Show &quot; + (statusKeyLabels[f]) + &quot; again&quot;">&times;</button>
</span></template>
<button type="button" class="btn-link-inline" v-if="hasActiveFilters()" @click="clearFilters()">Clear filters</button>
<div class="dashboard-active-filters">
<template v-for="(v, f, index) in filters?.status"><button type="button" class="filter-chip" v-if="v === false" @click="filters.status[f] = true" :aria-label="'Show ' + statusKeyLabels[f] + ' again'" :title="'Show ' + statusKeyLabels[f] + ' again'">{{ statusKeyLabels[f] }} hidden<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button></template>
<button type="button" class="filter-chip" v-if="typeFilter !== 'all'" @click="typeFilter = 'all'" aria-label="Show all project types">{{ typeFilter === 'repo' ? 'Repositories' : typeFilter === 'pr' ? 'Pull requests' : 'Gists' }}<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button>
<button type="button" class="btn-link-inline" v-if="showClearFilters()" @click="clearFilters()">Clear filters</button>
</div>
</div>
</div>
<!-- Table -->
<div class="paper-table paper-table-dashboard w-100" role="table" aria-label="Anonymizations" :aria-busy="(loading)">
<div class="paper-table paper-table-dashboard" role="table" aria-label="Anonymizations" :aria-busy="(loading)">
<div class="paper-table-head" role="row">
<div role="columnheader" :aria-sort="(isSortedBy(&#x27;_name&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;_name&#x27;)}" @click="setSort(&#x27;_name&#x27;)">
Anonymization <i class="fas" aria-hidden="true" v-if="isSortedBy(&#x27;_name&#x27;)" :class="sortDesc() ? &#x27;fa-arrow-down&#x27; : &#x27;fa-arrow-up&#x27;"></i>
<div role="columnheader" :aria-sort="(isSortedBy(&#x27;_label&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;_label&#x27;)}" @click="setSort(&#x27;_label&#x27;)">
Project <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;_label&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader" :aria-sort="(isSortedBy(&#x27;status&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;status&#x27;)}" @click="setSort(&#x27;status&#x27;)">
Status <i class="fas" aria-hidden="true" v-if="isSortedBy(&#x27;status&#x27;)" :class="sortDesc() ? &#x27;fa-arrow-down&#x27; : &#x27;fa-arrow-up&#x27;"></i>
Status <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;status&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader" class="num" :aria-sort="(isSortedBy(&#x27;pageView&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;pageView&#x27;)}" @click="setSort(&#x27;pageView&#x27;)">
Views <i class="fas" aria-hidden="true" v-if="isSortedBy(&#x27;pageView&#x27;)" :class="sortDesc() ? &#x27;fa-arrow-down&#x27; : &#x27;fa-arrow-up&#x27;"></i>
Views <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;pageView&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader" :aria-sort="(isSortedBy(&#x27;options.expirationDate&#x27;) ? (sortDesc() ? &#x27;descending&#x27; : &#x27;ascending&#x27;) : &#x27;none&#x27;)">
<button type="button" class="sortable" :class="{active: isSortedBy(&#x27;options.expirationDate&#x27;)}" @click="setSort(&#x27;options.expirationDate&#x27;)">
Expires <i class="fas" aria-hidden="true" v-if="isSortedBy(&#x27;options.expirationDate&#x27;)" :class="sortDesc() ? &#x27;fa-arrow-down&#x27; : &#x27;fa-arrow-up&#x27;"></i>
Expires <svg class="project-type-svg sort-direction" aria-hidden="true" v-if="isSortedBy(&#x27;options.expirationDate&#x27;)" :class="{'ascending': !sortDesc()}"><use href="#project-icon-arrow"></use></svg>
</button>
</div>
<div role="columnheader"><span class="sr-only">Actions</span></div>
@@ -143,27 +142,21 @@
<template v-for="(item, index) in filteredItems" :key="(item?._type + &#x27;:&#x27; + (item?._id || index))"><div class="paper-table-row" role="row" v-if="!loading" :class="{&#x27;repo-inactive&#x27;: item?._statusKey == &#x27;expired&#x27; || item?._statusKey == &#x27;removed&#x27;, &#x27;repo-error&#x27;: item?.status == &#x27;error&#x27;, &#x27;row-clickable&#x27;: !!item?._viewUrl}" @click="openItem(item, $event)">
<div class="cell-anon" role="cell">
<span class="type-badge" :class="{&#x27;type-repo&#x27;: item?._type === &#x27;repo&#x27;, &#x27;type-pr&#x27;: item?._type === &#x27;pr&#x27;, &#x27;type-gist&#x27;: item?._type === &#x27;gist&#x27;}">{{ item?._type === 'repo' ? 'Repo' : item?._type === 'pr' ? 'PR' : 'Gist' }}</span>
<span class="project-type-icon" role="img" :aria-label="item?._type === 'repo' ? 'Repository' : item?._type === 'pr' ? 'Pull request' : 'Gist'" :title="item?._type === 'repo' ? 'Repository' : item?._type === 'pr' ? 'Pull request' : 'Gist'"><svg class="project-type-svg" aria-hidden="true"><use :href="'#project-type-' + item._type"></use></svg></span>
<div class="anon-text">
<div class="anon-title">
<a class="repo-name" v-if="item?._viewUrl" v-text="item?._name" :href="safeUrl((item?._viewUrl))"></a>
<span class="repo-name repo-name-static" v-if="!item?._viewUrl" v-text="item?._name"></span>
<a class="repo-name" v-if="item?._viewUrl" :title="item._label" v-text="item?._label" :href="safeUrl((item?._viewUrl))"></a>
<span class="repo-name repo-name-static" v-if="!item?._viewUrl" v-text="item?._label"></span>
<span class="type-badge type-coauthor" title="You are a co-author on this anonymization" v-if="item?.role === &#x27;coauthor&#x27;">Co-author</span>
</div>
<div class="anon-sub" v-if="!item?._broken || item?.conference">
<span class="anon-source" v-if="item?._type === &#x27;repo&#x27; &amp;&amp; item?.source?.fullName &amp;&amp; !item?._broken">
<a target="_blank" rel="noopener" v-text="item?.source?.fullName" :href="safeUrl(&quot;https://github.com/&quot; + (item?.source?.fullName) + &quot;/&quot;)"></a><span v-if="item?.options?.update &amp;&amp; item?.source?.branch"> &middot; <a target="_blank" rel="noopener" v-text="item?.source?.branch" :href="safeUrl(&quot;https://github.com/&quot; + (item?.source?.fullName) + &quot;/tree/&quot; + (item?.source?.branch))"></a></span><span v-if="item?.source?.commit"> &middot; <a class="commit-hash" target="_blank" rel="noopener" :href="safeUrl(&quot;https://github.com/&quot; + (item?.source?.fullName) + &quot;/tree/&quot; + (item?.source?.commit))">@{{ item?.source?.commit?.substring(0, 8) }}</a></span>
</span>
<span class="anon-source" v-if="item?._type === &#x27;pr&#x27; &amp;&amp; item?.source?.repositoryFullName &amp;&amp; !item?._broken">
<a target="_blank" rel="noopener" v-text="item?._source" :href="safeUrl(&quot;https://github.com/&quot; + (item?.source?.repositoryFullName) + &quot;/pull/&quot; + (item?.source?.pullRequestId))"></a>
</span>
<span class="anon-source" v-if="item?._type === &#x27;gist&#x27; &amp;&amp; item?.source?.gistId &amp;&amp; !item?._broken">
<a target="_blank" rel="noopener" v-text="item?._source" :href="safeUrl(&quot;https://gist.github.com/&quot; + (item?.source?.gistId))"></a>
</span>
<span class="cell-conf conf-tag" v-if="item?.conference" :title="(item?.conference)"><i class="fas fa-university" aria-hidden="true"></i> <span v-text="item?.conference"></span></span>
<div class="anon-sub">
<code class="anonymous-id" v-if="item?._id" :title="item._id">{{ item._id }}</code>
<span class="commit-hash" v-if="item?._type === 'repo' &amp;&amp; item?.source?.commit" :title="item.source.commit">{{ item.source.commit.substring(0, 8) }}</span>
<span class="cell-conf conf-tag" v-if="item?.conference" :title="(item?.conference)"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-conference"></use></svg> <span v-text="item?.conference"></span></span>
</div>
</div>
</div>
<div class="dashboard-row-meta" role="presentation">
<div class="cell-status" role="cell">
<div class="status-line">
<span class="status-dot" aria-hidden="true" :class="&#x27;status-&#x27; + item?._statusKey"></span>
@@ -173,60 +166,91 @@
<div class="status-sub status-sub-warn" v-if="item?._stale" :title="&quot;Last activity &quot; + (fmt?.humanTime(item?.anonymizeDate || item?.lastView))">
Last activity {{ fmt?.humanTime(item?.anonymizeDate || item?.lastView) }} &middot; may be stuck
</div>
<div class="status-sub" v-if="item?.status != &#x27;error&#x27; &amp;&amp; !item?._stale &amp;&amp; item?.anonymizeDate">
<a class="project-attention-action" v-if="item.status === 'error' &amp;&amp; item._editUrl" :href="safeUrl(item._editUrl)">Fix source</a>
<button type="button" class="project-attention-action" v-if="item._stale || item._broken" @click.stop="showProjectDetails(item)">{{ item._broken ? 'Review details' : 'Check progress' }}</button>
<div class="status-sub status-sub-date" v-if="item?.status != &#x27;error&#x27; &amp;&amp; !item?._stale &amp;&amp; item?.anonymizeDate">
anonymized {{ fmt?.humanTime(item?.anonymizeDate) }}
</div>
</div>
<div class="cell-views num" role="cell" v-text="fmt?.number(item?.pageView)"></div>
<div class="cell-views num" role="cell" :aria-label="fmt.number(item.pageView) + ' views'"><svg class="project-type-svg mobile-meta-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg><span>{{ fmt.number(item.pageView) }}</span></div>
<div class="cell-expires" role="cell">
<svg class="project-type-svg mobile-meta-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>
<span class="expires-never" v-if="item?._expiry?.kind === &#x27;never&#x27;">Never</span>
<span v-if="item?._expiry?.kind === &#x27;date&#x27;" v-text="fmt?.humanTime(item?._expiry?.date)"></span>
<span class="expires-past" v-if="item?._expiry?.kind === &#x27;expired&#x27; &amp;&amp; item?._expiry?.date">Expired {{ fmt?.humanTime(item?._expiry?.date) }}</span>
<span class="expires-past" v-if="item?._expiry?.kind === &#x27;expired&#x27; &amp;&amp; !item?._expiry?.date">Expired</span>
<span class="empty-dash" aria-label="Not applicable" v-if="item?._expiry?.kind === &#x27;none&#x27;">&mdash;</span>
</div>
</div>
<div class="cell-actions" role="cell">
<button type="button" class="copy-link-inline" v-if="item._viewUrl" @click.stop="copyLink(item)" :disabled="!!copyingId" :title="'Copy anonymous link for ' + item._label" :aria-label="'Copy anonymous link for ' + item._label"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-copy"></use></svg></button>
<button type="button" class="project-details-button" v-if="item._broken" @click.stop="showProjectDetails(item)" :data-project-actions="encodeURIComponent(item._type + ':' + item._id)" :aria-controls="projectDetailsId(item)" :aria-expanded="selectedProject === item">Details</button>
<div class="dropdown" v-if="!item?._broken">
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :aria-label="&quot;Actions for &quot; + (item?._name)">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
<button class="btn btn-icon-dots" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" :data-project-actions="encodeURIComponent(item._type + ':' + item._id)" :aria-label="&quot;Actions for &quot; + (item?._name)">
<svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-more"></use></svg>
</button>
<div class="dropdown-menu dropdown-menu-right">
<button type="button" class="dropdown-item" @click="showProjectDetails(item)" :aria-controls="projectDetailsId(item)" :aria-expanded="selectedProject === item">Project details &amp; name</button>
<a class="dropdown-item" :href="safeUrl((item?._viewUrl))">
<i class="fa fa-eye" aria-hidden="true"></i> View
View
</a>
<a class="dropdown-item" target="_self" v-if="item?._type === &#x27;repo&#x27; &amp;&amp; item?.options?.page &amp;&amp; item?.status == &#x27;ready&#x27;" :href="safeUrl(&quot;/w/&quot; + (item?.repoId) + &quot;/&quot;)">
<i class="fas fa-globe" aria-hidden="true"></i> View page
View page
</a>
<button type="button" class="dropdown-item" @click="copyLink(item)" :disabled="!!copyingId" v-if="item?._viewUrl">
Copy anonymous link
</button>
<a class="dropdown-item" :href="safeUrl((item?._editUrl))">
<i class="far fa-edit" aria-hidden="true"></i> Edit
Edit
</a>
<button type="button" class="dropdown-item" v-if="item?.status == &#x27;ready&#x27; || item?.status == &#x27;error&#x27;" @click="refreshItem(item)">
<i class="fas fa-sync" aria-hidden="true"></i> Force update
Force update
</button>
<button type="button" class="dropdown-item" v-if="item?.status == &#x27;removed&#x27;" @click="refreshItem(item)">
<i class="fas fa-check-circle" aria-hidden="true"></i> Enable
Enable
</button>
<button type="button" class="dropdown-item" v-if="item?.status == &#x27;expired&#x27;" @click="extendItem(item)">
<i class="fas fa-calendar-plus" aria-hidden="true"></i> Extend 6 months
Extend 6 months
</button>
<div v-if="(item?.status == &#x27;ready&#x27; || item?.status == &#x27;expired&#x27; || item?.status == &#x27;error&#x27;) &amp;&amp; item?.role !== &#x27;coauthor&#x27;">
<div class="dropdown-divider"></div>
<button type="button" class="dropdown-item dropdown-item-danger" @click="removeItem(item)">
<i class="fas fa-trash-alt" aria-hidden="true"></i> Remove
Remove
</button>
</div>
</div>
</div>
</div>
</div>
<div class="project-details-row" role="row" v-if="!loading &amp;&amp; selectedProject === item">
<div class="project-details" role="cell" aria-colspan="5" :id="projectDetailsId(item)" tabindex="-1" @keydown.esc.stop="closeProjectDetails(item)">
<div class="project-details-heading"><h2>Project details</h2><button type="button" class="project-close" @click="closeProjectDetails(item)" aria-label="Close project details"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-close"></use></svg></button></div>
<dl class="project-details-list">
<div><dt>Source</dt><dd>{{ item._source || 'Not available' }}</dd></div>
<div><dt>Anonymous ID</dt><dd><code>{{ item._id || 'Missing identifier' }}</code></dd></div>
<div v-if="item.source?.commit"><dt>Commit</dt><dd><code>{{ item.source.commit }}</code></dd></div>
<div v-if="item.conference"><dt>Conference</dt><dd>{{ item.conference }}</dd></div>
<div><dt>Status</dt><dd>{{ fmt.statusLabel(item.status) }}<span v-if="item.statusMessage"> · {{ fmt.statusMsg(item.statusMessage) }}</span></dd></div>
<div v-if="item.anonymizeDate"><dt>Last anonymized</dt><dd>{{ fmt.humanTime(item.anonymizeDate) }}</dd></div>
</dl>
<p class="project-progress-help" v-if="item._stale">This download has not progressed recently. Check the source access and selected branch before retrying.</p>
<a class="project-attention-action" v-if="item._editUrl" :href="safeUrl(item._editUrl)">{{ needsAttention(item) ? 'Check source settings' : 'Edit anonymization settings' }}</a>
<form class="project-name-form" v-if="item._id" @submit.prevent="saveProjectName(item)">
<label :for="'project-name-' + item._type + '-' + item._id">Project name</label>
<p :id="'project-name-help-' + item._type + '-' + item._id">Only you see this name. Leave it blank to use the default.</p>
<div class="project-name-controls"><input class="form-control" type="text" :id="'project-name-' + item._type + '-' + item._id" :aria-describedby="'project-name-help-' + item._type + '-' + item._id" maxlength="100" v-model="projectName" :disabled="nameSaving"><button class="btn btn-ink" type="submit" :disabled="nameSaving">{{ nameSaving ? 'Saving…' : 'Save name' }}</button></div>
<p class="project-name-error" role="alert" v-if="nameError">{{ nameError }}</p><p role="status" v-if="nameMessage">{{ nameMessage }}</p>
</form>
</div>
</div></template>
<div class="paper-table-empty" v-if="!loading &amp;&amp; filteredItems?.length == 0">
<i class="fas fa-inbox" aria-hidden="true"></i>
<svg class="project-type-svg project-empty-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span v-if="items?.length == 0">You have no anonymizations yet.</span>
<span v-if="items?.length &gt; 0">Nothing matches the current filters.</span>
<div class="paper-table-empty-actions">
<button type="button" class="btn btn-outline-ink" v-if="hasActiveFilters()" @click="clearFilters()">Clear filters</button>
<a href="/anonymize" class="btn btn-ink" v-if="items?.length == 0"><i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> New anonymization</a>
<a href="/anonymize" class="btn btn-ink" v-if="items?.length == 0"><svg class="project-type-svg" aria-hidden="true"><use href="#project-icon-plus"></use></svg> New anonymization</a>
</div>
</div>
</div>
+11 -11
View File
@@ -1,6 +1,6 @@
<div class="explorer-page">
<button class="sidebar-toggle" v-show="files?.length" @click="sidebarCollapsed = !sidebarCollapsed" :aria-label="(sidebarCollapsed ? &#x27;Show files&#x27; : &#x27;Hide files&#x27;)">
<i class="fas" :class="sidebarCollapsed ? &#x27;fa-folder-open&#x27; : &#x27;fa-times&#x27;"></i>
<svg class="ui-icon" aria-hidden="true"><use :href="sidebarCollapsed ? '#ui-icon-folder' : '#project-icon-close'"></use></svg>
<span v-text="sidebarCollapsed ? &#x27;Files&#x27; : &#x27;Close&#x27;"></span>
</button>
@@ -8,12 +8,12 @@
<div class="leftCol-head">
<span class="leftCol-eyebrow">Files</span>
<button class="leftCol-close" aria-label="Close files" @click="sidebarCollapsed = true">
<i class="fas fa-times"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-close"></use></svg>
</button>
</div>
<div class="leftCol-search">
<div class="tree-search-box">
<i class="fas tree-search-icon" :class="fileSearchLoading ? &#x27;fa-spinner fa-spin&#x27; : &#x27;fa-search&#x27;"></i>
<svg class="ui-icon tree-search-icon" aria-hidden="true" :class="{'fa-spin': fileSearchLoading}"><use :href="fileSearchLoading ? '#ui-icon-spinner' : '#ui-icon-search'"></use></svg>
<input type="text" class="tree-search-input" placeholder="Search files" aria-label="Search files" v-field="{ state: viewState, set: value =&gt; { fileSearchQuery = value }, value: fileSearchQuery, form: null, options: { debounce: 300 }, change: () =&gt; { onFileSearchChange() } }">
<kbd class="tree-search-kbd" v-show="!(fileSearchQuery)">{{ isMac ? '⌘' : 'Ctrl+' }}K</kbd>
<button class="tree-search-clear" aria-label="Clear search" v-show="fileSearchQuery" @click="fileSearchQuery = &#x27;&#x27;; onFileSearchChange()">&times;</button>
@@ -25,11 +25,11 @@
</div>
<div class="leftCol-body">
<div class="paper-inline-warning" role="alert" v-if="options?.truncatedFolders?.length &gt; 0">
<i class="fas fa-exclamation-triangle"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg>
{{ fmt?.translate('WARNINGS.repo_truncated') }}
</div>
<div class="paper-inline-warning" role="alert" v-if="options?.hasSubmodules">
<i class="fas fa-exclamation-triangle"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg>
{{ fmt?.translate('WARNINGS.submodules_not_included') }}
</div>
<tree class="files" :file="files" :search-query="fileSearchQuery" :search-results="fileSearchResults" :page="viewState"></tree>
@@ -58,19 +58,19 @@
<li class="breadcrumb-item" v-for="(p, index) in paths" v-text="p"></li>
</ol>
<div class="status-bar-actions">
<a class="btn btn-sm" aria-label="Edit" v-if="options?.isAdmin || options?.isOwner" :href="safeUrl(&quot;/anonymize/&quot; + (repoId))"><i class="far fa-edit"></i><span class="d-none d-md-inline"> Edit</span></a>
<a class="btn btn-sm" aria-label="Edit" v-if="options?.isAdmin || options?.isOwner" :href="safeUrl(&quot;/anonymize/&quot; + (repoId))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg><span class="d-none d-md-inline"> Edit</span></a>
<button class="btn btn-sm" aria-label="Allow this document to run JavaScript" title="Scripts in this file are blocked by default. Enable them only if you trust the repository — the document stays isolated from your session either way." v-if="type == &#x27;html-doc&#x27; &amp;&amp; !showSource" :class="{&#x27;btn-active&#x27;: allowScripts}" @click="toggleAllowScripts()">
<i class="fab fa-js"></i><span class="d-none d-md-inline">
{{ allowScripts ? "JS on" : "JS off" }}</span>
</button>
<button class="btn btn-sm" aria-label="Toggle between the rendered document and its source" title="Toggle between the rendered document and its source" v-if="type == &#x27;html-doc&#x27;" @click="toggleSource()">
<i class="fas" :class="showSource ? &#x27;fa-eye&#x27; : &#x27;fa-code&#x27;"></i><span class="d-none d-md-inline">
<svg class="ui-icon" aria-hidden="true"><use :href="showSource ? '#project-icon-eye' : '#ui-icon-code'"></use></svg><span class="d-none d-md-inline">
{{ showSource ? "Rendered" : "Source" }}</span>
</button>
<a target="_self" class="btn btn-sm" aria-label="View raw current file" title="View the raw content of the current file" v-show="content != null" :href="safeUrl((url))"><i class="fas fa-file-alt"></i><span class="d-none d-md-inline"> Raw</span></a>
<a target="_self" class="btn btn-sm" aria-label="Download current file" title="Download the current file" v-show="content != null" :href="safeUrl((url) + &quot;&amp;download=true&quot;)"><i class="fas fa-download"></i><span class="d-none d-md-inline"> Download</span></a>
<a target="_self" class="btn btn-sm" aria-label="Download full repository as ZIP" title="Download the full repository as a ZIP archive" v-if="options?.download" :href="safeUrl(&quot;/api/repo/&quot; + (repoId) + &quot;/zip&quot;)"><i class="fas fa-file-archive"></i><span class="d-none d-md-inline"> Full repo ZIP</span></a>
<a target="_self" class="btn btn-sm" aria-label="Website" v-if="options?.hasWebsite" :href="safeUrl(&quot;/w/&quot; + (repoId) + &quot;/&quot;)"><i class="fas fa-globe"></i><span class="d-none d-md-inline"> Website</span></a>
<a target="_self" class="btn btn-sm" aria-label="View raw current file" title="View the raw content of the current file" v-show="content != null" :href="safeUrl((url))"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-file"></use></svg><span class="d-none d-md-inline"> Raw</span></a>
<a target="_self" class="btn btn-sm" aria-label="Download current file" title="Download the current file" v-show="content != null" :href="safeUrl((url) + &quot;&amp;download=true&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-download"></use></svg><span class="d-none d-md-inline"> Download</span></a>
<a target="_self" class="btn btn-sm" aria-label="Download full repository as ZIP" title="Download the full repository as a ZIP archive" v-if="options?.download" :href="safeUrl(&quot;/api/repo/&quot; + (repoId) + &quot;/zip&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-archive"></use></svg><span class="d-none d-md-inline"> Full repo ZIP</span></a>
<a target="_self" class="btn btn-sm" aria-label="Website" v-if="options?.hasWebsite" :href="safeUrl(&quot;/w/&quot; + (repoId) + &quot;/&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-globe"></use></svg><span class="d-none d-md-inline"> Website</span></a>
</div>
</div>
<div class="explorer-content">
+8 -27
View File
@@ -23,7 +23,7 @@
<!-- General -->
<h4 class="faq-cat">
<i class="fas fa-info-circle mr-2"></i>General
<svg class="ui-icon mr-2" aria-hidden="true"><use href="#ui-icon-info"></use></svg>General
</h4>
<div class="panel-group" id="faq-general" role="tablist" aria-multiselectable="true">
<div class="panel panel-default mb-3">
@@ -113,7 +113,7 @@
<!-- Features -->
<h4 class="faq-cat">
<i class="fas fa-cogs mr-2"></i>Features
<svg class="ui-icon mr-2" aria-hidden="true"><use href="#project-icon-filter"></use></svg>Features
</h4>
<div class="panel-group" id="faq-features" role="tablist" aria-multiselectable="true">
<div class="panel panel-default mb-3">
@@ -285,7 +285,7 @@ anonymous_github</code></pre>
<!-- Limitations -->
<h4 class="faq-cat">
<i class="fas fa-exclamation-triangle mr-2"></i>Limitations
<svg class="ui-icon mr-2" aria-hidden="true"><use href="#ui-icon-warning"></use></svg>Limitations
</h4>
<div class="panel-group" id="faq-limitations" role="tablist" aria-multiselectable="true">
<div class="panel panel-default mb-3">
@@ -322,7 +322,7 @@ anonymous_github</code></pre>
<!-- Privacy & Security -->
<h4 class="faq-cat">
<i class="fas fa-shield-alt mr-2"></i>Privacy &amp; Security
<svg class="ui-icon mr-2" aria-hidden="true"><use href="#ui-icon-shield"></use></svg>Privacy &amp; Security
</h4>
<div class="panel-group" id="faq-privacy" role="tablist" aria-multiselectable="true">
<div class="panel panel-default mb-3">
@@ -355,28 +355,9 @@ anonymous_github</code></pre>
</div>
<div id="permissions" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingPermissions">
<div class="panel-body p-3">
<p>
Anonymous GitHub only reads your repositories &mdash; it
never pushes commits, opens issues, modifies settings, or
deletes anything. From your perspective as a user, the
service is read-only.
</p>
<p>
However, GitHub's OAuth scopes do not offer a read-only
option for private repositories: the only scope that grants
access to private repos is <code>repo</code>, which is
documented as full read/write access. To support users who
want to anonymize a private repository, Anonymous GitHub
must request that scope, and GitHub then displays the
broader permission text at sign-in. The application itself
only ever performs read operations against the GitHub API.
</p>
<p>
If you only anonymize public repositories, the source code
is open and can be audited on the
<a href="https://github.com/tdurieux/anonymous_github/" target="_blank">GitHub repository</a>,
or you can self-host your own instance.
</p>
<p>The GitHub App has read-only access to the repositories you choose. You can change repository access on your <a href="/connections">GitHub connections</a> page.</p>
<p>Legacy GitHub OAuth includes write access to private repositories because GitHub's OAuth scopes do not offer a read-only option for them. Existing OAuth anonymizations and gists can still use this connection.</p>
<p>Anonymous GitHub reads repository content. It never pushes, modifies, or deletes files on GitHub.</p>
</div>
</div>
</div>
@@ -403,7 +384,7 @@ anonymous_github</code></pre>
<!-- Self-Hosting -->
<h4 class="faq-cat">
<i class="fas fa-server mr-2"></i>Self-Hosting
<svg class="ui-icon mr-2" aria-hidden="true"><use href="#project-icon-storage"></use></svg>Self-Hosting
</h4>
<div class="panel-group" id="faq-hosting" role="tablist" aria-multiselectable="true">
<div class="panel panel-default mb-3">
+15 -13
View File
@@ -6,40 +6,42 @@
</div>
<header class="pr-header">
<div class="pr-title-row">
<h1 class="paper-page-title pr-title">
<span v-if="details?.description" v-text="details?.description"></span>
<span class="text-muted" v-if="!details?.description">Untitled gist</span>
<a class="btn btn-sm" aria-label="Edit" v-if="options?.isAdmin || options?.isOwner" :href="safeUrl(&quot;/gist-anonymize/&quot; + (gistId))"><i class="far fa-edit"></i><span class="d-none d-md-inline"> Edit</span></a>
</h1>
<a class="btn btn-sm" aria-label="Edit" v-if="options?.isAdmin || options?.isOwner" :href="safeUrl(&quot;/gist-anonymize/&quot; + (gistId))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg><span class="d-none d-md-inline"> Edit</span></a>
</div>
<div class="pr-header-meta">
<span class="paper-pill" :class="{&#x27;good&#x27;: details?.isPublic, &#x27;warn&#x27;: !details?.isPublic}">
{{ details?.isPublic ? 'Public' : 'Secret' }}
</span>
<span class="pr-meta-item" v-if="details?.ownerLogin">
<i class="far fa-user"></i> @<span v-text="details?.ownerLogin"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user"></use></svg> @<span v-text="details?.ownerLogin"></span>
</span>
<span class="pr-meta-item" v-if="details?.updatedDate">
<i class="far fa-clock"></i> <span v-text="fmt?.date(details?.updatedDate)"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg> <span v-text="fmt?.date(details?.updatedDate)"></span>
</span>
<span class="pr-meta-item" v-if="details?.anonymizeDate">
<i class="fas fa-user-secret"></i> Anonymized <span v-text="fmt?.date(details?.anonymizeDate)"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymized <span v-text="fmt?.date(details?.anonymizeDate)"></span>
</span>
</div>
</header>
<nav class="paper-tabs" role="tablist" v-if="(details?.files &amp;&amp; details?.files?.length) || (details?.comments &amp;&amp; details?.comments?.length)">
<button class="paper-tab" type="button" role="tab" v-if="details?.files" :class="{&#x27;active&#x27;: tabState?.active == &#x27;files&#x27;}" @click="tabState.active = &#x27;files&#x27;">
<i class="fas fa-file-code"></i>
<button class="paper-tab" type="button" role="tab" v-if="details?.files" :class="{&#x27;active&#x27;: tabState?.active == &#x27;files&#x27;}" id="gist-files-tab" :aria-selected="tabState?.active === 'files'" aria-controls="gist-files-panel" :tabindex="tabState?.active === 'files' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="tabState.active = &#x27;files&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#project-type-gist"></use></svg>
<span v-text="fmt.plural(details?.files?.length, {&#x27;0&#x27;: &#x27;No files&#x27;, &#x27;one&#x27;: &#x27;1 file&#x27;, &#x27;other&#x27;: &#x27;{} files&#x27;})"></span>
</button>
<button class="paper-tab" type="button" role="tab" v-if="details?.comments" :class="{&#x27;active&#x27;: tabState?.active == &#x27;comments&#x27;}" @click="tabState.active = &#x27;comments&#x27;">
<i class="far fa-comment-dots"></i>
<button class="paper-tab" type="button" role="tab" v-if="details?.comments" :class="{&#x27;active&#x27;: tabState?.active == &#x27;comments&#x27;}" id="gist-comments-tab" :aria-selected="tabState?.active === 'comments'" aria-controls="gist-comments-panel" :tabindex="tabState?.active === 'comments' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="tabState.active = &#x27;comments&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-comment"></use></svg>
<span v-text="fmt.plural(details?.comments?.length, {&#x27;0&#x27;: &#x27;No comments&#x27;, &#x27;one&#x27;: &#x27;1 comment&#x27;, &#x27;other&#x27;: &#x27;{} comments&#x27;})"></span>
</button>
</nav>
<div class="paper-tab-content">
<div v-if="details?.files &amp;&amp; tabState?.active ==&#x27;files&#x27;">
<div role="tabpanel" id="gist-files-panel" aria-labelledby="gist-files-tab" v-if="details?.files &amp;&amp; tabState?.active ==&#x27;files&#x27;">
<ul class="pr-comments">
<li class="pr-comment" v-for="(file, index) in details?.files">
<div class="pr-comment-head">
@@ -49,18 +51,18 @@
<gist-file :file="file"></gist-file>
</li>
<li class="paper-table-empty" v-if="!details?.files?.length">
<i class="fas fa-file"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-file"></use></svg>
<span>No files in this gist.</span>
</li>
</ul>
</div>
<div v-if="details?.comments &amp;&amp; tabState?.active ==&#x27;comments&#x27;">
<div role="tabpanel" id="gist-comments-panel" aria-labelledby="gist-comments-tab" v-if="details?.comments &amp;&amp; tabState?.active ==&#x27;comments&#x27;">
<ul class="pr-comments">
<li class="pr-comment" v-for="(comment, index) in details?.comments">
<div class="pr-comment-head">
<span class="pr-comment-author" v-if="comment?.author">
<i class="far fa-user"></i> @<span v-text="comment?.author"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user"></use></svg> @<span v-text="comment?.author"></span>
</span>
<span class="pr-comment-date" v-if="comment?.updatedDate" v-text="fmt?.date(comment?.updatedDate)"></span>
</div>
@@ -69,7 +71,7 @@
</div>
</li>
<li class="paper-table-empty" v-if="!details?.comments?.length">
<i class="far fa-comment-dots"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-comment"></use></svg>
<span>No comments on this gist.</span>
</li>
</ul>
+16 -12
View File
@@ -13,28 +13,28 @@
</li>
<li class="nav-item" v-if="user">
<a class="nav-link" href="/dashboard" :class="{&#x27;active&#x27;: path == &#x27;/dashboard&#x27;}">
<i class="fas fa-th-large d-lg-none mr-1"></i>
<svg class="ui-icon d-lg-none mr-1" aria-hidden="true"><use href="#ui-icon-grid"></use></svg>
My work
</a>
</li>
<li class="nav-item" v-if="user">
<a class="nav-link" href="/anonymize" :class="{&#x27;active&#x27;:path == &#x27;/anonymize&#x27; || path == &#x27;/pull-request-anonymize&#x27;}">
<i class="fas fa-user-secret d-lg-none mr-1"></i>
<svg class="ui-icon d-lg-none mr-1" aria-hidden="true"><use href="#ui-icon-shield"></use></svg>
Anonymize
</a>
</li>
<li class="nav-item" v-if="user">
<a class="nav-link" href="/conferences" :class="{&#x27;active&#x27;:path == &#x27;/conferences&#x27;}">
<i class="fas fa-chalkboard-teacher d-lg-none mr-1"></i>
<svg class="ui-icon d-lg-none mr-1" aria-hidden="true"><use href="#project-icon-conference"></use></svg>
Conferences
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/faq" :class="{&#x27;active&#x27;:path == &#x27;/faq&#x27;}"><i class="fas fa-question-circle d-lg-none mr-1"></i>FAQ</a>
<a class="nav-link" href="/faq" :class="{&#x27;active&#x27;:path == &#x27;/faq&#x27;}"><svg class="ui-icon d-lg-none mr-1" aria-hidden="true"><use href="#ui-icon-help"></use></svg>FAQ</a>
</li>
<li class="nav-item" v-if="user &amp;&amp; user?.isAdmin">
<a class="nav-link" href="/admin/" :class="{&#x27;active&#x27;:path?.indexOf(&#x27;/admin&#x27;) === 0}">
<i class="fas fa-cog d-lg-none mr-1"></i>
<svg class="ui-icon d-lg-none mr-1" aria-hidden="true"><use href="#project-icon-filter"></use></svg>
Admin
</a>
</li>
@@ -47,19 +47,19 @@
anonymized repository often think it is about the paper, so the
label names the service and the tooltip says who to contact. -->
<li class="nav-item d-none d-lg-block">
<a class="nav-link" target="_blank" rel="noopener" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" title="Report a problem with the Anonymous GitHub service. Questions about an anonymized repository or paper go to its authors." data-offset="30"><i class="fas fa-bug d-lg-none mr-1" aria-hidden="true"></i>Report a bug
<a class="nav-link" target="_blank" rel="noopener" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" title="Report a problem with the Anonymous GitHub service. Questions about an anonymized repository or paper go to its authors." data-offset="30"><svg class="ui-icon d-lg-none mr-1" aria-hidden="true"><use href="#ui-icon-bug"></use></svg>Report a bug
</a>
</li>
<li class="nav-item" v-if="!isDarkMode">
<a class="nav-link nav-icon" href="#" title="Switch to dark mode" aria-label="Switch to dark mode" role="button" @click.prevent="darkMode(true);">
<i class="fas fa-moon" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-moon"></use></svg>
</a>
</li>
<li class="nav-item" v-if="isDarkMode">
<a class="nav-link nav-icon" href="#" title="Switch to light mode" aria-label="Switch to light mode" role="button" @click.prevent="darkMode(false);">
<i class="fas fa-sun" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-sun"></use></svg>
</a>
</li>
@@ -76,7 +76,7 @@
<div class="dropdown-menu dropdown-menu-right user-menu" aria-labelledby="navbarDropdownMenuLink">
<h6 class="dropdown-header">Signed in as @{{ user?.username }}</h6>
<a class="dropdown-item" href="/profile">
<i class="fas fa-sliders-h" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-filter"></use></svg>
<span>Settings<small>Defaults, quotas, account</small></span>
</a>
<a class="dropdown-item" href="/connections">
@@ -84,21 +84,25 @@
<span>GitHub connections<small>Repository access and OAuth</small></span>
</a>
<a class="dropdown-item" href="/claim">
<i class="fas fa-link" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-link"></use></svg>
<span>Claim an anonymization<small>Attach an existing mirror to your account</small></span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener">
<i class="fas fa-bug" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-bug"></use></svg>
<span>Report a bug<small>About this service, not a paper</small></span>
</a>
<a class="dropdown-item" href="https://github.com/tdurieux/anonymous_github/" target="_blank" rel="noopener">
<i class="fab fa-github" aria-hidden="true"></i>
<span>Source code</span>
</a>
<a class="dropdown-item" href="https://ko-fi.com/tdurieux" target="_blank" rel="noopener">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-heart"></use></svg>
<span>Support the project</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/api/user/logout" target="_self">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-logout"></use></svg>
<span>Sign out</span>
</a>
</div>
+14 -14
View File
@@ -9,8 +9,8 @@
</h1>
<p class="hero-subtitle">
Anonymous GitHub makes a read-only mirror of your repository, pull
request, or gist with every trace of identity removed, so you can
attach a link to your submission without breaking anonymity.
request, or gist. It redacts the terms you choose in text files,
so reviewers can browse your artifact through an anonymous link.
</p>
<div class="paper-cta-row">
@@ -34,7 +34,7 @@
<span class="dots"><i></i><i></i><i></i></span>
<span class="url">anonymous.4open.science/anonymize</span>
</div>
<img src="/imgs/anonymize.png" width="2880" height="1800" alt="The anonymize form with a live README preview showing the redactions">
<img src="/imgs/redaction-preview.png" width="1360" height="960" alt="A reviewer preview with the author, affiliation, and email replaced by anonymous terms">
</div>
</div>
</section>
@@ -117,9 +117,9 @@
<h2>Anonymize, review, <em>manage.</em></h2>
</div>
<p>
Every mirror is read-only and passes through the same filter, from the
README to a PDF's link targets. Monitor it, update it, or remove it the
day the decision is out.
Terms are redacted in text files. Enabled images and PDFs are served
unchanged, so review them for identifying content before sharing.
Monitor your mirror, update it, or remove it when the review is over.
</p>
</section>
<section class="paper-features paper-wrap" aria-label="Product features">
@@ -160,19 +160,19 @@
</div>
<div>
<div class="q">
<a href="/faq#howWork">Why does GitHub ask for write access at sign-in?</a>
<a href="/faq#permissions">What access does GitHub sign-in require?</a>
<p>
GitHub's OAuth scopes have no read-only option for private
repositories. Anonymous GitHub only ever reads your repositories; it
never pushes, modifies, or deletes anything.
The GitHub App has read-only access to the repositories you choose.
Legacy OAuth uses broader permissions and is still needed for gists.
Anonymous GitHub never writes to your repositories.
</p>
</div>
<div class="q">
<a href="/faq#formats">Which file formats are supported?</a>
<p>
Source code with syntax highlighting, Markdown, Jupyter notebooks,
PDFs, images, and GitHub Pages sites. Everything passes through the
same redaction filter.
PDFs, images, and GitHub Pages sites. Text files are anonymized;
enabled images and PDFs are served unchanged.
</p>
</div>
<div class="q">
@@ -191,7 +191,7 @@
<div class="paper-footer-mark">Anonymous <em>GitHub</em></div>
<p class="paper-footer-tag">
A read-only, identity-stripped mirror of your repository, built for
double-blind peer review.
double-anonymous peer review.
</p>
</div>
<div class="paper-footer-col">
@@ -215,7 +215,7 @@
</div>
<div class="paper-footer-rule"></div>
<div class="paper-footer-foot">
<span>&copy; {{ year || 2026 }} Anonymous GitHub &middot; MIT licensed</span>
<span>&copy; {{ year || 2026 }} Anonymous GitHub &middot; GPL-3.0 licensed</span>
<span class="paper-footer-meta">Built for reviewers, by researchers.</span>
</div>
</footer>
+71
View File
@@ -0,0 +1,71 @@
<svg class="ui-icon-definitions" aria-hidden="true" focusable="false">
<defs>
<symbol id="project-type-repo" viewBox="0 0 24 24">
<path d="M6 3h13v18H6a3 3 0 0 1 0-6h13M3 18V6a3 3 0 0 1 3-3M8 7h6"></path>
</symbol>
<symbol id="project-type-pr" viewBox="0 0 24 24">
<circle cx="6" cy="5" r="2.5"></circle><circle cx="6" cy="19" r="2.5"></circle><circle cx="18" cy="19" r="2.5"></circle>
<path d="M6 7.5v9M18 16.5V9a4 4 0 0 0-4-4h-3m3-3-3 3 3 3"></path>
</symbol>
<symbol id="project-type-gist" viewBox="0 0 24 24">
<path d="M14 3H5v18h14V8l-5-5v5h5M9 12l-2 2 2 2m6-4 2 2-2 2"></path>
</symbol>
<symbol id="project-icon-copy" viewBox="0 0 24 24"><rect x="8" y="8" width="12" height="13" rx="2"></rect><path d="M15 8V3H3v13h5"></path></symbol>
<symbol id="project-icon-more" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1"></circle><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle></symbol>
<symbol id="project-icon-eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Z"></path><circle cx="12" cy="12" r="3"></circle></symbol>
<symbol id="project-icon-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></symbol>
<symbol id="project-icon-conference" viewBox="0 0 24 24"><path d="m3 8 9-5 9 5H3Zm2 3v7m5-7v7m4-7v7m5-7v7M3 21h18"></path></symbol>
<symbol id="project-icon-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"></path></symbol>
<symbol id="project-icon-filter" viewBox="0 0 24 24"><path d="M3 6h18M3 12h18M3 18h18"></path><circle cx="8" cy="6" r="2"></circle><circle cx="16" cy="12" r="2"></circle><circle cx="9" cy="18" r="2"></circle></symbol>
<symbol id="project-icon-arrow" viewBox="0 0 24 24"><path d="M12 4v16m-5-5 5 5 5-5"></path></symbol>
<symbol id="project-icon-close" viewBox="0 0 24 24"><path d="m6 6 12 12M6 18 18 6"></path></symbol>
<symbol id="project-icon-chevron" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"></path></symbol>
<symbol id="project-icon-storage" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="7" rx="2"></rect><rect x="3" y="13" width="18" height="7" rx="2"></rect><path d="M7 7.5h.01M7 16.5h.01M11 7.5h6M11 16.5h6"></path></symbol>
<symbol id="project-icon-file" viewBox="0 0 24 24"><path d="M14 3H5v18h14V8l-5-5v5h5M9 12h6M9 16h6"></path></symbol>
<symbol id="project-icon-inbox" viewBox="0 0 24 24"><path d="m3 14 3-9h12l3 9v6H3v-6h5l2 3h4l2-3h5"></path></symbol>
<symbol id="ui-icon-arrow-left" viewBox="0 0 24 24"><path d="M20 12H4m6-6-6 6 6 6"/></symbol>
<symbol id="ui-icon-arrow-right" viewBox="0 0 24 24"><path d="M4 12h16m-6-6 6 6-6 6"/></symbol>
<symbol id="ui-icon-check" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6"/></symbol>
<symbol id="ui-icon-check-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="m7 12 3 3 7-7"/></symbol>
<symbol id="ui-icon-edit" viewBox="0 0 24 24"><path d="m15 4 5 5M4 20l5-1L21 7l-5-5L4 14v6Z"/></symbol>
<symbol id="ui-icon-external-link" viewBox="0 0 24 24"><path d="M14 3h7v7m0-7L10 14M10 3H3v18h18v-7"/></symbol>
<symbol id="ui-icon-key" viewBox="0 0 24 24"><circle cx="8" cy="8" r="5"/><path d="m12 12 9 9m-6-6 3-3m0 6 3-3"/></symbol>
<symbol id="ui-icon-refresh" viewBox="0 0 24 24"><path d="M20 8a8 8 0 0 0-14-3L3 8m0-5v5h5M4 16a8 8 0 0 0 14 3l3-3m0 5v-5h-5"/></symbol>
<symbol id="ui-icon-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7h.01"/></symbol>
<symbol id="ui-icon-warning" viewBox="0 0 24 24"><path d="m12 3 10 18H2L12 3Zm0 6v5m0 3h.01"/></symbol>
<symbol id="ui-icon-help" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9 9a3 3 0 0 1 6 0c0 2-3 2-3 4m0 4h.01"/></symbol>
<symbol id="ui-icon-user" viewBox="0 0 24 24"><circle cx="12" cy="7" r="4"/><path d="M4 21v-3a8 8 0 0 1 16 0v3"/></symbol>
<symbol id="ui-icon-users" viewBox="0 0 24 24"><circle cx="9" cy="7" r="4"/><path d="M2 21v-3a7 7 0 0 1 14 0v3M16 3a4 4 0 0 1 0 8m3 3a6 6 0 0 1 3 5v2"/></symbol>
<symbol id="ui-icon-shield" viewBox="0 0 24 24"><path d="m12 3 9 4v6c0 4-9 9-9 9S3 17 3 13V7l9-4Z"/></symbol>
<symbol id="ui-icon-code" viewBox="0 0 24 24"><path d="m7 6-6 6 6 6m10-12 6 6-6 6m-4-14-2 16"/></symbol>
<symbol id="ui-icon-comment" viewBox="0 0 24 24"><path d="M21 3H3v14h5l4 4v-4h9V3Z"/><path d="M7 8h10M7 12h6"/></symbol>
<symbol id="ui-icon-save" viewBox="0 0 24 24"><path d="M3 3h14l4 4v14H3V3Zm4 0v7h10V3M7 21v-7h10v7"/></symbol>
<symbol id="ui-icon-heart" viewBox="0 0 24 24"><path d="M12 21 3 12C-2 6 7-1 12 6c5-7 14 0 9 6l-9 9Z"/></symbol>
<symbol id="ui-icon-globe" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="4" ry="9"/><path d="M3 12h18"/></symbol>
<symbol id="ui-icon-hourglass" viewBox="0 0 24 24"><path d="M5 3h14M5 21h14M6 3v4l6 5 6-5V3M6 21v-4l6-5 6 5v4"/></symbol>
<symbol id="ui-icon-moon" viewBox="0 0 24 24"><path d="M20 14A9 9 0 0 1 10 3a9 9 0 1 0 10 11Z"/></symbol>
<symbol id="ui-icon-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 1v2m0 18v2M1 12h2m18 0h2M4 4l2 2m12 12 2 2M4 20l2-2M18 6l2-2"/></symbol>
<symbol id="ui-icon-grid" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></symbol>
<symbol id="ui-icon-logout" viewBox="0 0 24 24"><path d="M9 3H3v18h6M9 12h12m-4-4 4 4-4 4"/></symbol>
<symbol id="ui-icon-link" viewBox="0 0 24 24"><path d="m10 7 3-3a5 5 0 0 1 7 7l-3 3M7 10l-3 3a5 5 0 0 0 7 7l3-3M8 16l8-8"/></symbol>
<symbol id="ui-icon-chart" viewBox="0 0 24 24"><path d="M3 3v18h18M7 16V9m5 7V5m5 11v-5"/></symbol>
<symbol id="ui-icon-list" viewBox="0 0 24 24"><path d="M9 5h12M9 12h12M9 19h12m-18-14 1 1 2-2m-3 8 1 1 2-2m-3 8 1 1 2-2"/></symbol>
<symbol id="ui-icon-bug" viewBox="0 0 24 24"><rect x="7" y="7" width="10" height="14" rx="5"/><path d="M9 7V5a3 3 0 0 1 6 0v2M12 7v14M3 8l4 2m-4 5h4m-4 7 4-4M21 8l-4 2m4 5h-4m4 7-4-4"/></symbol>
<symbol id="ui-icon-chevron-left" viewBox="0 0 24 24"><path d="m15 5-7 7 7 7"/></symbol>
<symbol id="ui-icon-chevron-right" viewBox="0 0 24 24"><path d="m9 5 7 7-7 7"/></symbol>
<symbol id="ui-icon-download" viewBox="0 0 24 24"><path d="M12 3v12m-5-5 5 5 5-5M3 16v5h18v-5"/></symbol>
<symbol id="ui-icon-export" viewBox="0 0 24 24"><path d="M13 3H3v18h14v-6M12 9h9m-4-4 4 4-4 4"/></symbol>
<symbol id="ui-icon-clipboard" viewBox="0 0 24 24"><path d="M8 5H4v16h16V5h-4"/><rect x="8" y="2" width="8" height="6" rx="2"/></symbol>
<symbol id="ui-icon-ban" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="m6 6 12 12"/></symbol>
<symbol id="ui-icon-trash" viewBox="0 0 24 24"><path d="M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7m4-7v7"/></symbol>
<symbol id="ui-icon-broom" viewBox="0 0 24 24"><path d="m16 3-5 8m-7 5 6-6 6 4-3 7H3l1-5Zm1 5 4-6"/></symbol>
<symbol id="ui-icon-database" viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 4 18 4 18 0V5M3 12c0 4 18 4 18 0"/></symbol>
<symbol id="ui-icon-user-minus" viewBox="0 0 24 24"><circle cx="9" cy="7" r="4"/><path d="M2 21v-3a7 7 0 0 1 14 0v3m0-11h7"/></symbol>
<symbol id="ui-icon-user-shield" viewBox="0 0 24 24"><circle cx="8" cy="6" r="4"/><path d="M1 21v-4a7 7 0 0 1 10-6m7 0 5 2v4c0 3-5 6-5 6s-5-3-5-6v-4l5-2Z"/></symbol>
<symbol id="ui-icon-spinner" viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-9-9"/></symbol>
<symbol id="ui-icon-archive" viewBox="0 0 24 24"><path d="M3 3h18v5H3V3Zm2 5v13h14V8M10 3v3m4 3v3m-4 3v3m4 0v3"/></symbol>
<symbol id="ui-icon-calendar" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M7 2v6m10-6v6M3 11h18"/></symbol>
<symbol id="ui-icon-search" viewBox="0 0 24 24"><circle cx="10" cy="10" r="7"></circle><path d="m15 15 7 7"></path></symbol>
<symbol id="ui-icon-folder" viewBox="0 0 24 24"><path d="M3 20V4h6l2 3h10v13H3Z"/></symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

+3 -3
View File
@@ -37,7 +37,7 @@
<div class="form-group">
<label class="paper-field-label" for="url">Website</label>
<input type="url" class="form-control" name="url" id="url" placeholder="https://example.org" v-field="{ state: viewState, set: value =&gt; { options.url = value }, value: options?.url, form: &quot;conference&quot;, options: {} }" :class="{&#x27;is-invalid&#x27;: conference?.url?.invalid &amp;&amp; (conference?.url?.touched || conference?.submitted)}">
<div class="invalid-feedback" v-show="conference?.url?.errors?.invalid">
<div class="invalid-feedback" v-show="conference?.url?.errors?.url">
The url of the conference is invalid.
</div>
</div>
@@ -189,8 +189,8 @@
<div class="paper-settings-footer">
<div class="alert alert-danger" role="alert" v-if="error" v-text="error"></div>
<div class="alert alert-success" role="alert" v-if="message" v-text="message"></div>
<button id="send" type="submit" class="btn btn-ink" @click.prevent="submitForm($event, () =&gt; { submit($event) })">
{{ editionMode ? "Update" : "Create" }} conference
<button id="send" :disabled="saving" type="submit" class="btn btn-ink" @click.prevent="submitForm($event, () =&gt; { submit($event) })">
{{ saving ? (editionMode ? "Updating…" : "Creating…") : (editionMode ? "Update conference" : "Create conference") }}
</button>
</div>
</form>
+1 -1
View File
@@ -10,7 +10,7 @@
<div class="file-error container d-flex h-100" v-if="type == &#x27;rate_limited&#x27;">
<div class="paper-ratelimit-card m-auto" style="max-width:520px;">
<div class="paper-ratelimit-head">
<i class="fas fa-hourglass-half"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-hourglass"></use></svg>
<div>
<div class="paper-error-eyebrow">Temporarily paused</div>
<div class="paper-error-title">GitHub API rate limit reached</div>
+13 -13
View File
@@ -9,10 +9,10 @@
</div>
<div class="d-flex flex-wrap" style="gap: 6px">
<a href="/anonymize" class="btn btn-ink">
<i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> Anonymize Repo
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#project-icon-plus"></use></svg> Anonymize Repo
</a>
<a href="/pull-request-anonymize" class="btn btn-ink">
<i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> Anonymize PR
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#project-icon-plus"></use></svg> Anonymize PR
</a>
<a title="Claim the ownership of an existing anonymized repository." data-toggle="tooltip" data-placement="bottom" href="/claim" class="btn">
Claim
@@ -113,19 +113,19 @@
</div>
<div class="repo-meta">
<span title="Conference" v-if="pr?.conference">
<i class="fas fa-chalkboard-teacher"></i> {{ pr?.conference }}
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-conference"></use></svg> {{ pr?.conference }}
</span>
<span data-toggle="tooltip" data-placement="bottom" :title="&quot;Terms: &quot; + (pr?.options?.terms?.join(&#x27;, &#x27;))">
<i class="fas fa-shield-alt"></i> {{ fmt?.number(pr?.options?.terms?.length) }}
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> {{ fmt?.number(pr?.options?.terms?.length) }}
</span>
<span data-toggle="tooltip" data-placement="bottom" :title="&quot;Views: &quot; + (fmt?.number(pr?.pageView))">
<i class="far fa-eye"></i> {{ fmt?.number(pr?.pageView) }}
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> {{ fmt?.number(pr?.pageView) }}
</span>
<span data-toggle="tooltip" data-placement="bottom" :title="&quot;Last view: &quot; + (fmt?.date(pr?.lastView))">
<i class="far fa-calendar-alt"></i> {{ fmt?.humanTime(pr?.lastView) }}
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-calendar"></use></svg> {{ fmt?.humanTime(pr?.lastView) }}
</span>
<span v-if="pr?.options?.expirationMode!=&#x27;never&#x27; &amp;&amp; pr?.status == &#x27;ready&#x27;">
<i class="far fa-clock"></i> Expire: {{ fmt?.humanTime(pr?.options?.expirationDate) }}
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg> Expire: {{ fmt?.humanTime(pr?.options?.expirationDate) }}
</span>
</div>
</div>
@@ -136,26 +136,26 @@
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" :href="safeUrl(&quot;/pull-request-anonymize/&quot; + (pr?.pullRequestId))">
<i class="far fa-edit" aria-hidden="true"></i> Edit
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit
</a>
<a class="dropdown-item" href="#" v-show="pr?.status == &#x27;ready&#x27; || pr?.status == &#x27;error&#x27;" @click.prevent="updatePullRequest(pr)">
<i class="fas fa-sync"></i> Force update
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg> Force update
</a>
<a class="dropdown-item" href="#" v-show="pr?.status == &#x27;removed&#x27;" @click.prevent="updatePullRequest(pr)">
<i class="fas fa-check-circle"></i> Enable
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check-circle"></use></svg> Enable
</a>
<a class="dropdown-item" href="#" v-show="pr?.status == &#x27;ready&#x27;" @click.prevent="removePullRequest(pr)">
<i class="fas fa-trash-alt"></i> Remove
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Remove
</a>
<a class="dropdown-item" :href="safeUrl(&quot;/pr/&quot; + (pr?.pullRequestId) + &quot;/&quot;)">
<i class="fa fa-eye" aria-hidden="true"></i> View PR
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> View PR
</a>
</div>
</div>
</div>
</li>
<li class="repo-list-empty" v-if="filteredPullRequests?.length == 0">
<i class="fas fa-inbox"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-inbox"></use></svg>
<span>No pull requests to display.</span>
</li>
</ul>
+8 -27
View File
@@ -17,32 +17,11 @@
</aside>
<div class="paper-settings-main">
<!-- Quota: same markup as the dashboard, driven by quotaService -->
<!-- Account usage shares the dashboard component. -->
<section id="settings-quota" class="paper-settings-section">
<div class="paper-section-eyebrow">Quota</div>
<p class="paper-section-copy">What you are using across all your anonymizations. Conferences can lift these limits during a review window.</p>
<div class="quota-row" v-if="quota">
<div class="quota-item" v-for="(q, index) in [
{key: &#x27;repository&#x27;, label: &#x27;Repositories&#x27;, kind: &#x27;count&#x27;},
{key: &#x27;storage&#x27;, label: &#x27;Storage&#x27;, kind: &#x27;bytes&#x27;},
{key: &#x27;file&#x27;, label: &#x27;Files&#x27;, kind: &#x27;count&#x27;}
]">
<div class="quota-header">
<span class="quota-label">{{ q?.label }}</span>
<span class="quota-value" v-if="q?.kind === &#x27;count&#x27;">
{{ fmt?.number(quota[q?.key].used) }}<span v-if="!quota[q?.key].unlimited"> / {{ fmt?.number(quota[q?.key].total) }}</span>
<span class="quota-unlimited-tag" v-if="quota[q?.key].unlimited">Unlimited</span>
</span>
<span class="quota-value" v-if="q?.kind === &#x27;bytes&#x27;">
{{ fmt?.humanFileSize(quota[q?.key].used) }}<span v-if="!quota[q?.key].unlimited"> / {{ fmt?.humanFileSize(quota[q?.key].total) }}</span>
<span class="quota-unlimited-tag" v-if="quota[q?.key].unlimited">Unlimited</span>
</span>
</div>
<div class="quota-track" role="progressbar" aria-valuemin="0" :class="&#x27;quota-&#x27; + quota[q?.key].level" :aria-label="(q?.label) + &quot; quota&quot;" :aria-valuenow="(quota[q?.key].used)" :aria-valuemax="(quota[q?.key].unlimited ? quota[q?.key].used : quota[q?.key].total)" :aria-valuetext="(quota[q?.key].unlimited ? &#x27;unlimited&#x27; : fmt.number(quota[q?.key].percent, 0) + &#x27;% used&#x27;)">
<div class="quota-fill" v-if="!quota[q?.key].unlimited" :style="{width: quota[q?.key].percent + &#x27;%&#x27;}"></div>
</div>
</div>
</div>
<quota-usage :quota="quota" :fmt="fmt" v-if="quota"></quota-usage>
<div class="quota-row" aria-hidden="true" v-if="!quota">
<div class="quota-item" v-for="(i, index) in [1, 2, 3]">
<div class="quota-header"><span class="skeleton skeleton-line" style="width: 40%"></span><span class="skeleton skeleton-line" style="width: 25%"></span></div>
@@ -143,11 +122,13 @@ jane.smith@example.org" v-field="{ state: viewState, set: value =&gt; { terms =
</div>
</section>
<div class="draft-status" role="status" v-if="draftRestored">Your unsaved draft was restored. <button type="button" class="draft-discard" @click="discardDraft()">Discard draft</button></div>
<div class="draft-status" v-else-if="unsavedChanges">{{ draftSaved ? "Draft saved in this tab for 30 minutes." : "Unsaved changes. Keep this tab open until you save." }} <button type="button" class="draft-discard" @click="discardDraft()">{{ isUpdate ? "Restore saved settings" : "Discard changes" }}</button></div>
<div class="paper-settings-footer">
<div class="paper-inline-alert paper-inline-alert-error" role="alert" v-if="error"><i class="fas fa-exclamation-circle" aria-hidden="true"></i> <span v-text="error"></span></div>
<div class="paper-inline-alert paper-inline-alert-error" role="alert" v-if="error"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> <span v-text="error"></span></div>
<div class="paper-settings-actions">
<button id="save" type="submit" class="btn btn-ink" :disabled="saving">
<i class="fas fa-check mr-1" aria-hidden="true" v-if="message"></i>{{ saving ? 'Saving…' : (message ? 'Saved' : 'Save defaults') }}
<svg class="ui-icon mr-1" aria-hidden="true" v-if="message"><use href="#ui-icon-check"></use></svg>{{ saving ? 'Saving…' : (message ? 'Saved' : 'Save defaults') }}
</button>
<span class="paper-settings-hint">Applies to new anonymizations only. Existing ones keep their settings.</span>
</div>
@@ -178,9 +159,9 @@ jane.smith@example.org" v-field="{ state: viewState, set: value =&gt; { terms =
revokes the application's access to your GitHub account, and erases
your personal data (username, email, access tokens). This cannot be undone.
</p>
<div class="paper-inline-alert paper-inline-alert-error" role="alert" v-if="deleteError"><i class="fas fa-exclamation-circle" aria-hidden="true"></i> <span v-text="deleteError"></span></div>
<div class="paper-inline-alert paper-inline-alert-error" role="alert" v-if="deleteError"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> <span v-text="deleteError"></span></div>
<button type="button" class="btn btn-outline-danger" @click="deleteAccount()" :disabled="deletingAccount">
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>{{ deletingAccount ? 'Deleting…' : 'Delete my account' }}
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-trash"></use></svg>{{ deletingAccount ? 'Deleting…' : 'Delete my account' }}
</button>
</div>
</section>
+13 -11
View File
@@ -6,11 +6,13 @@
</div>
<header class="pr-header">
<div class="pr-title-row">
<h1 class="paper-page-title pr-title">
<span v-if="details?.title" v-text="details?.title"></span>
<span class="text-muted" v-if="!details?.title">Untitled pull request</span>
<a class="btn btn-sm" aria-label="Edit" v-if="options?.isAdmin || options?.isOwner" :href="safeUrl(&quot;/pull-request-anonymize/&quot; + (pullRequestId))"><i class="far fa-edit"></i><span class="d-none d-md-inline"> Edit</span></a>
</h1>
<a class="btn btn-sm" aria-label="Edit" v-if="options?.isAdmin || options?.isOwner" :href="safeUrl(&quot;/pull-request-anonymize/&quot; + (pullRequestId))"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-edit"></use></svg><span class="d-none d-md-inline"> Edit</span></a>
</div>
<div class="pr-header-meta">
<span class="paper-pill" :class="{&#x27;good&#x27;: details?.merged, &#x27;warn&#x27;: details?.state == &#x27;open&#x27;, &#x27;bad&#x27;: details?.state == &#x27;closed&#x27; &amp;&amp; !details?.merged}">
<span class="status-dot" :class="{&#x27;status-ready&#x27;: details?.merged, &#x27;status-error&#x27;: details?.state == &#x27;closed&#x27; &amp;&amp; !details?.merged}"></span>
@@ -20,10 +22,10 @@
<i class="fab fa-github"></i> <span v-text="details?.baseRepositoryFullName"></span>
</span>
<span class="pr-meta-item" v-if="details?.updatedDate">
<i class="far fa-clock"></i> <span v-text="fmt?.date(details?.updatedDate)"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg> <span v-text="fmt?.date(details?.updatedDate)"></span>
</span>
<span class="pr-meta-item" v-if="details?.anonymizeDate">
<i class="fas fa-user-secret"></i> Anonymized <span v-text="fmt?.date(details?.anonymizeDate)"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymized <span v-text="fmt?.date(details?.anonymizeDate)"></span>
</span>
</div>
</header>
@@ -34,26 +36,26 @@
</section>
<nav class="paper-tabs" role="tablist" v-if="details?.diff || details?.comments">
<button class="paper-tab" type="button" role="tab" v-if="details?.diff" :class="{&#x27;active&#x27;: tabState?.active == &#x27;diff&#x27;}" @click="tabState.active = &#x27;diff&#x27;">
<i class="fas fa-code"></i> Diff
<button class="paper-tab" type="button" role="tab" v-if="details?.diff" :class="{&#x27;active&#x27;: tabState?.active == &#x27;diff&#x27;}" id="pr-diff-tab" :aria-selected="tabState?.active === 'diff'" aria-controls="pr-diff-panel" :tabindex="tabState?.active === 'diff' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="tabState.active = &#x27;diff&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-code"></use></svg> Diff
</button>
<button class="paper-tab" type="button" role="tab" v-if="details?.comments" :class="{&#x27;active&#x27;: tabState?.active == &#x27;comments&#x27;}" @click="tabState.active = &#x27;comments&#x27;">
<i class="far fa-comment-dots"></i>
<button class="paper-tab" type="button" role="tab" v-if="details?.comments" :class="{&#x27;active&#x27;: tabState?.active == &#x27;comments&#x27;}" id="pr-comments-tab" :aria-selected="tabState?.active === 'comments'" aria-controls="pr-comments-panel" :tabindex="tabState?.active === 'comments' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="tabState.active = &#x27;comments&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-comment"></use></svg>
<span v-text="fmt.plural(details?.comments?.length, {&#x27;0&#x27;: &#x27;No comments&#x27;, &#x27;one&#x27;: &#x27;1 comment&#x27;, &#x27;other&#x27;: &#x27;{} comments&#x27;})"></span>
</button>
</nav>
<div class="paper-tab-content">
<div v-if="details?.diff &amp;&amp; tabState?.active ==&#x27;diff&#x27;">
<div role="tabpanel" id="pr-diff-panel" aria-labelledby="pr-diff-tab" v-if="details?.diff &amp;&amp; tabState?.active ==&#x27;diff&#x27;">
<div class="pr-diff" v-html="sanitize(fmt?.diff(details?.diff))"></div>
</div>
<div v-if="details?.comments &amp;&amp; tabState?.active ==&#x27;comments&#x27;">
<div role="tabpanel" id="pr-comments-panel" aria-labelledby="pr-comments-tab" v-if="details?.comments &amp;&amp; tabState?.active ==&#x27;comments&#x27;">
<ul class="pr-comments">
<li class="pr-comment" v-for="(comment, index) in details?.comments">
<div class="pr-comment-head">
<span class="pr-comment-author" v-if="comment?.author">
<i class="far fa-user"></i> @<span v-text="comment?.author"></span>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user"></use></svg> @<span v-text="comment?.author"></span>
</span>
<span class="pr-comment-date" v-if="comment?.updatedDate" v-text="fmt?.date(comment?.updatedDate)"></span>
</div>
@@ -62,7 +64,7 @@
</div>
</li>
<li class="paper-table-empty" v-if="!details?.comments?.length">
<i class="far fa-comment-dots"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-comment"></use></svg>
<span>No comments on this pull request.</span>
</li>
</ul>
+19
View File
@@ -0,0 +1,19 @@
<div class="quota-row quota-usage" v-if="quota">
<div class="quota-item" :class="'quota-' + quota[q.key].level" :key="q.key" v-for="q in [
{key: 'repository', label: 'Repositories', kind: 'count', icon: 'project-type-repo'},
{key: 'storage', label: 'Storage', kind: 'bytes', icon: 'project-icon-storage'},
{key: 'file', label: 'Files', kind: 'count', icon: 'project-icon-file'}
]">
<div class="quota-header">
<span class="quota-label"><svg class="project-type-svg" aria-hidden="true"><use :href="'#' + q.icon"></use></svg>{{ q.label }}</span>
<span class="quota-percent" v-if="!quota[q.key].unlimited">{{ fmt.number(quota[q.key].percent, 0) }}% used</span>
<span class="quota-unlimited-tag" v-if="quota[q.key].unlimited">Unlimited</span>
</div>
<div class="quota-value"><strong>{{ formatAmount(quota[q.key].used, q.kind) }}</strong><span v-if="!quota[q.key].unlimited"> / {{ formatAmount(quota[q.key].total, q.kind) }}</span></div>
<div class="quota-track" v-if="!quota[q.key].unlimited" role="progressbar" aria-valuemin="0" :class="'quota-' + quota[q.key].level" :aria-label="q.label + ' quota'" :aria-valuenow="Math.min(quota[q.key].used, quota[q.key].total)" :aria-valuemax="quota[q.key].total" :aria-valuetext="fmt.number(quota[q.key].percent, 0) + '% used'">
<div class="quota-fill" v-if="!quota[q?.key].unlimited" :style="{width: quota[q.key].percent + '%'}"></div>
</div>
<p class="quota-remaining" v-if="!quota[q.key].unlimited &amp;&amp; quota[q.key].used &lt; quota[q.key].total">{{ formatAmount(quota[q.key].total - quota[q.key].used, q.kind) }} {{ q.key === 'repository' ? 'repositories available' : q.key === 'file' ? 'files available' : 'available' }}</p>
<p class="quota-remaining quota-limit" v-if="!quota[q.key].unlimited &amp;&amp; quota[q.key].used &gt;= quota[q.key].total">{{ quota[q.key].used &gt; quota[q.key].total ? 'Limit exceeded' : 'Limit reached' }}</p>
</div>
</div>
+5 -5
View File
@@ -1,5 +1,5 @@
<div class="container paper-page auth-page">
<a class="auth-back" href="/"><i class="fas fa-arrow-left" aria-hidden="true"></i> Anonymous GitHub</a>
<a class="auth-back" href="/"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-arrow-left"></use></svg> Anonymous GitHub</a>
<div class="auth-layout">
<header class="auth-intro">
<div class="paper-eyebrow">Your research, ready for review</div>
@@ -18,16 +18,16 @@
<h2>Continue with GitHub</h2>
<p>Connect through the GitHub App with read-only repository access.</p>
<ul class="auth-permissions">
<li><i class="fas fa-check" aria-hidden="true"></i> You choose which repositories to share</li>
<li><i class="fas fa-check" aria-hidden="true"></i> No permission to write to your code</li>
<li><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check"></use></svg> You choose which repositories to share</li>
<li><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-check"></use></svg> No permission to write to your code</li>
</ul>
<a class="btn btn-ink auth-continue" target="_self" href="/github/app/login">Sign in with GitHub <i class="fas fa-arrow-right" aria-hidden="true"></i></a>
<a class="btn btn-ink auth-continue" target="_self" href="/github/app/login">Sign in with GitHub <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-arrow-right"></use></svg></a>
<small>Your existing anonymizations and connections stay with your account.</small>
</section>
<section class="auth-legacy" v-if="!site_options?.GITHUB_APP_ENABLED &amp;&amp; site_options?.GITHUB_OAUTH_ENABLED &amp;&amp; !accountRecovery">
<div class="connection-title"><h2>Continue with GitHub</h2></div>
<p>Sign in to access your account. GitHub OAuth includes write access to private repositories.</p>
<a class="connection-link" target="_self" href="/github/login">Sign in with GitHub <i class="fas fa-arrow-right" aria-hidden="true"></i></a>
<a class="connection-link" target="_self" href="/github/login">Sign in with GitHub <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-arrow-right"></use></svg></a>
</section>
<p role="status" v-if="site_options &amp;&amp; !site_options.GITHUB_APP_ENABLED &amp;&amp; !site_options.GITHUB_OAUTH_ENABLED">GitHub sign-in is currently unavailable. Please try again later.</p>
</div>
+14 -14
View File
@@ -1,4 +1,4 @@
<div class="container paper-page">
<div class="container paper-page status-page">
<div class="paper-crumbs">Anonymization &nbsp;/&nbsp; <span class="here">Status</span></div>
<div class="d-flex align-items-end justify-content-between flex-wrap" style="gap: 12px;">
<div>
@@ -22,7 +22,7 @@
<div class="paper-ratelimit-card" role="status" v-if="rateLimitResetAt">
<div class="paper-ratelimit-head">
<i class="fas fa-hourglass-half"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-hourglass"></use></svg>
<div>
<div class="paper-error-eyebrow">Temporarily paused</div>
<div class="paper-error-title">GitHub API rate limit reached</div>
@@ -41,7 +41,7 @@
<div class="paper-error-card" role="alert" v-if="repo?.status == &#x27;error&#x27; &amp;&amp; !rateLimitResetAt">
<div class="paper-error-head">
<i class="fas fa-exclamation-triangle"></i>
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-warning"></use></svg>
<div>
<div class="paper-error-eyebrow">Anonymization failed</div>
<div class="paper-error-title">Something went wrong while preparing this repository.</div>
@@ -55,9 +55,9 @@
<li>If you just signed in, the access token may need a moment to propagate &mdash; try again.</li>
</ul>
<div class="paper-error-actions">
<a class="btn btn-ink" :href="safeUrl(&quot;/anonymize/&quot; + (repoId))"><i class="far fa-edit mr-1"></i> Edit anonymization</a>
<a class="btn" href="/faq"><i class="far fa-question-circle mr-1"></i> Read the FAQ</a>
<a class="btn" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener"><i class="fas fa-bug mr-1" aria-hidden="true"></i> Report a bug in Anonymous GitHub</a>
<a class="btn btn-ink" :href="safeUrl(&quot;/anonymize/&quot; + (repoId))"><svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-edit"></use></svg> Edit anonymization</a>
<a class="btn" href="/faq"><svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-help"></use></svg> Read the FAQ</a>
<a class="btn" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener"><svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-bug"></use></svg> Report a bug in Anonymous GitHub</a>
</div>
</div>
@@ -74,10 +74,10 @@
<div class="anonymize-submit-bar" v-if="repo?.status == &#x27;ready&#x27;">
<a class="btn btn-ink" target="_self" :href="safeUrl(&quot;/r/&quot; + (repoId) + &quot;/&quot;)">
<i class="far fa-eye mr-1"></i> Go to anonymized repository
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#project-icon-eye"></use></svg> Go to anonymized repository
</a>
<a class="btn" target="_self" v-if="repo?.options?.page" :href="safeUrl(&quot;/w/&quot; + (repoId) + &quot;/&quot;)">
<i class="fas fa-globe mr-1"></i> Go to anonymized GitHub page
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-globe"></use></svg> Go to anonymized GitHub page
</a>
</div>
</section>
@@ -91,19 +91,19 @@
<div class="paper-support-grid">
<a class="paper-support-card" href="https://github.com/tdurieux/anonymous_github/" target="_blank" rel="noopener">
<div class="paper-support-eyebrow"><i class="fas fa-code-branch"></i> Contribute</div>
<div class="paper-support-eyebrow"><svg class="ui-icon" aria-hidden="true"><use href="#project-type-repo"></use></svg> Contribute</div>
<p>Collaborate by implementing new features and fixing bugs. Help with new file formats or deployment is welcome.</p>
<span class="paper-support-cta">Open on GitHub <i class="fas fa-arrow-right"></i></span>
<span class="paper-support-cta">Open on GitHub <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-arrow-right"></use></svg></span>
</a>
<a class="paper-support-card" href="https://github.com/tdurieux/anonymous_github/issues/new?template=issue_report.yml" target="_blank" rel="noopener">
<div class="paper-support-eyebrow"><i class="far fa-comment-dots"></i> Feedback</div>
<div class="paper-support-eyebrow"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-comment"></use></svg> Feedback</div>
<p>Tell us about bugs and missing features in Anonymous GitHub itself. Questions about an anonymized repository go to its authors.</p>
<span class="paper-support-cta">File an issue <i class="fas fa-arrow-right"></i></span>
<span class="paper-support-cta">File an issue <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-arrow-right"></use></svg></span>
</a>
<a class="paper-support-card" href="https://github.com/sponsors/tdurieux/" target="_blank" rel="noopener">
<div class="paper-support-eyebrow"><i class="fas fa-heart"></i> Sponsor</div>
<div class="paper-support-eyebrow"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-heart"></use></svg> Sponsor</div>
<p>Server costs hover around $25 a month. Any contribution helps keep the lights on.</p>
<span class="paper-support-cta">GitHub Sponsors <i class="fas fa-arrow-right"></i></span>
<span class="paper-support-cta">GitHub Sponsors <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-arrow-right"></use></svg></span>
</a>
</div>