merge: reconcile paid hosting with latest main

This commit is contained in:
Thomas Durieux committed 2026-11-02 21:12:29 +00:00
commit ded6f905fd
180 files changed
+21709 -3614

No files matched your search

+2 -2
View File
@@ -5,7 +5,7 @@
<h1 class="paper-page-title">Errors</h1>
<div class="errors-actions">
<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>
<button class="btn btn-sm btn-danger-soft" type="button" @click="clearAll()"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-trash"></use></svg> Clear all</button>
</div>
</header>
@@ -128,7 +128,7 @@
</div>
<div class="col-sev">
<span class="sev-dot" :class="&#x27;sev-&#x27; + row?._bucket"></span>
<span class="sev-label">{{ fmt?.uppercase(row?._bucket) }}</span>
<span class="sev-label">{{ fmt?.title(row?._bucket) }}</span>
</div>
<div class="col-mod"><span class="pill pill-module">{{ row?.module }}</span></div>
<div class="col-msg">
+5 -5
View File
@@ -1,5 +1,5 @@
<div class="container paper-page admin-page overview-page">
<div class="paper-crumbs">Admin &middot; System Health</div>
<div class="paper-crumbs">Admin &nbsp;/&nbsp; <span class="here">Overview</span></div>
<div class="ov-header">
<h1 class="paper-page-title">Overview</h1>
@@ -105,11 +105,11 @@
<div class="ov-chart-card">
<div class="ov-chart-head">
<span class="ov-chart-title">Users &middot; 30d</span>
<span class="ov-chart-legend"><span class="ov-dot-legend ov-dot-user-fill"></span>total users</span>
<span class="ov-chart-legend"><span class="ov-dot-legend ov-dot-user-fill"></span>total users &middot; {{ fmt?.number(historyRange(&#x27;nbUsers&#x27;).min) }}&ndash;{{ fmt?.number(historyRange(&#x27;nbUsers&#x27;).max) }}</span>
</div>
<div class="ov-spark-bars" v-if="data?.history?.length">
<div class="ov-spark-col has-tip" v-for="(d, index) in data?.history" :key="index" :data-tip="(historyLabel(d)) + &quot;: &quot; + (fmt?.number(d?.nbUsers)) + &quot; users&quot;" :aria-label="(historyLabel(d)) + &quot;: &quot; + (fmt?.number(d?.nbUsers)) + &quot; users&quot;">
<span class="ov-spark-fill ov-spark-fill-user" :style="{height: historyBarH(d, &#x27;nbUsers&#x27;) + &#x27;px&#x27;}"></span>
<span class="ov-spark-fill ov-spark-fill-user" :style="{height: historyRangeH(d, &#x27;nbUsers&#x27;) + &#x27;px&#x27;}"></span>
</div>
</div>
<div class="ov-spark-x" v-if="data?.history?.length">
@@ -308,7 +308,7 @@
<thead><tr><th scope="col">Route</th><th scope="col" class="num">Requests</th><th scope="col" class="num">Average</th><th scope="col" class="num">First byte p95</th><th scope="col" class="num">p95</th><th scope="col" class="num">p99</th><th scope="col" class="num">Interrupted</th><th scope="col" class="num">5xx</th><th scope="col" class="num">Slow</th></tr></thead>
<tbody><tr v-for="row in performance.routes" :key="row.service + row.method + row.route">
<th scope="row" class="ov-metric-name"><span>{{ row.route }}</span><small>{{ row.service }} &middot; {{ row.method }}</small></th>
<td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ row.avgMs }} ms</td>
<td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ formatMs(row.avgMs) }}</td>
<td class="num">{{ firstByteBound(row) }}</td><td class="num">{{ latencyBound(row, 'p95UpperMs') }}</td><td class="num">{{ latencyBound(row, 'p99UpperMs') }}</td>
<td class="num" :class="{'ov-metric-warn': row.aborted > 0}">{{ fmt.number(row.aborted) }}</td><td class="num" :class="{'ov-metric-warn': row.errors > 0}">{{ fmt.number(row.errors) }}</td><td class="num" :class="{'ov-metric-warn': row.slow > 0}">{{ fmt.number(row.slow) }}</td>
</tr></tbody>
@@ -340,7 +340,7 @@
<caption class="sr-only">Work stages and cache results</caption>
<thead><tr><th scope="col">Work stage</th><th scope="col" class="num">Samples</th><th scope="col" class="num">Average</th><th scope="col" class="num">p95</th></tr></thead>
<tbody><tr v-for="row in performance.stages.filter(row => row.metric !== 'first_byte')" :key="row.service + row.metric">
<th scope="row" class="ov-metric-name"><span>{{ row.metric }}</span><small>{{ row.service }}</small></th><td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ row.avgMs }} ms</td><td class="num">{{ latencyBound(row, 'p95UpperMs') }}</td>
<th scope="row" class="ov-metric-name"><span>{{ row.metric }}</span><small>{{ row.service }}</small></th><td class="num">{{ fmt.number(row.count) }}</td><td class="num">{{ formatMs(row.avgMs) }}</td><td class="num">{{ latencyBound(row, 'p95UpperMs') }}</td>
</tr></tbody>
</table>
</div>
+28 -28
View File
@@ -11,7 +11,7 @@
<button class="btn btn-sm" :class="{active: range == &#x27;7d&#x27;}" @click="setRange(&#x27;7d&#x27;)">7d</button>
</div>
<button class="btn btn-sm" @click="pauseAll()">Pause all</button>
<button class="btn btn-sm btn-dark" @click="drainSelected()">Drain {{ selectedQueue }}</button>
<button class="btn btn-sm btn-danger-soft" @click="drainSelected()">Drain {{ selectedQueue }}</button>
</div>
</div>
@@ -44,7 +44,7 @@
<!-- Detail: throughput chart + stats panel -->
<div class="q-detail" v-if="selectedStats">
<div class="q-throughput">
<div class="q-section-label">{{ selectedQueue }}&middot;throughput <span class="q-section-right"><span class="q-legend-completed">&#9679;</span> completed <span class="q-legend-failed">&#9679;</span> failed <span class="q-legend-exec">- -</span> avg time &middot; {{ fmt?.uppercase(range) }}</span></div>
<div class="q-section-label">{{ selectedQueue }} &middot; throughput <span class="q-section-right"><span class="q-legend-completed">&#9679;</span> completed <span class="q-legend-failed">&#9679;</span> failed <span class="q-legend-exec">- -</span> avg time &middot; {{ range }}</span></div>
<div class="q-chart-wrap">
<canvas id="q-throughput-chart" height="180"></canvas>
<div id="q-chart-tooltip" class="q-chart-tooltip" style="display:none;"></div>
@@ -52,30 +52,30 @@
</div>
</div>
<div class="q-stats-panel">
<div class="q-section-label">{{ selectedQueue }}&middot;stats</div>
<div class="q-section-label">{{ selectedQueue }} &middot; stats</div>
<div class="q-stats-grid">
<div class="q-stat">
<div class="q-stat-label">WAITING</div>
<div class="q-stat-label">Waiting</div>
<div class="q-stat-value" v-text="selectedStats?.counts?.waiting || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">ACTIVE</div>
<div class="q-stat-label">Active</div>
<div class="q-stat-value" v-text="selectedStats?.counts?.active || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">COMPLETED (24H)</div>
<div class="q-stat-label">Completed (24h)</div>
<div class="q-stat-value" v-text="fmt?.number(selectedStats?.completed24h)"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">FAILED (24H)</div>
<div class="q-stat-label">Failed (24h)</div>
<div class="q-stat-value" v-text="selectedStats?.failed24h || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">DELAYED</div>
<div class="q-stat-label">Delayed</div>
<div class="q-stat-value" v-text="selectedStats?.counts?.delayed || 0"></div>
</div>
<div class="q-stat">
<div class="q-stat-label">WORKERS</div>
<div class="q-stat-label">Workers</div>
<div class="q-stat-value" v-text="selectedStats?.workers || 0"></div>
</div>
</div>
@@ -94,7 +94,7 @@
<!-- Jobs table -->
<div class="q-jobs">
<div class="q-jobs-header">
<div class="q-section-label">ALL JOBS &middot; {{ fmt?.uppercase(selectedQueue) }}</div>
<div class="q-section-label">All jobs &middot; {{ selectedQueue }}</div>
<div class="q-state-filters">
<label class="q-state-toggle" v-for="(s, index) in allStates">
<input type="checkbox" v-field="{ state: viewState, set: value =&gt; { stateFilter[s] = value }, value: stateFilter[s], form: null, options: {} }">
@@ -115,12 +115,12 @@
<table class="q-table" v-if="filteredJobs().length &gt; 0">
<thead>
<tr>
<th>STATE</th>
<th>JOB ID</th>
<th>PAYLOAD</th>
<th>ATTEMPTS</th>
<th>DURATION</th>
<th>PROGRESS</th>
<th>State</th>
<th>Job id</th>
<th>Payload</th>
<th>Attempts</th>
<th>Duration</th>
<th>Progress</th>
<th></th>
</tr>
</thead>
@@ -156,52 +156,52 @@
<div class="q-job-detail">
<div class="q-job-detail-grid">
<div class="q-job-detail-item">
<span class="q-job-detail-label">JOB ID</span>
<span class="q-job-detail-label">Job id</span>
<span class="q-job-detail-value"><a target="_blank" v-text="job?.id" :href="safeUrl(&quot;/r/&quot; + (job?.id))"></a></span>
</div>
<div class="q-job-detail-item">
<span class="q-job-detail-label">STATE</span>
<span class="q-job-detail-label">State</span>
<span class="q-job-detail-value"><span v-text="job?._state" :class="&quot;q-state-badge q-state-&quot; + (job?._state)"></span></span>
</div>
<div class="q-job-detail-item" v-if="job?.data?.repoId">
<span class="q-job-detail-label">REPO ID</span>
<span class="q-job-detail-label">Repo ID</span>
<span class="q-job-detail-value" v-text="job?.data?.repoId"></span>
</div>
<div class="q-job-detail-item" v-if="job?.timestamp">
<span class="q-job-detail-label">CREATED</span>
<span class="q-job-detail-label">Created</span>
<span class="q-job-detail-value" v-text="humanTime(job?.timestamp)"></span>
</div>
<div class="q-job-detail-item" v-if="job?._state == &#x27;delayed&#x27; &amp;&amp; job?.delayUntil">
<span class="q-job-detail-label">RETRY AT</span>
<span class="q-job-detail-label">Retry at</span>
<span class="q-job-detail-value">{{ humanTime(job?.delayUntil) }} ({{ delayCountdown(job?.delayUntil) }})</span>
</div>
<div class="q-job-detail-item" v-if="job?.processedOn">
<span class="q-job-detail-label">PROCESSED</span>
<span class="q-job-detail-label">Processed</span>
<span class="q-job-detail-value" v-text="humanTime(job?.processedOn)"></span>
</div>
<div class="q-job-detail-item" v-if="job?.finishedOn">
<span class="q-job-detail-label">FINISHED</span>
<span class="q-job-detail-label">Finished</span>
<span class="q-job-detail-value" v-text="humanTime(job?.finishedOn)"></span>
</div>
<div class="q-job-detail-item" v-if="job?.attemptsMade">
<span class="q-job-detail-label">ATTEMPTS</span>
<span class="q-job-detail-label">Attempts</span>
<span class="q-job-detail-value" v-text="job?.attemptsMade"></span>
</div>
<div class="q-job-detail-item" v-if="job?.progress &amp;&amp; job?.progress?.status">
<span class="q-job-detail-label">STATUS</span>
<span class="q-job-detail-label">Status</span>
<span class="q-job-detail-value" v-text="job?.progress?.status"></span>
</div>
<div class="q-job-detail-item" v-if="jobProgressPct(job) !== null">
<span class="q-job-detail-label">PROGRESS</span>
<span class="q-job-detail-label">Progress</span>
<span class="q-job-detail-value" v-text="jobProgressPct(job) + &#x27;%&#x27;"></span>
</div>
</div>
<div class="q-job-detail-error" v-if="job?.failedReason">
<span class="q-job-detail-label">ERROR</span>
<span class="q-job-detail-label">Error</span>
<div class="q-error-reason" v-text="job?.failedReason"></div>
</div>
<div v-if="job?.stacktrace?.length">
<span class="q-job-detail-label">STACKTRACE</span>
<span class="q-job-detail-label">Stacktrace</span>
<pre class="q-error-stack" v-for="(stack, index) in job?.stacktrace" :key="index"><code v-text="stack"></code></pre>
</div>
<div class="q-job-detail-actions">
+1 -1
View File
@@ -82,7 +82,7 @@
<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;<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>
<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) }} {{ repo?.options?.terms?.length === 1 ? 'term' : 'terms' }}</span>
</div>
</div>
</div>
+5 -4
View File
@@ -1,5 +1,5 @@
<div class="container paper-page admin-page">
<div class="paper-crumbs"><a href="/admin/users">Users</a> &nbsp;/&nbsp; <span class="here">{{ userInfo?.username || 'Profile' }}</span></div>
<div class="paper-crumbs"><a href="/admin/">Admin</a> &nbsp;/&nbsp; <a href="/admin/users">Users</a> &nbsp;/&nbsp; <span class="here">{{ userInfo?.username || 'Profile' }}</span></div>
<h1 class="paper-page-title">{{ userInfo?.username || 'User' }}</h1>
<nav class="admin-nav">
@@ -40,7 +40,8 @@
<div class="detail-value">{{ userInfo?.emails?.[0]?.email }}</div>
<div class="detail-label">Access token</div>
<div class="detail-value" style="font-family: var(--font-mono); font-size: 0.85rem;">{{ userInfo?.accessTokens?.github }}</div>
<div class="detail-value" style="font-family: var(--font-mono); font-size: 0.85rem;" v-if="userInfo?.accessTokens?.github">{{ userInfo?.accessTokens?.github }}</div>
<div class="detail-value text-muted" v-if="!userInfo?.accessTokens?.github">None stored</div>
<div class="detail-label">GitHub</div>
<div class="detail-value">
@@ -98,7 +99,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"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-plus"></use></svg> Generate</button>
<button type="submit" class="btn btn-ink"><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">
@@ -180,7 +181,7 @@
<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;<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>
<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) }} {{ repo?.options?.terms?.length === 1 ? 'term' : 'terms' }}</span>
</div>
</div>
</div>
+27 -4
View File
@@ -27,14 +27,14 @@
<small class="form-text" style="color: var(--ink-muted);">
Repository, pull request (…/pull/42) and gist (gist.github.com/…) URLs are all accepted.
</small>
<aside class="repo-access-details" aria-label="GitHub access" v-if="!sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<aside class="repo-access-details" aria-label="GitHub access" v-if="!sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected">
<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 <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><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Anonymized content is public, even from private repositories.</span></div>
<div class="repo-access-note"><span v-if="error &amp;&amp; detectedType !== 'gist' &amp;&amp; githubConnection === 'github-app'">Signing in does not grant access to private repositories. Use Manage access to select this repository, then return here. Organization access may require administrator approval.</span><span><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Anonymized content is public, even from private repositories.</span></div>
</aside>
</div>
</div>
@@ -105,14 +105,37 @@
</div>
</div>
<aside class="repo-access-details" aria-label="GitHub access" v-if="sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<aside class="repo-access-details" aria-label="Previous repository configuration" v-if="!isUpdate &amp;&amp; previousConfigurations.length">
<p role="status">You have already anonymized this repository. Use the existing configuration to keep its anonymous link and avoid creating another repository.</p>
<div class="form-group" v-if="previousConfigurations.length > 1">
<label class="paper-field-label" for="previousConfiguration">Previous anonymization</label>
<select id="previousConfiguration" class="form-control" v-model="previousConfigurationId">
<option v-for="previous in previousConfigurations" :key="previous.repoId" :value="previous.repoId">{{ previous.repoId }}</option>
</select>
</div>
<p v-else>{{ previousConfigurationId }}</p>
<p>You can review and update its settings before saving.</p>
<div class="repo-access-controls">
<a class="btn btn-ink" :href="existingConfigurationUrl()">Use existing configuration</a>
<button type="button" class="btn btn-link" @click="previousConfigurations = []">Create a separate anonymization</button>
</div>
</aside>
<aside class="repo-access-details" aria-label="GitHub access" v-if="sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected">
<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 <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><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Anonymized content is public, even from private repositories.</span></div>
<div class="repo-access-note"><span v-if="error &amp;&amp; detectedType !== 'gist' &amp;&amp; githubConnection === 'github-app'">Signing in does not grant access to private repositories. Use Manage access to select this repository, then return here. Organization access may require administrator approval.</span><span><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Anonymized content is public, even from private repositories.</span></div>
</aside>
<aside class="repo-access-details" v-if="isUpdate &amp;&amp; detectedType === 'repo' &amp;&amp; role === 'owner' &amp;&amp; accessRepairAllowed &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnection === 'github-app' &amp;&amp; sourceUrl === 'https://github.com/' + _originalFullName">
<p>If this repository became private or its App installation changed, repair its access here. This keeps the same anonymous URL and published commit.</p>
<button type="button" class="btn btn-outline-ink" :disabled="repairingGitHubAccess" @click="repairGitHubAccess()">Repair GitHub access</button>
<button type="button" class="btn btn-outline-ink" @click="grantGitHubAccess()">Manage access</button>
<p v-if="accessRepairMessage" role="status">{{ accessRepairMessage }}</p>
</aside>
<aside class="repo-access-details" v-if="isUpdate &amp;&amp; detectedType === 'repo' &amp;&amp; role === 'owner' &amp;&amp; sourceUrl === 'https://github.com/' + _originalFullName">
+2 -2
View File
@@ -10,14 +10,14 @@
<form class="form needs-validation" name="claimForm" novalidate v-form="viewState" @submit.prevent="submitForm($event, () =&gt; { claim() })">
<div class="form-group">
<label class="paper-field-label" for="repoUrl">GitHub repository URL</label>
<input type="text" class="form-control" name="repoUrl" id="repoUrl" required v-field="{ state: viewState, set: value =&gt; { repoUrl = value }, value: repoUrl, form: &quot;claimForm&quot;, options: {} }" :class="{&#x27;is-invalid&#x27;: claimForm?.repoUrl?.invalid &amp;&amp; (claimForm?.repoUrl?.touched || claimForm?.submitted)}">
<input type="text" class="form-control" name="repoUrl" id="repoUrl" placeholder="https://github.com/owner/repository" required v-field="{ state: viewState, set: value =&gt; { repoUrl = value }, value: repoUrl, form: &quot;claimForm&quot;, options: {} }" :class="{&#x27;is-invalid&#x27;: claimForm?.repoUrl?.invalid &amp;&amp; (claimForm?.repoUrl?.touched || claimForm?.submitted)}">
<div class="invalid-feedback" v-show="claimForm?.repoUrl?.errors?.not_found">
No anonymization matches this repository and ID. Check both values and that you can access the repository on GitHub.
</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="repoId">Anonymized repository ID</label>
<input type="text" class="form-control" name="repoId" id="repoId" required v-field="{ state: viewState, set: value =&gt; { repoId = value }, value: repoId, form: &quot;claimForm&quot;, options: {} }" :class="{&#x27;is-invalid&#x27;: claimForm?.repoId?.invalid &amp;&amp; (claimForm?.repoId?.touched || claimForm?.submitted)}">
<input type="text" class="form-control" name="repoId" id="repoId" placeholder="e.g. my-paper-7F2A" aria-describedby="idHelp" required v-field="{ state: viewState, set: value =&gt; { repoId = value }, value: repoId, form: &quot;claimForm&quot;, options: {} }" :class="{&#x27;is-invalid&#x27;: claimForm?.repoId?.invalid &amp;&amp; (claimForm?.repoId?.touched || claimForm?.submitted)}">
<small id="idHelp" class="form-text text-muted">The ID is the last part of the anonymized URL: <code>anonymous.4open.science/r/&lt;ID&gt;</code>.</small>
</div>
+10
View File
@@ -8,6 +8,16 @@
<p role="status" v-if="!connections &amp;&amp; !connectionError">Loading your connections…</p>
<button class="btn btn-outline-ink" v-if="!connections &amp;&amp; connectionError" :disabled="busy" @click="loadConnections()">Try again</button>
<div v-if="connections">
<section class="connection-resources" v-if="connections.emailNotificationsEnabled">
<h2>Email alerts</h2>
<p>We will email you if your GitHub connection stops working or a repository becomes inaccessible. Your address is never shown on anonymous pages.</p>
<form @submit.prevent="saveNotificationEmail()">
<label for="notification-email">Email address</label>
<input id="notification-email" class="form-control" type="email" autocomplete="email" maxlength="254" required v-model="notificationEmail" :disabled="busy" />
<button class="btn btn-ink mt-2" type="submit" :disabled="busy">Save email address</button>
<p v-if="emailSaved" role="status">Email address saved.</p>
</form>
</section>
<div class="connections-grid">
<section v-if="connections.appEnabled">
<div class="connection-top"><i class="fab fa-github" aria-hidden="true"></i><span class="connection-badge">Recommended</span></div>
+9 -5
View File
@@ -20,8 +20,12 @@
</div>
</div>
<div class="leftCol-project-header">
<span class="project-name" v-text="repoId"></span>
<span class="project-name" v-text="repoId" :title="repoId"></span>
<span class="project-file-count">{{ fileCounts?.[''] || files?.length }} files</span>
<span class="project-expiry" v-if="options?.expirationDate" :title="options.expirationDate">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-clock"></use></svg>
Available until {{ fmt?.date(options.expirationDate) }}
</span>
</div>
<div class="leftCol-body">
<div class="paper-inline-warning" role="alert" v-if="options?.truncatedFolders?.length &gt; 0">
@@ -53,7 +57,7 @@
<div class="leftCol-backdrop" v-show="files?.length &amp;&amp; !sidebarCollapsed" @click="sidebarCollapsed = true"></div>
<div class="explorer-main">
<div class="status-bar">
<div class="status-bar" v-show="type != &#x27;error&#x27; || files?.length">
<ol class="breadcrumb paths" aria-label="Path">
<li class="breadcrumb-item" v-for="(p, index) in paths" v-text="p"></li>
</ol>
@@ -67,9 +71,9 @@
<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))"><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 btn-icon-only" aria-label="View raw current file" title="View raw file" v-show="content != null &amp;&amp; type != &#x27;error&#x27; &amp;&amp; type != &#x27;rate_limited&#x27;" :href="safeUrl((url))"><svg class="ui-icon" aria-hidden="true"><use href="#project-icon-file"></use></svg></a>
<a target="_self" class="btn btn-sm btn-icon-only" aria-label="Download current file" title="Download this file" v-show="content != null &amp;&amp; type != &#x27;error&#x27; &amp;&amp; type != &#x27;rate_limited&#x27;" :href="safeUrl((url) + &quot;&amp;download=true&quot;)"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-download"></use></svg></a>
<a target="_self" class="btn btn-sm btn-ink" aria-label="Download repository as ZIP" title="Download the whole 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"> Download repository</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>
+17 -2
View File
@@ -44,7 +44,7 @@
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<li class="nav-item" v-if="user || !isReviewerRoute">
<a class="nav-link nav-icon" target="_blank" href="https://github.com/tdurieux/anonymous_github/" title="Anonymous GitHub source code" aria-label="Anonymous GitHub source code on GitHub" rel="noopener" data-offset="30"><i class="fab fa-github" aria-hidden="true"></i></a>
</li>
<!-- "Report a bug" is about this service. Reviewers who land on an
@@ -67,7 +67,7 @@
</a>
</li>
<li class="nav-item" v-if="!user">
<li class="nav-item" v-if="!user &amp;&amp; !isReviewerRoute">
<a class="nav-link btn-signin" target="_self" href="/signin" data-offset="30"><i class="fab fa-github mr-1"></i>
Sign in
</a>
@@ -118,3 +118,18 @@
</div>
</nav>
<div class="navbar shadow generalMessage" v-if="generalMessage" v-html="sanitize(generalMessage)"></div>
<dialog id="email-prompt" class="email-prompt" v-if="emailPromptOpen" aria-labelledby="email-prompt-title" aria-describedby="email-prompt-reason" @cancel.prevent="respondToEmailPrompt('later')">
<form @submit.prevent="respondToEmailPrompt('save')">
<h2 id="email-prompt-title">Stay informed about access problems</h2>
<p id="email-prompt-reason">Get email alerts when your GitHub token or repository access fails.</p>
<label for="email-prompt-address">Email address</label>
<input id="email-prompt-address" class="form-control" type="email" autocomplete="email" maxlength="254" required v-model="emailPromptAddress" :disabled="emailPromptBusy" />
<p class="paper-inline-alert paper-inline-alert-error" role="alert" v-if="emailPromptError">{{ emailPromptError }}</p>
<div class="email-prompt-actions">
<button type="button" class="btn btn-outline-ink" :disabled="emailPromptBusy" @click="respondToEmailPrompt('later')">Remind me later</button>
<button type="button" class="btn btn-outline-ink" :disabled="emailPromptBusy" @click="respondToEmailPrompt('never')">Never</button>
<button type="submit" class="btn btn-ink" :disabled="emailPromptBusy">{{ emailPromptBusy ? 'Saving…' : 'Save' }}</button>
</div>
</form>
</dialog>
+2 -1
View File
@@ -108,7 +108,8 @@
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notebook" name="notebook" v-field="{ state: viewState, set: value =&gt; { options.options.notebook = value }, value: options?.options?.notebook, form: &quot;conference&quot;, options: {} }">
<label class="form-check-label" for="notebook">Display Notebooks</label>
<label class="form-check-label" for="notebook">Display notebooks</label>
<small class="form-text text-muted">Render Jupyter notebooks instead of raw JSON.</small>
</div>
</section>
+17 -2
View File
@@ -1,5 +1,5 @@
<div v-code-editor="{ content: content, options: aceOption }" v-if="type == &#x27;text&#x27;"></div>
<div class="file-content markdown-body" v-if="type == &#x27;html&#x27;" v-html="sanitize(content)"></div>
<div class="file-content markdown-body" v-if="type == &#x27;html&#x27;" v-html="highlightRedactions(sanitize(content))"></div>
<html-doc v-if="type == &#x27;html-doc&#x27; &amp;&amp; !showSource &amp;&amp; content != null" :content="content" :base-url="(fileBaseUrl)" :allow-scripts="allowScripts"></html-doc>
<div v-code-editor="{ content: content, options: aceOption }" v-if="(type == &#x27;code&#x27; || (type == &#x27;html-doc&#x27; &amp;&amp; showSource)) &amp;&amp; content != null"></div>
<img class="image-content" v-if="type == &#x27;image&#x27;" :src="safeUrl((url))">
@@ -19,7 +19,22 @@
<p class="paper-error-msg">This repository will be available in <strong>{{ rateLimitCountdown }}</strong>. The page will reload automatically.</p>
</div>
</div>
<div class="file-error container d-flex h-100" v-if="type == &#x27;error&#x27;"><h1 class="paper-empty-title m-auto" v-text="fmt.translate(&quot;ERRORS.&quot; + (content))">Error</h1></div>
<div class="file-error container d-flex h-100" v-if="type == 'error'">
<section class="viewer-error paper-error-card file-error-card m-auto" role="alert" aria-labelledby="viewer-error-title">
<h1 id="viewer-error-title" class="paper-empty-title">{{ fmt.translate(isSourceAccessError() ? 'VIEWER_ERRORS.access_title' : 'VIEWER_ERRORS.title') }}</h1>
<p>{{ fmt.translate('ERRORS.' + content) }}</p>
<template v-if="isSourceAccessError()">
<p>{{ fmt.translate('VIEWER_ERRORS.reader_help') }}</p>
<p>{{ fmt.translate('VIEWER_ERRORS.owner_help') }}</p>
</template>
<div class="paper-error-actions">
<a v-if="!isSourceAccessError()" href="/" class="btn btn-sm">Back to home</a>
<a v-if="!isSourceAccessError()" href="/faq" class="btn btn-sm">Read the FAQ</a>
<button type="button" class="btn btn-primary" @click="retryContent()">{{ fmt.translate('VIEWER_ERRORS.retry') }}</button>
<a v-if="isSourceAccessError()" class="btn btn-outline-ink" href="/dashboard">{{ fmt.translate('VIEWER_ERRORS.dashboard') }}</a>
</div>
</section>
</div>
<div class="file-error container d-flex h-100" v-if="type == &#x27;loading&#x27; &amp;&amp; !error"><h1 class="paper-empty-title m-auto">Loading&hellip;</h1></div>
<div class="file-error container d-flex h-100" v-if="type == &#x27;empty&#x27;"><h1 class="paper-empty-title m-auto">Empty <em>repository</em>.</h1></div>
<div class="file-error container d-flex h-100" v-if="content == null &amp;&amp; type != &#x27;empty&#x27;"><h1 class="paper-empty-title m-auto">Empty <em>file</em>.</h1></div>
+11
View File
@@ -76,6 +76,17 @@ jane.smith@example.org" v-field="{ state: viewState, set: value =&gt; { terms =
<a class="btn btn-outline-ink" href="/api/user/logout" target="_self">Sign out</a>
</div>
<form class="paper-account-email" @submit.prevent="saveAccountEmail()">
<label class="paper-field-label" for="account-email">Email address</label>
<p id="account-email-help" class="paper-section-copy">Used for account alerts. Your address is never shown on anonymous pages.</p>
<input id="account-email" class="form-control" type="email" autocomplete="email" maxlength="254" required v-model="accountEmail" aria-describedby="account-email-help" :disabled="emailLoading || emailSaving || !emailCSRFReady" @input="emailSaved = false" />
<p role="status" v-if="emailLoading">Loading your email address…</p>
<p class="paper-inline-alert paper-inline-alert-error" role="alert" v-if="emailError">{{ emailError }}</p>
<button class="btn btn-outline-ink mt-2" type="button" v-if="!emailCSRFReady &amp;&amp; !emailLoading" @click="loadAccountEmail()">Try again</button>
<button class="btn btn-ink mt-2" type="submit" :disabled="emailLoading || emailSaving || !emailCSRFReady">{{ emailSaving ? 'Saving…' : 'Save email address' }}</button>
<p role="status" v-if="emailSaved">Email address saved.</p>
</form>
<a class="paper-settings-connection-link" href="/connections">
<i class="fab fa-github" aria-hidden="true"></i>
<span>GitHub connections<small>Manage repository access and legacy OAuth.</small></span>
+25
View File
@@ -0,0 +1,25 @@
<section class="container py-4" style="max-width: 48rem; overflow-wrap: anywhere">
<a href="/">Anonymous GitHub</a>
<h1 class="mt-3">Approve artifact access</h1>
<p>This records your approval as the repository owner or an authorized coauthor.</p>
<p v-if="!user?.username">Sign in first, then reopen the artifact link from your submission. <a href="/signin">Sign in</a></p>
<p v-if="error" role="alert">{{ error }}</p>
<p v-if="saved" role="status">Consent recorded. <span v-if="handoffEnabled">Continue to review to finish linking.</span><span v-else>Artifact linking is not complete. Return to your submission to check its status.</span></p>
<button v-if="saved && handoffEnabled" class="btn btn-primary" type="button" @click="continueReview" :disabled="busy">Continue to review</button>
<form v-if="user?.username && !saved" @submit.prevent="loadPreview">
<label for="review-repository">Anonymous repository ID</label>
<input id="review-repository" class="form-control" v-model="repositoryId" maxlength="128" autocomplete="off" :disabled="busy || uncertain" aria-describedby="review-repository-help">
<p id="review-repository-help">Use the ID after /r/ in the anonymous repository URL.</p>
<button type="submit" class="btn btn-primary" :disabled="busy || uncertain">Preview access and retention</button>
</form>
<section v-if="preview && !saved" class="mt-4" aria-labelledby="review-policy-title">
<h2 id="review-policy-title">{{ preview.repositoryName }}</h2>
<p>Access: {{ preview.policy.access === 'anonymous-link' ? 'Anyone with the anonymous link' : 'Restricted review access' }}.</p>
<p>Retain until {{ preview.policy.retainUntil }}. Policy version {{ preview.policy.version }}.</p>
<p v-if="expired" role="alert">This preview expired. Open a new artifact link from your submission.</p>
<div><label><input type="checkbox" v-model="acceptAccess" :disabled="busy || uncertain || expired"> I approve the access policy above.</label></div>
<div><label><input type="checkbox" v-model="acceptRetention" :disabled="busy || uncertain || expired"> I approve retention until the date above.</label></div>
<button class="btn btn-primary" type="button" @click="confirmConsent" :disabled="busy || expired || !acceptAccess || !acceptRetention">{{ uncertain ? 'Retry confirmation' : 'Confirm consent' }}</button>
</section>
<p v-if="busy" role="status">Waiting for the server…</p>
</section>
+15 -15
View File
@@ -57,25 +57,25 @@
</div>
</div>
<div class="task-result" v-if="repo?.status === 'ready'">
<div class="detail-label">Repository</div>
<div class="detail-value">
<a target="_self" :href="safeUrl(&quot;/r/&quot; + (repoId) + &quot;/&quot;)">{{ reviewUrl }}</a>
<div class="share-link" v-if="repo?.status == &#x27;ready&#x27;">
<p class="paper-section-copy">Reviewers can browse the repository at this address without seeing who you are.</p>
<div class="share-link-row">
<input id="share-url" class="form-control share-link-input" readonly aria-label="Anonymous link" :value="shareUrl" @focus="$event.target.select()">
<button type="button" class="btn btn-ink" @click="copyShareUrl()">
<svg class="ui-icon" aria-hidden="true"><use :href="copied ? '#ui-icon-check' : '#project-icon-copy'"></use></svg>
<span aria-live="polite" v-text="copied ? 'Copied' : 'Copy link'"></span>
</button>
</div>
<div class="detail-label" v-if="repo?.options?.page">GitHub Page</div>
<div class="detail-value" v-if="repo?.options?.page">
<a target="_self" :href="safeUrl(&quot;/w/&quot; + (repoId) + &quot;/&quot;)">/w/{{ repoId }}/</a>
<div class="share-link-actions">
<a class="btn" target="_self" :href="safeUrl(&quot;/r/&quot; + (repoId) + &quot;/&quot;)">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-eye"></use></svg> Open repository
</a>
<a class="btn" target="_self" v-if="repo?.options?.page" :href="safeUrl(&quot;/w/&quot; + (repoId) + &quot;/&quot;)">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-globe"></use></svg> Open GitHub page
</a>
</div>
</div>
<div class="task-actions" v-if="repo?.status == &#x27;ready&#x27;">
<a class="btn btn-ink" target="_self" :href="safeUrl(&quot;/r/&quot; + (repoId) + &quot;/&quot;)">
<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;)">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-globe"></use></svg> Go to anonymized GitHub page
</a>
</div>
</section>
<aside class="task-summary" aria-label="Repository summary">