update design

This commit is contained in:
tdurieux
2026-04-24 14:55:18 +02:00
parent 063f10f18e
commit 88f826aab4
31 changed files with 10047 additions and 9040 deletions
+5914 -6140
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -10,6 +10,7 @@
"lint": "eslint .",
"start": "node --inspect=5858 -r ts-node/register ./src/server/index.ts",
"dev": "nodemon --transpile-only ./src/server/index.ts",
"dev:ui": "node scripts/dev-proxy.js",
"build": "rm -rf build && tsc && gulp",
"knip": "knip"
},
@@ -88,6 +89,7 @@
"gulp-concat": "^2.6.1",
"gulp-order": "^1.2.0",
"gulp-uglify": "^3.0.2",
"http-proxy-middleware": "^3.0.5",
"knip": "^5.1.0",
"mocha": "^10.8.2",
"nodemon": "^3.1.0",
+1 -1
View File
File diff suppressed because one or more lines are too long
+2141 -254
View File
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -31,8 +31,14 @@
<link rel="manifest" href="/favicon/site.webmanifest" />
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
<meta name="theme-color" content="#ffffff" />
<meta name="theme-color" content="#FAF9F6" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
/>
<link rel="stylesheet" href="/css/all.min.css" />
</head>
<body keypress-events class="d-flex flex-column">
+16 -4
View File
@@ -1,5 +1,17 @@
<div class="container d-flex h-100">
<h1 class=" m-auto text-center">
<div class="display-1">404</div><small class="text-muted">Page not found</small>
</h1>
<div class="paper-empty">
<div class="paper-empty-inner">
<div class="paper-eyebrow">Error &middot; 404</div>
<h1 class="paper-empty-title">
This link<br /><em>isn&rsquo;t here.</em>
</h1>
<p class="paper-empty-lede">
The anonymous mirror you&rsquo;re looking for has expired, been removed
by its owner, or never existed. If you received this URL from an
author, ask them to re-issue it.
</p>
<div class="paper-empty-cta">
<a href="/" class="btn-hero">Back to home</a>
<a href="/faq" class="btn-hero-ghost">Read the FAQ</a>
</div>
</div>
</div>
+115 -160
View File
@@ -1,187 +1,142 @@
<div class="container page">
<!-- Admin Navigation -->
<div class="container paper-page">
<div class="paper-crumbs">Admin &nbsp;/&nbsp; <span class="here">Conferences</span></div>
<h1 class="paper-page-title">All <em>conferences</em></h1>
<p class="paper-page-lede">Every venue configured on the platform.</p>
<nav class="admin-nav">
<a href="/admin/">
<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/"><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>
</nav>
<!-- Summary Stats -->
<div class="admin-stats">
<div class="admin-stat-card">
<div class="stat-value" ng-bind="total >= 0 ? (total | number) : '...'"></div>
<div class="stat-label">Total Conferences</div>
<div class="stat-label">Total conferences</div>
</div>
<div class="admin-stat-card">
<div class="stat-value">{{query.page}}/{{totalPage || '...'}}</div>
<div class="stat-label">Current Page</div>
<div class="stat-label">Current page</div>
</div>
</div>
<!-- Toolbar -->
<div class="admin-toolbar">
<input
type="search"
class="form-control"
aria-label="Search conferences..."
placeholder="Search conferences..."
autocomplete="off"
ng-model="query.search"
/>
<div class="pagination-compact">
<button class="btn btn-sm" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
<i class="fas fa-chevron-left"></i>
</button>
<form class="w-100 dashboard-filter-row" aria-label="Conferences" accept-charset="UTF-8">
<div class="search-wrap">
<input
type="number"
class="form-control form-control-sm"
ng-model="query.page"
min="1"
max="{{totalPage}}"
type="search"
class="form-control"
aria-label="Search conferences"
placeholder="Search conferences…"
autocomplete="off"
ng-model="query.search"
/>
<span>/{{totalPage}}</span>
<button class="btn btn-sm" ng-click="query.page = Math.min(totalPage, query.page + 1)" ng-disabled="query.page >= totalPage">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
>
<i class="fas fa-sort"></i> Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="query.sort = 'source.conferenceName'">
<i class="fas fa-check" ng-show="query.sort == 'source.conferenceName'"></i> Conference
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'anonymizeDate'">
<i class="fas fa-check" ng-show="query.sort == 'anonymizeDate'"></i> Anonymize Date
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'status'">
<i class="fas fa-check" ng-show="query.sort == 'status'"></i> Status
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'lastView'">
<i class="fas fa-check" ng-show="query.sort == 'lastView'"></i> Last View
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'pageView'">
<i class="fas fa-check" ng-show="query.sort == 'pageView'"></i> Page Views
</a>
</div>
</div>
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
>
<i class="fas fa-filter"></i> Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Filter by status</h6>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.ready" /> Ready
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.preparing" /> Preparing
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.expired" /> Expired
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.removed" /> Removed
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.error" /> Error
</label>
</div>
</div>
</div>
<!-- Conference List -->
<div
class="admin-list-item"
ng-repeat="conference in conferences | filter:conferenceFiler | orderBy:orderBy as filteredConferences"
>
<div class="item-main">
<div class="item-title">
<a
ng-href="/conference/{{conference.conferenceID}}"
ng-bind="conference.name"
></a>
<span
class="status-badge"
ng-class="'status-' + conference.status"
>{{conference.status | title}}</span>
</div>
<div class="item-meta">
<span>
<i class="fas fa-fingerprint"></i> ID: {{conference.conferenceID}}
</span>
<span>
<i class="fas fa-table"></i> {{::conference.repositories.length || 0 | number}} repos
</span>
<span>
<i class="fas fa-euro-sign"></i> {{conference.price || 0 | number}} &euro;
</span>
<span>
<i class="fas fa-calendar-alt"></i>
{{conference.startDate | date}} &ndash; {{conference.endDate | date}}
</span>
</div>
</div>
<div class="item-actions">
<div class="dropdown">
<button
class="btn dropdown-toggle btn-sm"
type="button"
data-toggle="dropdown"
>
Actions
<div class="d-flex flex-wrap" style="gap: 8px; align-items: center;">
<div class="pagination-compact">
<button class="btn btn-sm" type="button" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
<i class="fas fa-chevron-left"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/conference/{{conference.conferenceID}}/edit">
<i class="far fa-edit"></i> Edit
<input type="number" class="form-control form-control-sm" ng-model="query.page" min="1" max="{{totalPage}}" />
<span>/{{totalPage}}</span>
<button class="btn btn-sm" type="button" ng-click="query.page = Math.min(totalPage, query.page + 1)" ng-disabled="query.page >= totalPage">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownSort" data-toggle="dropdown">Sort</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="query.sort = 'source.conferenceName'">
<i class="fas fa-check" ng-show="query.sort == 'source.conferenceName'"></i> Conference
</a>
<a class="dropdown-item" href="/conference/{{conference.conferenceID}}/">
<i class="fa fa-eye"></i> View
<a class="dropdown-item" href="#" ng-click="query.sort = 'anonymizeDate'">
<i class="fas fa-check" ng-show="query.sort == 'anonymizeDate'"></i> Anonymize date
</a>
<div class="dropdown-divider"></div>
<a
class="dropdown-item text-danger"
href="#"
ng-show="conference.status != 'removed'"
ng-click="removeConference(conference)"
>
<i class="fas fa-trash-alt"></i> Remove
<a class="dropdown-item" href="#" ng-click="query.sort = 'status'">
<i class="fas fa-check" ng-show="query.sort == 'status'"></i> Status
</a>
</div>
</div>
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownStatus" data-toggle="dropdown">Status</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Filter by status</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminConfStatusReady" ng-model="query.ready" />
<label class="form-check-label" for="adminConfStatusReady">Ready</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminConfStatusPreparing" ng-model="query.preparing" />
<label class="form-check-label" for="adminConfStatusPreparing">Preparing</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminConfStatusExpired" ng-model="query.expired" />
<label class="form-check-label" for="adminConfStatusExpired">Expired</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminConfStatusRemoved" ng-model="query.removed" />
<label class="form-check-label" for="adminConfStatusRemoved">Removed</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminConfStatusError" ng-model="query.error" />
<label class="form-check-label" for="adminConfStatusError">Error</label>
</div>
</div>
</div>
</div>
</form>
<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>
<div role="columnheader">Status</div>
<div role="columnheader" class="num">Repos</div>
<div role="columnheader">Window</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div
class="paper-table-row"
role="row"
ng-repeat="conference in conferences | filter:conferenceFiler | orderBy:orderBy as filteredConferences"
>
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Conf</span>
<div class="anon-text">
<a class="repo-name" ng-href="/conference/{{conference.conferenceID}}" ng-bind="conference.name"></a>
<div class="anon-sub">
<span>{{conference.conferenceID}}</span>&nbsp;&middot;&nbsp;<span>{{conference.price || 0 | number}} &euro;</span>
</div>
</div>
</div>
<div class="cell-status" role="cell">
<span class="status-dot" ng-class="{'status-removed': conference.status == 'removed' || conference.status == 'expired', 'status-ready': conference.status == 'ready'}"></span>
<span ng-bind="conference.status | title"></span>
</div>
<div class="cell-views num" role="cell" ng-bind="::conference.repositories.length || 0 | number"></div>
<div class="cell-expires" role="cell">{{conference.startDate | date}} &ndash; {{conference.endDate | date}}</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>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/conference/{{conference.conferenceID}}/edit"><i class="far fa-edit"></i> Edit</a>
<a class="dropdown-item" href="/conference/{{conference.conferenceID}}/"><i class="fa fa-eye"></i> View</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="#" ng-show="conference.status != 'removed'" ng-click="removeConference(conference)"><i class="fas fa-trash-alt"></i> Remove</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" ng-if="filteredConferences.length == 0">
<i class="fas fa-inbox"></i>
<span>No conferences match the current filters.</span>
</div>
</div>
<div class="empty-state" ng-if="filteredConferences.length == 0">
<i class="fas fa-chalkboard-teacher"></i>
No conferences match the current filters.
</div>
<!-- Bottom pagination -->
<div class="admin-toolbar" ng-if="totalPage > 1" style="justify-content: center; border-bottom: none;">
<div class="pagination-compact">
<button class="btn btn-sm" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
+51 -120
View File
@@ -1,195 +1,126 @@
<div class="container page">
<!-- Admin Navigation -->
<div class="container paper-page">
<div class="paper-crumbs">Admin &nbsp;/&nbsp; <span class="here">Queues</span></div>
<h1 class="paper-page-title">Background <em>queues</em></h1>
<p class="paper-page-lede">Watch anonymization jobs as they move through the workers.</p>
<nav class="admin-nav">
<a href="/admin/">
<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/"><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>
</nav>
<!-- Summary Stats -->
<div class="admin-stats">
<div class="admin-stat-card">
<div class="stat-value">{{downloadJobs.length || 0}}</div>
<div class="stat-label">Download Jobs</div>
<div class="stat-label">Download jobs</div>
</div>
<div class="admin-stat-card">
<div class="stat-value">{{removeJobs.length || 0}}</div>
<div class="stat-label">Remove Jobs</div>
<div class="stat-label">Remove jobs</div>
</div>
<div class="admin-stat-card">
<div class="stat-value">{{removeCaches.length || 0}}</div>
<div class="stat-label">Cache Jobs</div>
<div class="stat-label">Cache jobs</div>
</div>
</div>
<!-- Download Jobs -->
<div class="admin-section-header">
<h2><i class="fas fa-download mr-1"></i> Download Jobs</h2>
<h2><i class="fas fa-download"></i> Download jobs</h2>
<span class="section-count">{{downloadJobs.length || 0}}</span>
</div>
<div
class="queue-job-card"
ng-repeat="job in downloadJobs as filteredDownloadJobs"
>
<div class="queue-job-card" ng-repeat="job in downloadJobs as filteredDownloadJobs">
<div class="job-header">
<div class="job-id">
<span class="status-dot" ng-class="{'status-ready': job.progress.status == 'ready', 'status-error': job.progress.status == 'error', 'status-preparing': job.progress.status == 'preparing', 'status-removed': job.progress.status == 'removed'}"></span>
<a target="_blank" ng-href="/r/{{job.id}}" ng-bind="job.id"></a>
<span
class="status-badge"
ng-class="'status-' + job.progress.status"
>{{job.progress.status | title}}</span>
<span ng-bind="job.progress.status | title" style="font-family: var(--font-sans); color: var(--ink-muted); font-size: 12px;"></span>
</div>
</div>
<div class="job-timestamps">
<span ng-if="job.timestamp">
<i class="fas fa-clock"></i> Created: {{job.timestamp | humanTime}}
</span>
<span ng-if="job.processedOn">
<i class="fas fa-cog"></i> Processed: {{job.processedOn | humanTime}}
</span>
<span ng-if="job.finishedOn">
<i class="fas fa-check"></i> Finished: {{job.finishedOn | humanTime}}
</span>
<span ng-if="job.timestamp"><i class="fas fa-clock"></i> Created: {{job.timestamp | humanTime}}</span>
<span ng-if="job.processedOn"><i class="fas fa-cog"></i> Processed: {{job.processedOn | humanTime}}</span>
<span ng-if="job.finishedOn"><i class="fas fa-check"></i> Finished: {{job.finishedOn | humanTime}}</span>
</div>
<div ng-if="job.stacktrace.length">
<pre
ng-repeat="stack in job.stacktrace track by $index"
style="font-size: 0.8rem; max-height: 100px; overflow: auto; margin: 6px 0 0 0"
><code ng-bind="stack"></code></pre>
<pre ng-repeat="stack in job.stacktrace track by $index" style="font-size: 0.8rem; max-height: 100px; overflow: auto; margin: 6px 0 0 0"><code ng-bind="stack"></code></pre>
</div>
<div class="job-actions">
<button class="btn btn-sm" ng-click="retryJob('download', job)">
<i class="fas fa-sync"></i> Retry
</button>
<button class="btn btn-sm" ng-click="removeJob('download', job)">
<i class="fas fa-trash-alt"></i> Remove
</button>
<a class="btn btn-sm" href="/anonymize/{{job.id}}">
<i class="far fa-edit"></i> Edit
</a>
<button class="btn btn-sm" ng-click="retryJob('download', job)"><i class="fas fa-sync"></i> Retry</button>
<button class="btn btn-sm" ng-click="removeJob('download', job)"><i class="fas fa-trash-alt"></i> Remove</button>
<a class="btn btn-sm" href="/anonymize/{{job.id}}"><i class="far fa-edit"></i> Edit</a>
</div>
</div>
<div class="empty-state" ng-if="filteredDownloadJobs.length == 0" style="padding: 20px">
<div class="paper-table-empty" ng-if="filteredDownloadJobs.length == 0" style="border:1px solid var(--border-color);border-radius:10px;background:var(--paper-card);">
<i class="fas fa-check-circle"></i>
No download jobs in the queue.
<span>No download jobs in the queue.</span>
</div>
<!-- Remove Jobs -->
<div class="admin-section-header">
<h2><i class="fas fa-trash mr-1"></i> Remove Jobs</h2>
<h2><i class="fas fa-trash"></i> Remove jobs</h2>
<span class="section-count">{{removeJobs.length || 0}}</span>
</div>
<div
class="queue-job-card"
ng-repeat="job in removeJobs as filteredRemoveJobs"
>
<div class="queue-job-card" ng-repeat="job in removeJobs as filteredRemoveJobs">
<div class="job-header">
<div class="job-id">
<span class="status-dot" ng-class="{'status-ready': job.progress.status == 'ready', 'status-error': job.progress.status == 'error', 'status-preparing': job.progress.status == 'preparing', 'status-removed': job.progress.status == 'removed'}"></span>
<a target="_blank" ng-href="/r/{{job.id}}" ng-bind="job.id"></a>
<span
class="status-badge"
ng-class="'status-' + job.progress.status"
>{{job.progress.status | title}}</span>
<span ng-bind="job.progress.status | title" style="font-family: var(--font-sans); color: var(--ink-muted); font-size: 12px;"></span>
</div>
</div>
<div class="job-timestamps">
<span ng-if="job.timestamp">
<i class="fas fa-clock"></i> Created: {{job.timestamp | humanTime}}
</span>
<span ng-if="job.processedOn">
<i class="fas fa-cog"></i> Processed: {{job.processedOn | humanTime}}
</span>
<span ng-if="job.finishedOn">
<i class="fas fa-check"></i> Finished: {{job.finishedOn | humanTime}}
</span>
<span ng-if="job.timestamp"><i class="fas fa-clock"></i> Created: {{job.timestamp | humanTime}}</span>
<span ng-if="job.processedOn"><i class="fas fa-cog"></i> Processed: {{job.processedOn | humanTime}}</span>
<span ng-if="job.finishedOn"><i class="fas fa-check"></i> Finished: {{job.finishedOn | humanTime}}</span>
</div>
<div ng-if="job.stacktrace.length">
<pre
ng-repeat="stack in job.stacktrace track by $index"
style="font-size: 0.8rem; max-height: 100px; overflow: auto; margin: 6px 0 0 0"
><code ng-bind="stack"></code></pre>
<pre ng-repeat="stack in job.stacktrace track by $index" style="font-size: 0.8rem; max-height: 100px; overflow: auto; margin: 6px 0 0 0"><code ng-bind="stack"></code></pre>
</div>
<div class="job-actions">
<button class="btn btn-sm" ng-click="retryJob('remove', job)">
<i class="fas fa-sync"></i> Retry
</button>
<button class="btn btn-sm" ng-click="removeJob('remove', job)">
<i class="fas fa-trash-alt"></i> Remove
</button>
<a class="btn btn-sm" href="/anonymize/{{job.id}}">
<i class="far fa-edit"></i> Edit
</a>
<button class="btn btn-sm" ng-click="retryJob('remove', job)"><i class="fas fa-sync"></i> Retry</button>
<button class="btn btn-sm" ng-click="removeJob('remove', job)"><i class="fas fa-trash-alt"></i> Remove</button>
<a class="btn btn-sm" href="/anonymize/{{job.id}}"><i class="far fa-edit"></i> Edit</a>
</div>
</div>
<div class="empty-state" ng-if="filteredRemoveJobs.length == 0" style="padding: 20px">
<div class="paper-table-empty" ng-if="filteredRemoveJobs.length == 0" style="border:1px solid var(--border-color);border-radius:10px;background:var(--paper-card);">
<i class="fas fa-check-circle"></i>
No remove jobs in the queue.
<span>No remove jobs in the queue.</span>
</div>
<!-- Cache Jobs -->
<div class="admin-section-header">
<h2><i class="fas fa-broom mr-1"></i> Cache Cleanup Jobs</h2>
<h2><i class="fas fa-broom"></i> Cache cleanup jobs</h2>
<span class="section-count">{{removeCaches.length || 0}}</span>
</div>
<div
class="queue-job-card"
ng-repeat="job in removeCaches as filteredRemoveCache"
>
<div class="queue-job-card" ng-repeat="job in removeCaches as filteredRemoveCache">
<div class="job-header">
<div class="job-id">
<span class="status-dot" ng-class="{'status-ready': job.progress.status == 'ready', 'status-error': job.progress.status == 'error', 'status-preparing': job.progress.status == 'preparing', 'status-removed': job.progress.status == 'removed'}"></span>
<a target="_blank" ng-href="/r/{{job.id}}" ng-bind="job.id"></a>
<span
class="status-badge"
ng-class="'status-' + job.progress.status"
>{{job.progress.status | title}}</span>
<span ng-bind="job.progress.status | title" style="font-family: var(--font-sans); color: var(--ink-muted); font-size: 12px;"></span>
</div>
</div>
<div class="job-timestamps">
<span ng-if="job.timestamp">
<i class="fas fa-clock"></i> Created: {{job.timestamp | humanTime}}
</span>
<span ng-if="job.processedOn">
<i class="fas fa-cog"></i> Processed: {{job.processedOn | humanTime}}
</span>
<span ng-if="job.finishedOn">
<i class="fas fa-check"></i> Finished: {{job.finishedOn | humanTime}}
</span>
<span ng-if="job.timestamp"><i class="fas fa-clock"></i> Created: {{job.timestamp | humanTime}}</span>
<span ng-if="job.processedOn"><i class="fas fa-cog"></i> Processed: {{job.processedOn | humanTime}}</span>
<span ng-if="job.finishedOn"><i class="fas fa-check"></i> Finished: {{job.finishedOn | humanTime}}</span>
</div>
<div ng-if="job.stacktrace.length">
<pre
ng-repeat="stack in job.stacktrace track by $index"
style="font-size: 0.8rem; max-height: 100px; overflow: auto; margin: 6px 0 0 0"
><code ng-bind="stack"></code></pre>
<pre ng-repeat="stack in job.stacktrace track by $index" style="font-size: 0.8rem; max-height: 100px; overflow: auto; margin: 6px 0 0 0"><code ng-bind="stack"></code></pre>
</div>
<div class="job-actions">
<button class="btn btn-sm" ng-click="retryJob('cache', job)">
<i class="fas fa-sync"></i> Retry
</button>
<button class="btn btn-sm" ng-click="removeJob('cache', job)">
<i class="fas fa-trash-alt"></i> Remove
</button>
<a class="btn btn-sm" href="/anonymize/{{job.id}}">
<i class="far fa-edit"></i> Edit
</a>
<button class="btn btn-sm" ng-click="retryJob('cache', job)"><i class="fas fa-sync"></i> Retry</button>
<button class="btn btn-sm" ng-click="removeJob('cache', job)"><i class="fas fa-trash-alt"></i> Remove</button>
<a class="btn btn-sm" href="/anonymize/{{job.id}}"><i class="far fa-edit"></i> Edit</a>
</div>
</div>
<div class="empty-state" ng-if="filteredRemoveCache.length == 0" style="padding: 20px">
<div class="paper-table-empty" ng-if="filteredRemoveCache.length == 0" style="border:1px solid var(--border-color);border-radius:10px;background:var(--paper-card);">
<i class="fas fa-check-circle"></i>
No cache cleanup jobs in the queue.
<span>No cache cleanup jobs in the queue.</span>
</div>
</div>
+131 -222
View File
@@ -1,251 +1,160 @@
<div class="container page">
<!-- Admin Navigation -->
<div class="container paper-page">
<div class="paper-crumbs">Admin &nbsp;/&nbsp; <span class="here">Repositories</span></div>
<h1 class="paper-page-title">Anonymized <em>repositories</em></h1>
<p class="paper-page-lede">Every anonymization across every user and conference.</p>
<nav class="admin-nav">
<a href="/admin/" 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/" 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>
</nav>
<!-- Summary Stats -->
<div class="admin-stats">
<div class="admin-stat-card">
<div class="stat-value" ng-bind="total >= 0 ? (total | number) : '...'"></div>
<div class="stat-label">Total Repos</div>
<div class="stat-label">Total repos</div>
</div>
<div class="admin-stat-card">
<div class="stat-value">{{query.page}}/{{totalPage || '...'}}</div>
<div class="stat-label">Current Page</div>
<div class="stat-label">Current page</div>
</div>
</div>
<!-- Toolbar -->
<div class="admin-toolbar">
<input
type="search"
class="form-control"
aria-label="Search repositories..."
placeholder="Search repositories..."
autocomplete="off"
ng-model="query.search"
/>
<div class="pagination-compact">
<button class="btn btn-sm" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
<i class="fas fa-chevron-left"></i>
</button>
<form class="w-100 dashboard-filter-row" aria-label="Repositories" accept-charset="UTF-8">
<div class="search-wrap">
<input
type="number"
class="form-control form-control-sm"
ng-model="query.page"
min="1"
max="{{totalPage}}"
type="search"
class="form-control"
aria-label="Search repositories"
placeholder="Search repositories…"
autocomplete="off"
ng-model="query.search"
/>
<span>/{{totalPage}}</span>
<button class="btn btn-sm" ng-click="query.page = Math.min(totalPage, query.page + 1)" ng-disabled="query.page >= totalPage">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i class="fas fa-sort"></i> Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="query.sort = 'source.repositoryName'">
<i class="fas fa-check" ng-show="query.sort == 'source.repositoryName'"></i> Repository
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'anonymizeDate'">
<i class="fas fa-check" ng-show="query.sort == 'anonymizeDate'"></i> Anonymize Date
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'status'">
<i class="fas fa-check" ng-show="query.sort == 'status'"></i> Status
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'lastView'">
<i class="fas fa-check" ng-show="query.sort == 'lastView'"></i> Last View
</a>
<a class="dropdown-item" href="#" ng-click="query.sort = 'pageView'">
<i class="fas fa-check" ng-show="query.sort == 'pageView'"></i> Page Views
</a>
</div>
</div>
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i class="fas fa-filter"></i> Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Filter by status</h6>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.ready" /> Ready
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.preparing" /> Preparing
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.expired" /> Expired
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.removed" /> Removed
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="query.error" /> Error
</label>
</div>
</div>
</div>
<!-- Repository List -->
<div
class="admin-list-item"
ng-repeat="repo in repositories | filter:repoFiler | orderBy:orderBy as filteredRepositories"
>
<div class="item-main">
<div class="item-title">
<a target="_blank" ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
<span
class="status-badge"
ng-class="'status-' + repo.status"
>{{repo.status | title}}</span>
<span
ng-if="repo.status == 'error'"
style="font-size: 0.8rem; color: #dc3545;"
ng-bind="repo.statusMessage"
></span>
</div>
<div class="item-meta">
<span>
<i class="fab fa-github"></i>
<a
href="https://github.com/{{repo.source.repositoryName}}/"
ng-bind="repo.source.repositoryName"
></a>
</span>
<span ng-if="repo.options.update">
<i class="fas fa-code-branch"></i>
<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}"
ng-bind="repo.source.branch"
></a>
</span>
<span ng-if="!repo.options.update">
@<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}"
ng-bind="repo.source.commit.substring(0, 8)"
></a>
</span>
<span>anonymized {{repo.anonymizeDate | humanTime}}</span>
</div>
<div class="item-meta" style="margin-top: 4px">
<span ng-if="::repo.conference">
<i class="fas fa-chalkboard-teacher"></i> {{repo.conference}}
</span>
<span title="Terms: {{::repo.options.terms.join(', ')}}">
<i class="fas fa-shield-alt"></i> {{::repo.options.terms.length | number}} terms
</span>
<span title="Size: {{::repo.size.storage | humanFileSize}}">
<i class="fas fa-database"></i> {{::repo.size.storage | humanFileSize}}
</span>
<span>
<i class="far fa-eye"></i> {{::repo.pageView || 0 | number}} views
</span>
<span>
<i class="far fa-calendar-alt"></i> Last: {{::repo.lastView | humanTime}}
</span>
<span ng-if="repo.options.expirationMode != 'never' && repo.status == 'ready'">
<i class="far fa-clock"></i> Expire: {{repo.options.expirationDate | humanTime}}
</span>
</div>
</div>
<div class="item-actions">
<div class="dropdown">
<button
class="btn dropdown-toggle btn-sm"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Actions
<div class="d-flex flex-wrap" style="gap: 8px; align-items: center;">
<div class="pagination-compact">
<button class="btn btn-sm" type="button" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
<i class="fas fa-chevron-left"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/anonymize/{{repo.repoId}}">
<i class="far fa-edit"></i> Edit
<input
type="number"
class="form-control form-control-sm"
ng-model="query.page"
min="1"
max="{{totalPage}}"
/>
<span>/{{totalPage}}</span>
<button class="btn btn-sm" type="button" ng-click="query.page = Math.min(totalPage, query.page + 1)" ng-disabled="query.page >= totalPage">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownSort" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Sort</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="query.sort = 'source.repositoryName'">
<i class="fas fa-check" ng-show="query.sort == 'source.repositoryName'"></i> Repository
</a>
<a class="dropdown-item" href="/r/{{repo.repoId}}/">
<i class="fa fa-eye"></i> View Repo
<a class="dropdown-item" href="#" ng-click="query.sort = 'anonymizeDate'">
<i class="fas fa-check" ng-show="query.sort == 'anonymizeDate'"></i> Anonymize date
</a>
<a
class="dropdown-item"
href="/w/{{repo.repoId}}/"
target="_self"
ng-if="repo.options.page && repo.status == 'ready'"
>
<i class="fas fa-globe"></i> View Page
<a class="dropdown-item" href="#" ng-click="query.sort = 'status'">
<i class="fas fa-check" ng-show="query.sort == 'status'"></i> Status
</a>
<div class="dropdown-divider"></div>
<a
class="dropdown-item"
href="#"
ng-show="repo.status == 'ready' || repo.status == 'error'"
ng-click="updateRepository(repo)"
>
<i class="fas fa-sync"></i> Force Update
<a class="dropdown-item" href="#" ng-click="query.sort = 'lastView'">
<i class="fas fa-check" ng-show="query.sort == 'lastView'"></i> Last view
</a>
<a
class="dropdown-item"
href="#"
ng-show="repo.status == 'removed'"
ng-click="updateRepository(repo)"
>
<i class="fas fa-check-circle"></i> Enable
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" ng-click="removeCache(repo)">
<i class="fas fa-broom"></i> Remove Cache
</a>
<a
class="dropdown-item text-danger"
href="#"
ng-show="repo.status == 'ready'"
ng-click="removeRepository(repo)"
>
<i class="fas fa-trash-alt"></i> Remove
<a class="dropdown-item" href="#" ng-click="query.sort = 'pageView'">
<i class="fas fa-check" ng-show="query.sort == 'pageView'"></i> Page views
</a>
</div>
</div>
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownStatus" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Status</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Filter by status</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminStatusReady" ng-model="query.ready" />
<label class="form-check-label" for="adminStatusReady">Ready</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminStatusPreparing" ng-model="query.preparing" />
<label class="form-check-label" for="adminStatusPreparing">Preparing</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminStatusExpired" ng-model="query.expired" />
<label class="form-check-label" for="adminStatusExpired">Expired</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminStatusRemoved" ng-model="query.removed" />
<label class="form-check-label" for="adminStatusRemoved">Removed</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminStatusError" ng-model="query.error" />
<label class="form-check-label" for="adminStatusError">Error</label>
</div>
</div>
</div>
</div>
</form>
<div class="paper-table paper-table-repos w-100" role="table" aria-label="Repositories">
<div class="paper-table-head" role="row">
<div role="columnheader">Repository</div>
<div role="columnheader">Status</div>
<div role="columnheader" class="num">Views</div>
<div role="columnheader">Anonymized</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div
class="paper-table-row"
role="row"
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed'}"
ng-repeat="repo in repositories | filter:repoFiler | orderBy:orderBy as filteredRepositories"
>
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<div class="anon-text">
<a class="repo-name" target="_blank" ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
<div class="anon-sub">
<a href="https://github.com/{{repo.source.repositoryName}}/" ng-bind="repo.source.repositoryName"></a><span ng-if="repo.options.update">&nbsp;&middot;&nbsp;<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}" ng-bind="repo.source.branch"></a></span><span ng-if="!repo.options.update">&nbsp;&middot;&nbsp;@<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}" ng-bind="repo.source.commit.substring(0, 8)"></a></span><span ng-if="::repo.conference">&nbsp;&middot;&nbsp;<i class="fas fa-chalkboard-teacher"></i> {{repo.conference}}</span><span>&nbsp;&middot;&nbsp;{{::repo.size.storage | humanFileSize}}</span><span>&nbsp;&middot;&nbsp;{{::repo.options.terms.length | number}} terms</span>
</div>
</div>
</div>
<div class="cell-status" role="cell">
<span class="status-dot" ng-class="{'status-removed': repo.status == 'removed' || repo.status == 'expired', 'status-ready': repo.status == 'ready', 'status-error': repo.status == 'error', 'status-preparing': repo.status == 'preparing'}"></span>
<span ng-bind="repo.status | title"></span>
</div>
<div class="cell-views num" role="cell" ng-bind="::repo.pageView || 0 | number"></div>
<div class="cell-expires" role="cell" ng-bind="repo.anonymizeDate | humanTime"></div>
<div class="cell-actions" role="cell">
<div class="dropdown">
<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>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/anonymize/{{repo.repoId}}"><i class="far fa-edit"></i> Edit</a>
<a class="dropdown-item" href="/r/{{repo.repoId}}/"><i class="fa fa-eye"></i> View repo</a>
<a class="dropdown-item" href="/w/{{repo.repoId}}/" target="_self" ng-if="repo.options.page && repo.status == 'ready'"><i class="fas fa-globe"></i> View page</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" ng-show="repo.status == 'ready' || repo.status == 'error'" ng-click="updateRepository(repo)"><i class="fas fa-sync"></i> Force update</a>
<a class="dropdown-item" href="#" ng-show="repo.status == 'removed'" ng-click="updateRepository(repo)"><i class="fas fa-check-circle"></i> Enable</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" ng-click="removeCache(repo)"><i class="fas fa-broom"></i> Remove cache</a>
<a class="dropdown-item text-danger" href="#" ng-show="repo.status == 'ready'" ng-click="removeRepository(repo)"><i class="fas fa-trash-alt"></i> Remove</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" ng-if="filteredRepositories.length == 0">
<i class="fas fa-inbox"></i>
<span>No repositories match the current filters.</span>
</div>
</div>
<div class="empty-state" ng-if="filteredRepositories.length == 0">
<i class="fas fa-code-branch"></i>
No repositories match the current filters.
</div>
<!-- Bottom pagination -->
<div class="admin-toolbar" ng-if="totalPage > 1" style="justify-content: center; border-bottom: none;">
<div class="pagination-compact">
<button class="btn btn-sm" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
+126 -218
View File
@@ -1,40 +1,26 @@
<div class="container page">
<!-- Admin Navigation -->
<div class="container paper-page">
<div class="paper-crumbs"><a href="/admin/users">Users</a> &nbsp;/&nbsp; <span class="here">{{userInfo.username || 'Profile'}}</span></div>
<h1 class="paper-page-title">User <em>profile</em></h1>
<p class="paper-page-lede">Inspect activity, quota, and repositories for a single account.</p>
<nav class="admin-nav">
<a href="/admin/">
<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/"><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>
</nav>
<!-- User Detail Card -->
<div class="user-detail-card" ng-if="userInfo">
<div class="user-header">
<img
ng-src="{{userInfo.photo}}"
ng-if="userInfo.photo"
width="48"
height="48"
/>
<img ng-src="{{userInfo.photo}}" ng-if="userInfo.photo" width="56" height="56" />
<div>
<h1>
{{userInfo.username}}
<span
class="status-badge"
ng-class="'status-' + userInfo.status"
>{{userInfo.status | title}}</span>
<span
class="status-badge status-active"
ng-if="userInfo.isAdmin"
>Admin</span>
<span class="status-dot-wrap">
<span class="status-dot" ng-class="{'status-ready': userInfo.status == 'active', 'status-removed': userInfo.status != 'active'}"></span>
<span ng-bind="userInfo.status | title"></span>
</span>
<span class="type-badge type-repo" ng-if="userInfo.isAdmin">Admin</span>
</h1>
</div>
</div>
@@ -46,8 +32,8 @@
<div class="detail-label">Email</div>
<div class="detail-value">{{userInfo.emails[0].email}}</div>
<div class="detail-label">Access Token</div>
<div class="detail-value" style="font-family: monospace; font-size: 0.85rem;">{{userInfo.accessTokens.github}}</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-label">GitHub</div>
<div class="detail-value">
@@ -56,219 +42,141 @@
</a>
</div>
<div class="detail-label">GitHub Repos</div>
<div class="detail-label">GitHub repos</div>
<div class="detail-value">
{{userInfo.repositories.length}} repositories
<button
class="btn btn-sm ml-2"
ng-click="showRepos = !showRepos"
>
<button class="btn btn-sm ml-2" ng-click="showRepos = !showRepos">
{{showRepos ? 'Hide' : 'Show'}}
</button>
<button
class="btn btn-primary btn-sm ml-1"
ng-click="getGitHubRepositories()"
>
<button class="btn btn-sm ml-1" ng-click="getGitHubRepositories()">
<i class="fas fa-sync"></i> Refresh
</button>
</div>
</div>
<!-- GitHub repos expandable list -->
<div ng-if="showRepos" style="margin-top: 16px">
<div
class="admin-list-item"
ng-repeat="repo in userInfo.repositories"
>
<div class="item-main">
<div class="item-title" style="font-size: 0.9rem">{{repo.name}}</div>
<div class="item-meta">
<span title="Size">
<i class="fas fa-database"></i> {{::repo.size | humanFileSize}}
</span>
<div ng-if="showRepos" style="margin-top: 20px">
<div class="paper-section-eyebrow">GitHub repositories</div>
<div class="paper-table w-100" style="margin-top: 10px;">
<div class="paper-table-row" ng-repeat="repo in userInfo.repositories" style="grid-template-columns: 1fr 160px;">
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<div class="anon-text">
<span class="repo-name" ng-bind="repo.name"></span>
</div>
</div>
<div class="cell-expires" role="cell">
<i class="fas fa-database"></i> {{::repo.size | humanFileSize}}
</div>
</div>
</div>
</div>
</div>
<!-- Repositories Section -->
<div class="admin-section-header">
<h2><i class="fas fa-code-branch mr-1"></i> Anonymized Repositories</h2>
<h2><i class="fas fa-code-branch"></i> Anonymized repositories</h2>
<span class="section-count">{{repositories.length}}</span>
</div>
<!-- Toolbar -->
<div class="admin-toolbar">
<input
type="search"
class="form-control"
aria-label="Search repositories..."
placeholder="Search repositories..."
autocomplete="off"
ng-model="search"
/>
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
>
<i class="fas fa-sort"></i> Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="orderBy = '-anonymizeDate'">
<i class="fas fa-check" ng-show="orderBy == '-anonymizeDate'"></i> Anonymize Date
</a>
<a class="dropdown-item" href="#" ng-click="orderBy = 'repoId'">
<i class="fas fa-check" ng-show="orderBy == 'repoId'"></i> ID
</a>
<a class="dropdown-item" href="#" ng-click="orderBy = '-status'">
<i class="fas fa-check" ng-show="orderBy == '-status'"></i> Status
</a>
</div>
<form class="w-100 dashboard-filter-row" aria-label="Repositories" accept-charset="UTF-8">
<div class="search-wrap">
<input
type="search"
class="form-control"
aria-label="Search repositories"
placeholder="Search repositories"
autocomplete="off"
ng-model="search"
/>
</div>
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
>
<i class="fas fa-filter"></i> Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="filters.status.ready" /> Ready
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="filters.status.expired" /> Expired
</label>
<label class="dropdown-item mb-0">
<input type="checkbox" ng-model="filters.status.removed" /> Removed
</label>
</div>
</div>
</div>
<!-- Repository List -->
<div
class="admin-list-item"
ng-repeat="repo in repositories | filter:repoFiler | orderBy:orderBy as filteredRepositories"
>
<div class="item-main">
<div class="item-title">
<a ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
<span
class="status-badge"
ng-class="'status-' + repo.status"
>{{repo.status | title}}</span>
</div>
<div class="item-meta">
<span>
<i class="fab fa-github"></i>
<a
href="https://github.com/{{repo.source.fullName}}/"
ng-bind="repo.source.fullName"
></a>
</span>
<span ng-if="repo.options.update">
<i class="fas fa-code-branch"></i>
<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}"
ng-bind="repo.source.branch"
></a>
</span>
<span ng-if="!repo.options.update">
@<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}"
ng-bind="repo.source.commit.substring(0, 8)"
></a>
</span>
<span>anonymized {{repo.anonymizeDate | humanTime}}</span>
</div>
<div class="item-meta" style="margin-top: 4px">
<span title="Terms: {{::repo.options.terms.join(', ')}}">
<i class="fas fa-shield-alt"></i> {{::repo.options.terms.length | number}}
</span>
<span title="Size">
<i class="fas fa-database"></i> {{::repo.size.storage | humanFileSize}}
</span>
<span>
<i class="far fa-eye"></i> {{::repo.pageView | number}}
</span>
<span>
<i class="far fa-calendar-alt"></i> Last: {{::repo.lastView | humanTime}}
</span>
<span ng-if="repo.options.expirationMode != 'never' && repo.status == 'ready'">
<i class="far fa-clock"></i> Expire: {{repo.options.expirationDate | humanTime}}
</span>
</div>
</div>
<div class="item-actions">
<div class="d-flex flex-wrap" style="gap: 8px;">
<div class="dropdown">
<button
class="btn dropdown-toggle btn-sm"
type="button"
data-toggle="dropdown"
>
Actions
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/anonymize/{{repo.repoId}}">
<i class="far fa-edit"></i> Edit
<button class="btn dropdown-toggle" type="button" id="dropdownSort" data-toggle="dropdown">Sort</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="orderBy = '-anonymizeDate'">
<i class="fas fa-check" ng-show="orderBy == '-anonymizeDate'"></i> Anonymize date
</a>
<a class="dropdown-item" href="/r/{{repo.repoId}}/">
<i class="fa fa-eye"></i> View Repo
<a class="dropdown-item" href="#" ng-click="orderBy = 'repoId'">
<i class="fas fa-check" ng-show="orderBy == 'repoId'"></i> ID
</a>
<a
class="dropdown-item"
href="/w/{{repo.repoId}}/"
target="_self"
ng-if="repo.options.page && repo.status == 'ready'"
>
<i class="fas fa-globe"></i> View Page
</a>
<div class="dropdown-divider"></div>
<a
class="dropdown-item"
href="#"
ng-show="repo.status == 'ready' || repo.status == 'error'"
ng-click="updateRepository(repo)"
>
<i class="fas fa-sync"></i> Force Update
</a>
<a
class="dropdown-item"
href="#"
ng-show="repo.status == 'removed'"
ng-click="updateRepository(repo)"
>
<i class="fas fa-check-circle"></i> Enable
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" ng-click="removeCache(repo)">
<i class="fas fa-broom"></i> Remove Cache
</a>
<a
class="dropdown-item text-danger"
href="#"
ng-show="repo.status == 'ready'"
ng-click="removeRepository(repo)"
>
<i class="fas fa-trash-alt"></i> Remove
<a class="dropdown-item" href="#" ng-click="orderBy = '-status'">
<i class="fas fa-check" ng-show="orderBy == '-status'"></i> Status
</a>
</div>
</div>
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownStatus" data-toggle="dropdown">Status</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Filter by status</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminUserStatusReady" ng-model="filters.status.ready" />
<label class="form-check-label" for="adminUserStatusReady">Ready</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminUserStatusExpired" ng-model="filters.status.expired" />
<label class="form-check-label" for="adminUserStatusExpired">Expired</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" id="adminUserStatusRemoved" ng-model="filters.status.removed" />
<label class="form-check-label" for="adminUserStatusRemoved">Removed</label>
</div>
</div>
</div>
</div>
</form>
<div class="paper-table paper-table-repos w-100" role="table" aria-label="Repositories">
<div class="paper-table-head" role="row">
<div role="columnheader">Repository</div>
<div role="columnheader">Status</div>
<div role="columnheader" class="num">Views</div>
<div role="columnheader">Anonymized</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div
class="paper-table-row"
role="row"
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed'}"
ng-repeat="repo in repositories | filter:repoFiler | orderBy:orderBy as filteredRepositories"
>
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<div class="anon-text">
<a class="repo-name" ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
<div class="anon-sub">
<a href="https://github.com/{{repo.source.fullName}}/" ng-bind="repo.source.fullName"></a><span ng-if="repo.options.update">&nbsp;&middot;&nbsp;<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}" ng-bind="repo.source.branch"></a></span><span ng-if="!repo.options.update">&nbsp;&middot;&nbsp;@<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}" ng-bind="repo.source.commit.substring(0, 8)"></a></span><span>&nbsp;&middot;&nbsp;{{::repo.size.storage | humanFileSize}}</span><span>&nbsp;&middot;&nbsp;{{::repo.options.terms.length | number}} terms</span>
</div>
</div>
</div>
<div class="cell-status" role="cell">
<span class="status-dot" ng-class="{'status-removed': repo.status == 'removed' || repo.status == 'expired', 'status-ready': repo.status == 'ready', 'status-error': repo.status == 'error'}"></span>
<span ng-bind="repo.status | title"></span>
</div>
<div class="cell-views num" role="cell" ng-bind="::repo.pageView | number"></div>
<div class="cell-expires" role="cell" ng-bind="repo.anonymizeDate | humanTime"></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>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/anonymize/{{repo.repoId}}"><i class="far fa-edit"></i> Edit</a>
<a class="dropdown-item" href="/r/{{repo.repoId}}/"><i class="fa fa-eye"></i> View repo</a>
<a class="dropdown-item" href="/w/{{repo.repoId}}/" target="_self" ng-if="repo.options.page && repo.status == 'ready'"><i class="fas fa-globe"></i> View page</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" ng-show="repo.status == 'ready' || repo.status == 'error'" ng-click="updateRepository(repo)"><i class="fas fa-sync"></i> Force update</a>
<a class="dropdown-item" href="#" ng-show="repo.status == 'removed'" ng-click="updateRepository(repo)"><i class="fas fa-check-circle"></i> Enable</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#" ng-click="removeCache(repo)"><i class="fas fa-broom"></i> Remove cache</a>
<a class="dropdown-item text-danger" href="#" ng-show="repo.status == 'ready'" ng-click="removeRepository(repo)"><i class="fas fa-trash-alt"></i> Remove</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" ng-if="filteredRepositories.length == 0">
<i class="fas fa-inbox"></i>
<span>No repositories to display.</span>
</div>
</div>
<div class="empty-state" ng-if="filteredRepositories.length == 0">
<i class="fas fa-code-branch"></i>
No repositories to display.
</div>
</div>
+85 -135
View File
@@ -1,160 +1,110 @@
<div class="container page">
<!-- Admin Navigation -->
<div class="container paper-page">
<div class="paper-crumbs">Admin &nbsp;/&nbsp; <span class="here">Users</span></div>
<h1 class="paper-page-title">Registered <em>users</em></h1>
<p class="paper-page-lede">Browse, search, and manage every account.</p>
<nav class="admin-nav">
<a href="/admin/">
<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/"><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>
</nav>
<!-- Summary Stats -->
<div class="admin-stats">
<div class="admin-stat-card">
<div class="stat-value" ng-bind="total >= 0 ? (total | number) : '...'"></div>
<div class="stat-label">Total Users</div>
<div class="stat-label">Total users</div>
</div>
<div class="admin-stat-card">
<div class="stat-value">{{query.page}}/{{totalPage || '...'}}</div>
<div class="stat-label">Current Page</div>
<div class="stat-label">Current page</div>
</div>
</div>
<!-- Toolbar -->
<div class="admin-toolbar">
<input
type="search"
class="form-control"
aria-label="Search users..."
placeholder="Search users..."
autocomplete="off"
ng-model="query.search"
/>
<div class="pagination-compact">
<button class="btn btn-sm" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
<i class="fas fa-chevron-left"></i>
</button>
<form class="w-100 dashboard-filter-row" aria-label="Users" accept-charset="UTF-8">
<div class="search-wrap">
<input
type="number"
class="form-control form-control-sm"
ng-model="query.page"
min="1"
max="{{totalPage}}"
type="search"
class="form-control"
aria-label="Search users"
placeholder="Search users…"
autocomplete="off"
ng-model="query.search"
/>
<span>/{{totalPage}}</span>
<button class="btn btn-sm" ng-click="query.page = Math.min(totalPage, query.page + 1)" ng-disabled="query.page >= totalPage">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i class="fas fa-sort"></i> Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="query.sort = 'username'">
<i class="fas fa-check" ng-show="query.sort == 'username'"></i> Username
</a>
</div>
</div>
</div>
<!-- User List -->
<div
class="admin-list-item"
ng-repeat="u in users | filter:userFiler | orderBy:orderBy as filteredUsers"
>
<div class="item-main">
<div class="item-title">
<a ng-href="/admin/users/{{u.username}}">
<img
ng-src="{{u.photo}}"
ng-if="u.photo"
width="20"
height="20"
class="rounded-circle mr-1"
style="vertical-align: text-bottom"
/>
{{u.username}}
</a>
<span
class="status-badge"
ng-class="'status-' + u.status"
>{{u.status | title}}</span>
<span
class="status-badge status-active"
ng-if="u.isAdmin"
>Admin</span>
</div>
<div class="item-meta">
<span ng-if="u.emails[0].email">
<i class="fas fa-envelope"></i> {{u.emails[0].email}}
</span>
<span>
<i class="fab fa-github"></i>
<a ng-href="https://github.com/{{u.username}}" target="_blank">{{u.username}}</a>
</span>
</div>
</div>
<div class="item-actions">
<div class="dropdown">
<button
class="btn dropdown-toggle btn-sm"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Actions
<div class="d-flex flex-wrap" style="gap: 8px; align-items: center;">
<div class="pagination-compact">
<button class="btn btn-sm" type="button" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
<i class="fas fa-chevron-left"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/admin/users/{{u.username}}">
<i class="far fa-eye"></i> View Details
</a>
<div class="dropdown-divider"></div>
<a
class="dropdown-item"
href="#"
ng-show="u.status == 'active'"
ng-click="banUser(u)"
>
<i class="fas fa-ban"></i> Ban
</a>
<a
class="dropdown-item"
href="#"
ng-show="u.status == 'removed' || u.status == 'banned'"
ng-click="activateUser(u)"
>
<i class="fas fa-check-circle"></i> Activate
<input type="number" class="form-control form-control-sm" ng-model="query.page" min="1" max="{{totalPage}}" />
<span>/{{totalPage}}</span>
<button class="btn btn-sm" type="button" ng-click="query.page = Math.min(totalPage, query.page + 1)" ng-disabled="query.page >= totalPage">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownSort" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Sort</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Sort by</h6>
<a class="dropdown-item" href="#" ng-click="query.sort = 'username'">
<i class="fas fa-check" ng-show="query.sort == 'username'"></i> Username
</a>
</div>
</div>
</div>
</form>
<div class="paper-table w-100" role="table" aria-label="Users" style="--cols: minmax(280px, 2.4fr) 140px 140px 52px;">
<div class="paper-table-head admin-users-row" role="row">
<div role="columnheader">User</div>
<div role="columnheader">Status</div>
<div role="columnheader">Role</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div
class="paper-table-row admin-users-row"
role="row"
ng-repeat="u in users | filter:userFiler | orderBy:orderBy as filteredUsers"
>
<div class="cell-anon" role="cell">
<img ng-src="{{u.photo}}" ng-if="u.photo" width="28" height="28" class="rounded-circle" style="flex-shrink:0;" />
<div class="anon-text">
<a class="repo-name" ng-href="/admin/users/{{u.username}}" ng-bind="u.username"></a>
<div class="anon-sub">
<span ng-if="u.emails[0].email">{{u.emails[0].email}}</span><span ng-if="u.emails[0].email">&nbsp;&middot;&nbsp;</span><a href="https://github.com/{{u.username}}" target="_blank"><i class="fab fa-github"></i> {{u.username}}</a>
</div>
</div>
</div>
<div class="cell-status" role="cell">
<span class="status-dot" ng-class="{'status-ready': u.status == 'active', 'status-removed': u.status == 'removed' || u.status == 'banned'}"></span>
<span ng-bind="u.status | title"></span>
</div>
<div class="cell-status" role="cell">
<span ng-if="u.isAdmin" class="type-badge type-repo">Admin</span>
<span ng-if="!u.isAdmin" class="empty-dash">&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="Actions">
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/admin/users/{{u.username}}"><i class="far fa-eye"></i> View details</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger" href="#" ng-show="u.status == 'active'" ng-click="banUser(u)"><i class="fas fa-ban"></i> Ban</a>
<a class="dropdown-item" href="#" ng-show="u.status == 'removed' || u.status == 'banned'" ng-click="activateUser(u)"><i class="fas fa-check-circle"></i> Activate</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" ng-if="filteredUsers.length == 0">
<i class="fas fa-inbox"></i>
<span>No users match the current filters.</span>
</div>
</div>
<div class="empty-state" ng-if="filteredUsers.length == 0">
<i class="fas fa-users"></i>
No users match the current filters.
</div>
<!-- Bottom pagination -->
<div class="admin-toolbar" ng-if="totalPage > 1" style="justify-content: center; border-bottom: none;">
<div class="pagination-compact">
<button class="btn btn-sm" ng-click="query.page = Math.max(1, query.page - 1)" ng-disabled="query.page <= 1">
+217 -197
View File
@@ -2,216 +2,170 @@
<!-- ===== STATE 1: No URL — centered input ===== -->
<div class="anonymize-landing" ng-hide="sourceUrl">
<div class="anonymize-landing-inner">
<h2 class="mb-3">Anonymize</h2>
<div class="form-group mb-2">
<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 or pull-request URL. We&rsquo;ll fetch it,
strip every trace of identity, and hand you back a stable link.
</p>
<div class="form-group mt-4 mb-2">
<label class="paper-field-label" for="sourceUrl-landing">Source URL</label>
<input
id="sourceUrl-landing"
type="text"
class="form-control form-control-lg"
ng-model="sourceUrl"
placeholder="Paste a GitHub repo or pull request URL"
placeholder="https://github.com/owner/repo or https://github.com/owner/repo/pull/42"
ng-model-options="{ debounce: {default: 1000, blur: 0, click: 0}, updateOn: 'default blur click' }"
ng-change="urlSelected()"
/>
</div>
<small class="form-text text-muted">
Paste a repository URL to anonymize a repo, or a pull request URL to anonymize a PR.
<small class="form-text" style="color: var(--ink-muted);">
Paste a repository URL to anonymize a repo, or a pull-request URL to anonymize a PR.
</small>
</div>
</div>
<!-- ===== STATE 2: URL provided — preview (left) + form (right) ===== -->
<div class="container-fluid h-100" ng-show="sourceUrl">
<div class="row h-100 flex-column flex-md-row">
<!-- Preview column (left on desktop) -->
<div
class="col-md p-2 overflow-auto markdown-body body anonymize-preview-col"
ng-bind-html="html_readme"
ng-if="detectedType === 'repo' && html_readme"
></div>
<div class="col-md p-2 overflow-auto anonymize-preview-col" ng-if="detectedType === 'pr' && details">
<div class="d-flex w-100 justify-content-between align-items-center flex-wrap">
<h2 class="pr-title mb-1">
<span ng-if="options.title">{{anonymizePrContent(details.pullRequest.title)}}</span>
<span class="badge" ng-class="{'badge-success':details.pullRequest.merged, 'badge-warning':details.pullRequest.state=='open', 'badge-danger':details.pullRequest.state=='closed' && !details.pullRequest.merged}">
{{details.pullRequest.merged ? "merged" : details.pullRequest.state | title}}
</span>
</h2>
<small ng-bind="details.pullRequest.updatedDate | date" ng-if="options.date"></small>
<!-- ===== STATE 2: URL provided — form (left) + preview (right) ===== -->
<div class="anonymize-workspace" ng-show="sourceUrl">
<header class="anonymize-topbar">
<div class="anonymize-topbar-inner">
<div class="paper-crumbs">
<a href="/dashboard">My work</a> &nbsp;/&nbsp;
<span class="here">{{ isUpdate ? 'Edit anonymization' : 'New anonymization' }}</span>
</div>
<small ng-if="options.origin">Pull Request on {{details.pullRequest.baseRepositoryFullName}}</small>
<div class="pr-body shadow-sm p-3 mb-4 rounded" style="background: var(--sidebar-bg-color)" ng-if="options.body">
<markdown content="anonymizePrContent(details.pullRequest.body)" options="options" terms="terms"></markdown>
</div>
<ul class="nav nav-tabs" id="prTabs" role="tablist">
<li class="nav-item" role="presentation" ng-if="options.diff">
<button class="nav-link active" id="pills-diff-tab" data-toggle="pill" data-target="#pills-diff" type="button" role="tab">Diff</button>
</li>
<li class="nav-item" role="presentation" ng-if="options.comments">
<button class="nav-link" ng-class="{'active':!options.diff}" id="pills-comments-tab" data-toggle="pill" data-target="#pills-comments" type="button" role="tab">
<ng-pluralize count="details.pullRequest.comments.length" when="{'0': 'No comment', 'one': 'One Comment', 'other': '{} Comments'}"></ng-pluralize>
</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane show active" id="pills-diff" role="tabpanel" ng-if="options.diff">
<div class="pr-diff shadow-sm p-3 mb-4 rounded" style="background: var(--sidebar-bg-color)">
<pre style="overflow-x: auto"><code ng-bind-html="anonymizePrContent(details.pullRequest.diff) | diff"></code></pre>
</div>
</div>
<div class="tab-pane" ng-class="{'show active':!options.diff}" id="pills-comments" role="tabpanel" ng-if="options.comments">
<ul class="pr-comments list-group">
<li class="pr-comment list-group-item" ng-repeat="comment in details.pullRequest.comments">
<div class="d-flex w-100 justify-content-between flex-wrap">
<h5 class="mb-1" ng-if="options.username">@{{anonymizePrContent(comment.author)}}</h5>
<small ng-bind="comment.updatedDate | date" ng-if="options.date"></small>
</div>
<p class="mb-1">
<markdown class="pr-comment-body" ng-if="options.body" content="anonymizePrContent(comment.body)" options="options" terms="terms"></markdown>
</p>
</li>
</ul>
</div>
<div class="anonymize-topbar-head">
<h1 class="paper-page-title anonymize-topbar-title">
<span ng-if="!isUpdate">New <em>anonymization</em></span>
<span ng-if="isUpdate">Edit <em>anonymization</em></span>
</h1>
<span class="type-badge" ng-show="detectedType" ng-class="{'type-repo': detectedType === 'repo', 'type-pr': detectedType === 'pr'}">
{{detectedType === 'repo' ? 'Repo' : 'PR'}}
</span>
</div>
</div>
</header>
<!-- Form column (right on desktop) -->
<div class="anonymize-split">
<!-- Form column (left) -->
<div class="anonymize-form-col overflow-auto">
<form class="form needs-validation p-3" name="anonymize" novalidate>
<form class="form needs-validation paper-settings-main" name="anonymize" novalidate>
<!-- Source URL -->
<div class="form-group mb-2">
<input
type="text"
class="form-control"
name="sourceUrl"
id="sourceUrl"
ng-class="{'is-invalid': anonymize.sourceUrl.$invalid}"
ng-model="sourceUrl"
placeholder="Paste a GitHub repo or pull request URL"
ng-model-options="{ debounce: {default: 1000, blur: 0, click: 0}, updateOn: 'default blur click' }"
ng-change="urlSelected()"
/>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.github">
Please provide a valid GitHub URL.
</div>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.access">
Not accessible. The organization may restrict access.
</div>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.missing">
Does not exist or is not accessible.
</div>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.used">
Already anonymized.
</div>
</div>
<section class="paper-settings-section">
<div class="paper-section-eyebrow">Source</div>
<div class="mb-2" ng-show="detectedType">
<span class="type-badge" ng-class="{'type-repo': detectedType === 'repo', 'type-pr': detectedType === 'pr'}">
<i ng-class="{'fas fa-code-branch': detectedType === 'repo', 'fas fa-code-merge': detectedType === 'pr'}"></i>
{{detectedType === 'repo' ? 'Repository' : 'Pull Request'}}
</span>
</div>
<!-- ── Source (repo only) ── -->
<h6 class="anonymize-section-title" ng-show="detectedType === 'repo'">
<i class="fas fa-code-branch"></i> Source
</h6>
<div ng-show="detectedType === 'repo'">
<div class="form-group">
<label for="branch">Branch</label>
<div class="input-group mb-1">
<select class="form-control" id="branch" name="branch" ng-model="source.branch">
<option ng-repeat="b in branches" ng-bind="b.name" value="{{b.name}}"></option>
</select>
<div class="input-group-append">
<button class="btn btn-outline-secondary" ng-click="getBranches(true)" title="Refresh" data-toggle="tooltip" data-placement="bottom">
<i class="fa fa-undo"></i>
</button>
<label class="paper-field-label" for="sourceUrl">GitHub URL</label>
<input
type="text"
class="form-control"
name="sourceUrl"
id="sourceUrl"
ng-class="{'is-invalid': anonymize.sourceUrl.$invalid}"
ng-model="sourceUrl"
placeholder="Paste a GitHub repo or pull request URL"
ng-model-options="{ debounce: {default: 1000, blur: 0, click: 0}, updateOn: 'default blur click' }"
ng-change="urlSelected()"
/>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.github">
Please provide a valid GitHub URL.
</div>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.access">
Not accessible. The organization may restrict access.
</div>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.missing">
Does not exist or is not accessible.
</div>
<div class="invalid-feedback" ng-show="anonymize.sourceUrl.$error.used">
Already anonymized.
</div>
</div>
<div ng-show="detectedType === 'repo'" class="form-grid-2">
<div class="form-group">
<label class="paper-field-label" for="branch">Branch</label>
<div class="input-group">
<select class="form-control" id="branch" name="branch" ng-model="source.branch">
<option ng-repeat="b in branches" ng-bind="b.name" value="{{b.name}}"></option>
</select>
<div class="input-group-append">
<button class="btn" type="button" ng-click="getBranches(true)" title="Refresh" data-toggle="tooltip" data-placement="bottom">
<i class="fa fa-undo"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="commit">Commit</label>
<input class="form-control" id="commit" name="commit" pattern="[a-fA-Z0-9]{6,}" ng-model="source.commit" required ng-class="{'is-invalid': anonymize.commit.$invalid}" />
<div class="invalid-feedback" ng-show="anonymize.commit.$error.pattern || anonymize.commit.$error.required">
The commit SHA is not valid.
</div>
</div>
</div>
<div class="form-group">
<label for="commit">Commit</label>
<input class="form-control" id="commit" name="commit" pattern="[a-fA-Z0-9]{6,}" ng-model="source.commit" required ng-class="{'is-invalid': anonymize.commit.$invalid}" />
<small class="form-text text-muted">The SHA of the commit to anonymize.</small>
<div class="invalid-feedback" ng-show="anonymize.commit.$error.pattern || anonymize.commit.$error.required">
The commit SHA is not valid.
</div>
</div>
</div>
<div class="form-group" ng-show="detectedType">
<div class="form-check">
<div class="form-check" ng-show="detectedType">
<input class="form-check-input" type="checkbox" id="update" name="update" ng-model="options.update" />
<label class="form-check-label" for="update">Auto update</label>
<small class="form-text text-muted">Automatically update with the latest changes.</small>
<small class="form-text text-muted">Automatically update with the latest changes (hourly max).</small>
</div>
</div>
</section>
<!-- ── Identity ── -->
<h6 class="anonymize-section-title" ng-show="detectedType">
<i class="fas fa-fingerprint"></i> Identity
</h6>
<section class="paper-settings-section" ng-show="detectedType">
<div class="paper-section-eyebrow">Identity</div>
<div class="form-group" ng-show="detectedType === 'repo'">
<label for="repoId">Anonymized repository ID</label>
<input type="text" class="form-control" name="repoId" id="repoId" ng-class="{'is-invalid': anonymize.repoId.$invalid}" ng-model="repoId" ng-model-options="{ debounce: {default: 1000, blur: 0, click: 0}, updateOn: 'default blur click' }" />
<small class="form-text text-muted">https://anonymous.4open.science/r/{{repoId}}</small>
<div class="invalid-feedback" ng-show="anonymize.repoId.$error.format">ID can only contain letters and numbers.</div>
<div class="invalid-feedback" ng-show="anonymize.repoId.$error.used">{{repoId}} is already used.</div>
</div>
<div class="form-group" ng-show="detectedType === 'pr'">
<label for="pullRequestId">Anonymized pull request ID</label>
<input type="text" class="form-control" name="pullRequestId" id="pullRequestId" ng-class="{'is-invalid': anonymize.pullRequestId.$invalid}" ng-model="pullRequestId" ng-model-options="{ debounce: {default: 1000, blur: 0, click: 0}, updateOn: 'default blur click' }" />
<small class="form-text text-muted">https://anonymous.4open.science/pr/{{pullRequestId}}</small>
<div class="invalid-feedback" ng-show="anonymize.pullRequestId.$error.format">ID can only contain letters and numbers.</div>
<div class="invalid-feedback" ng-show="anonymize.pullRequestId.$error.used">{{pullRequestId}} is already used.</div>
</div>
<div class="form-group" ng-show="detectedType">
<label for="conference">Conference <span class="text-muted">(Optional)</span></label>
<input class="form-control" id="conference" name="conference" ng-model="conference" ng-class="{'is-invalid': anonymize.conference.$invalid}" />
<small class="form-text text-muted" ng-show="conference_data">
<a ng-href="{{conference_data.url}}" target="_blank">{{conference_data.name}}</a> expires {{conference_data.endDate | date}}.
</small>
<div class="invalid-feedback" ng-show="anonymize.conference.$error.activated">The conference is not activated.</div>
<small class="form-text text-muted" ng-show="!conference_data">Links to a conference for automatic settings.</small>
</div>
<!-- ── Anonymization ── -->
<h6 class="anonymize-section-title" ng-show="detectedType">
<i class="fas fa-shield-alt"></i> Anonymization
</h6>
<div class="form-group" ng-show="detectedType">
<label for="terms">Terms to anonymize</label>
<textarea class="form-control" id="terms" name="terms" rows="3" ng-model="terms" ng-model-options="{ debounce: 250 }" ng-class="{'is-invalid': anonymize.terms.$invalid}"></textarea>
<small class="form-text text-muted">One term per line (regex). Replaced by {{site_options.ANONYMIZATION_MASK}}-[N].</small>
<div class="warning-feedback" ng-show="anonymize.terms.$error.regex">Regex characters detected. Escape them if unintentional.</div>
<div class="invalid-feedback" ng-show="anonymize.terms.$error.format">Terms are in an invalid format.</div>
</div>
<!-- ── Display ── -->
<h6 class="anonymize-section-title" ng-show="detectedType">
<i class="fas fa-eye"></i> Display
</h6>
<div ng-show="detectedType">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="link" name="link" ng-model="options.link" />
<label class="form-check-label" for="link">Keep links</label>
<div class="form-group" ng-show="detectedType === 'repo'">
<label class="paper-field-label" for="repoId">Anonymized repository ID</label>
<input type="text" class="form-control" name="repoId" id="repoId" ng-class="{'is-invalid': anonymize.repoId.$invalid}" ng-model="repoId" ng-model-options="{ debounce: {default: 1000, blur: 0, click: 0}, updateOn: 'default blur click' }" />
<small class="form-text text-muted">Your share link will be <code>anonymous.4open.science/r/{{repoId}}</code>.</small>
<div class="invalid-feedback" ng-show="anonymize.repoId.$error.format">ID can only contain letters and numbers.</div>
<div class="invalid-feedback" ng-show="anonymize.repoId.$error.used">{{repoId}} is already used.</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="image" name="image" ng-model="options.image" />
<label class="form-check-label" for="image">Display images</label>
<div class="form-group" ng-show="detectedType === 'pr'">
<label class="paper-field-label" for="pullRequestId">Anonymized pull request ID</label>
<input type="text" class="form-control" name="pullRequestId" id="pullRequestId" ng-class="{'is-invalid': anonymize.pullRequestId.$invalid}" ng-model="pullRequestId" ng-model-options="{ debounce: {default: 1000, blur: 0, click: 0}, updateOn: 'default blur click' }" />
<small class="form-text text-muted">Your share link will be <code>anonymous.4open.science/pr/{{pullRequestId}}</code>.</small>
<div class="invalid-feedback" ng-show="anonymize.pullRequestId.$error.format">ID can only contain letters and numbers.</div>
<div class="invalid-feedback" ng-show="anonymize.pullRequestId.$error.used">{{pullRequestId}} is already used.</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="conference">Conference <span class="paper-optional">(optional)</span></label>
<input class="form-control" id="conference" name="conference" ng-model="conference" ng-class="{'is-invalid': anonymize.conference.$invalid}" />
<small class="form-text text-muted" ng-show="conference_data">
<a ng-href="{{conference_data.url}}" target="_blank">{{conference_data.name}}</a> expires {{conference_data.endDate | date}}.
</small>
<div class="invalid-feedback" ng-show="anonymize.conference.$error.activated">The conference is not activated.</div>
<small class="form-text text-muted" ng-show="!conference_data">Link to a conference to apply its shared defaults.</small>
</div>
</section>
<section class="paper-settings-section" ng-show="detectedType">
<div class="paper-section-eyebrow">Anonymization</div>
<div class="form-group">
<label class="paper-field-label" for="terms">Terms to redact</label>
<textarea class="form-control" id="terms" name="terms" rows="4" ng-model="terms" ng-model-options="{ debounce: 250 }" ng-class="{'is-invalid': anonymize.terms.$invalid}"></textarea>
<small class="form-text text-muted">One term per line (regex allowed). Replaced by <code>{{site_options.ANONYMIZATION_MASK}}-[N]</code>.</small>
<div class="warning-feedback" ng-show="anonymize.terms.$error.regex">Regex characters detected. Escape them if unintentional.</div>
<div class="invalid-feedback" ng-show="anonymize.terms.$error.format">Terms are in an invalid format.</div>
</div>
</section>
<section class="paper-settings-section" ng-show="detectedType">
<div class="paper-section-eyebrow">Display</div>
<div ng-show="detectedType === 'repo'">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="link" name="link" ng-model="options.link" />
<label class="form-check-label" for="link">Keep links</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="image" name="image" ng-model="options.image" />
<label class="form-check-label" for="image">Display images</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="pdf" name="pdf" ng-model="options.pdf" />
<label class="form-check-label" for="pdf">Display PDFs</label>
@@ -256,45 +210,111 @@
<label class="form-check-label" for="origin">Project name</label>
</div>
</div>
</div>
</section>
<!-- ── Expiration ── -->
<h6 class="anonymize-section-title" ng-show="detectedType">
<i class="far fa-clock"></i> Expiration
</h6>
<section class="paper-settings-section" ng-show="detectedType">
<div class="paper-section-eyebrow">Expiration</div>
<div class="form-group" ng-show="detectedType">
<select class="form-control" id="expiration" name="expiration" ng-model="options.expirationMode">
<option value="never" selected>Never expire</option>
<option value="redirect">Redirect to GitHub when expired</option>
<option value="remove">Remove when expired</option>
</select>
</div>
<div class="form-group" ng-show="detectedType && options.expirationMode!='never'">
<label for="expirationDate">Expiration date</label>
<input class="form-control" type="date" name="expirationDate" id="expirationDate" ng-model="options.expirationDate" />
<div class="form-grid-2">
<div class="form-group">
<label class="paper-field-label" for="expiration">Strategy</label>
<select class="form-control" id="expiration" name="expiration" ng-model="options.expirationMode">
<option value="never" selected>Never expire</option>
<option value="redirect">Redirect to GitHub when expired</option>
<option value="remove">Remove when expired</option>
</select>
</div>
<div class="form-group" ng-show="options.expirationMode!='never'">
<label class="paper-field-label" for="expirationDate">Expiration date</label>
<input class="form-control" type="date" name="expirationDate" id="expirationDate" ng-model="options.expirationDate" />
</div>
</div>
<small class="form-text text-muted" ng-show="options.expirationMode=='remove'">After {{options.expirationDate | date}}, the content will be removed.</small>
<small class="form-text text-muted" ng-show="options.expirationMode=='redirect'">After {{options.expirationDate | date}}, visitors will be redirected to GitHub.</small>
</div>
</section>
<div class="alert alert-danger" role="alert" ng-if="error" ng-bind="error"></div>
<div class="anonymize-submit-bar" ng-show="detectedType">
<button type="submit" class="btn btn-primary btn-block" ng-click="anonymizeRepo($event)" ng-if="detectedType === 'repo' && !isUpdate">
<button type="submit" class="btn btn-ink" ng-click="anonymizeRepo($event)" ng-if="detectedType === 'repo' && !isUpdate">
<i class="fas fa-user-secret mr-1"></i> Anonymize Repository
</button>
<button type="submit" class="btn btn-primary btn-block" ng-click="anonymizeRepo($event)" ng-if="detectedType === 'repo' && isUpdate">
<button type="submit" class="btn btn-ink" ng-click="anonymizeRepo($event)" ng-if="detectedType === 'repo' && isUpdate">
<i class="fas fa-save mr-1"></i> Update Repository
</button>
<button type="submit" class="btn btn-primary btn-block" ng-click="anonymizePullRequest($event)" ng-if="detectedType === 'pr' && !isUpdate">
<button type="submit" class="btn btn-ink" ng-click="anonymizePullRequest($event)" ng-if="detectedType === 'pr' && !isUpdate">
<i class="fas fa-user-secret mr-1"></i> Anonymize Pull Request
</button>
<button type="submit" class="btn btn-primary btn-block" ng-click="anonymizePullRequest($event)" ng-if="detectedType === 'pr' && isUpdate">
<button type="submit" class="btn btn-ink" ng-click="anonymizePullRequest($event)" ng-if="detectedType === 'pr' && isUpdate">
<i class="fas fa-save mr-1"></i> Update Pull Request
</button>
</div>
</form>
</div>
<!-- Preview column (right) -->
<div
class="anonymize-preview-col"
ng-if="detectedType === 'repo' && html_readme"
>
<div class="anonymize-preview-head">
<span class="paper-eyebrow">Live preview</span>
<span class="anonymize-preview-sub">README with redactions applied</span>
</div>
<div class="anonymize-preview-body markdown-body body" ng-bind-html="html_readme"></div>
</div>
<div class="anonymize-preview-col" ng-if="detectedType === 'pr' && details">
<div class="anonymize-preview-head">
<span class="paper-eyebrow">Live preview</span>
<span class="anonymize-preview-sub">Pull request with redactions applied</span>
</div>
<div class="anonymize-preview-body">
<div class="d-flex w-100 justify-content-between align-items-center flex-wrap">
<h2 class="pr-title mb-1">
<span ng-if="options.title">{{anonymizePrContent(details.pullRequest.title)}}</span>
<span class="badge" ng-class="{'badge-success':details.pullRequest.merged, 'badge-warning':details.pullRequest.state=='open', 'badge-danger':details.pullRequest.state=='closed' && !details.pullRequest.merged}">
{{details.pullRequest.merged ? "merged" : details.pullRequest.state | title}}
</span>
</h2>
<small ng-bind="details.pullRequest.updatedDate | date" ng-if="options.date"></small>
</div>
<small ng-if="options.origin">Pull Request on {{details.pullRequest.baseRepositoryFullName}}</small>
<div class="pr-body shadow-sm p-3 mb-4 rounded" style="background: var(--paper-bg-alt)" ng-if="options.body">
<markdown content="anonymizePrContent(details.pullRequest.body)" options="options" terms="terms"></markdown>
</div>
<ul class="nav nav-tabs" id="prTabs" role="tablist">
<li class="nav-item" role="presentation" ng-if="options.diff">
<button class="nav-link active" id="pills-diff-tab" data-toggle="pill" data-target="#pills-diff" type="button" role="tab">Diff</button>
</li>
<li class="nav-item" role="presentation" ng-if="options.comments">
<button class="nav-link" ng-class="{'active':!options.diff}" id="pills-comments-tab" data-toggle="pill" data-target="#pills-comments" type="button" role="tab">
<ng-pluralize count="details.pullRequest.comments.length" when="{'0': 'No comment', 'one': 'One Comment', 'other': '{} Comments'}"></ng-pluralize>
</button>
</li>
</ul>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane show active" id="pills-diff" role="tabpanel" ng-if="options.diff">
<div class="pr-diff shadow-sm p-3 mb-4 rounded" style="background: var(--paper-bg-alt)">
<pre style="overflow-x: auto"><code ng-bind-html="anonymizePrContent(details.pullRequest.diff) | diff"></code></pre>
</div>
</div>
<div class="tab-pane" ng-class="{'show active':!options.diff}" id="pills-comments" role="tabpanel" ng-if="options.comments">
<ul class="pr-comments list-group">
<li class="pr-comment list-group-item" ng-repeat="comment in details.pullRequest.comments">
<div class="d-flex w-100 justify-content-between flex-wrap">
<h5 class="mb-1" ng-if="options.username">@{{anonymizePrContent(comment.author)}}</h5>
<small ng-bind="comment.updatedDate | date" ng-if="options.date"></small>
</div>
<p class="mb-1">
<markdown class="pr-comment-body" ng-if="options.body" content="anonymizePrContent(comment.body)" options="options" terms="terms"></markdown>
</p>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
+12 -11
View File
@@ -13,14 +13,15 @@
name="anonymizeForm"
novalidate
>
<h5 class="card-title mb-2">Anonymize a pull request</h5>
<p class="text-muted mb-3" style="font-size: 0.9rem">
Fill the information to anonymize! It will only take 5min.
<div class="paper-crumbs">Anonymize &nbsp;/&nbsp; <span class="here">Pull request</span></div>
<h1 class="paper-page-title">Anonymize a <em>pull request</em></h1>
<p class="paper-page-lede">
Fill in the details &mdash; it only takes a minute.
</p>
<h5 class="anonymize-section-title">
<div class="paper-section-eyebrow anonymize-section-title">
<i class="fab fa-github"></i> Source
</h5>
</div>
<!-- pullRequestUrl -->
<div class="form-group">
<label for="pullRequestUrl"
@@ -76,9 +77,9 @@
</div>
</div>
<h5 class="anonymize-section-title">
<div class="paper-section-eyebrow anonymize-section-title">
<i class="fas fa-chalkboard-teacher"></i> Conference ID
</h5>
</div>
<!-- Conference -->
<div class="form-group">
<label for="conference"
@@ -111,9 +112,9 @@
</small>
</div>
<h5 class="anonymize-section-title">
<div class="paper-section-eyebrow anonymize-section-title">
<i class="fas fa-shield-alt"></i> Anonymization Options
</h5>
</div>
<!-- Pull Request ID -->
<div class="form-group">
<label for="pullRequestId">Anonymized pull request id</label>
@@ -375,7 +376,7 @@
<button
id="submit"
type="submit"
class="btn btn-primary btn-block"
class="btn btn-ink btn-block"
ng-click="anonymizePullRequest($event)"
ng-if="!isUpdate"
>
@@ -384,7 +385,7 @@
<button
id="submit"
type="submit"
class="btn btn-primary btn-block"
class="btn btn-ink btn-block"
ng-click="updatePullRequest($event)"
ng-if="isUpdate"
>
+48 -56
View File
@@ -1,59 +1,51 @@
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col shadow overflow-auto h-100 d-flex ">
<div class="card w-50 m-auto">
<form
class="form needs-validation card-body"
name="claimForm"
novalidate
>
<h5 class="card-title">Claim an anonymized repository</h5>
<h6 class="card-subtitle mb-2 text-muted">
Claim the ownership of an existing anonymized repository.
</h6>
<div class="form-row">
<div class="col form-group">
<label for="repoUrl">Type the url of your repository</label>
<input
type="text"
class="form-control"
name="repoUrl"
id="repoUrl"
required
ng-class="{'is-invalid': claimForm.repoUrl.$invalid}"
ng-model="repoUrl"
/>
<div class="invalid-feedback"
ng-show="claimForm.repoUrl.$error.not_found">The repository is not found.</div>
</div>
</div>
<div class="form-group">
<label for="repoId">Anonymize repository id</label>
<input
type="text"
class="form-control"
name="repoId"
id="repoId"
required
ng-model="repoId"
ng-class="{'is-invalid': claimForm.repoId.$invalid}"
/>
<small id="idHelp" class="form-text text-muted"
>The id is in the repository
https://anonymous.4open.science/r/{id}</small
>
</div>
<button
id="submit"
type="submit"
class="btn black_border"
ng-click="claim()"
>
Claim
</button>
</form>
<div class="container paper-page">
<div class="paper-crumbs">Reference &nbsp;/&nbsp; <span class="here">Claim</span></div>
<h1 class="paper-page-title">Claim an <em>anonymization</em></h1>
<p class="paper-page-lede">
Take ownership of an existing anonymized repository so it appears on your
dashboard.
</p>
<div class="paper-meta-rule"></div>
<form class="form needs-validation" name="claimForm" novalidate>
<div class="form-group">
<label class="paper-field-label" for="repoUrl">Repository URL</label>
<input
type="text"
class="form-control"
name="repoUrl"
id="repoUrl"
required
ng-class="{'is-invalid': claimForm.repoUrl.$invalid}"
ng-model="repoUrl"
/>
<div class="invalid-feedback" ng-show="claimForm.repoUrl.$error.not_found">
The repository is not found.
</div>
</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
ng-model="repoId"
ng-class="{'is-invalid': claimForm.repoId.$invalid}"
/>
<small id="idHelp" class="form-text text-muted"
>The id is in the repository
https://anonymous.4open.science/r/{id}</small
>
</div>
<button
id="submit"
type="submit"
class="btn btn-ink"
ng-click="claim()"
>
Claim
</button>
</form>
</div>
+151 -258
View File
@@ -1,269 +1,162 @@
<div class="container page">
<div class="container page paper-page">
<div>
<h1>
<a ng-href="{{conference.url}}">{{conference.name}}</a>
<span
class="badge"
ng-class="{'badge-warning': conference.status == 'removed' || conference.status == 'expired', 'badge-success': conference.status == 'ready'}"
ng-bind="conference.status | title"
></span>
</h1>
<div class="row mb-3 m-0 py-2 border">
<div class="col-2 font-weight-bold">ID</div>
<div class="col-10">{{conference.conferenceID}}</div>
<div class="col-2 font-weight-bold">Name</div>
<div class="col-10">{{conference.name}}</div>
<div class="col-2 font-weight-bold">URL</div>
<div class="col-10">
<a ng-href="{{conference.url}}" ng-bind="conference.url"></a>
<div class="paper-crumbs">
<a href="/conferences">Conferences</a> &nbsp;/&nbsp; <span class="here">{{conference.conferenceID}}</span>
</div>
<div class="d-flex align-items-end flex-wrap" style="gap: 12px; justify-content: space-between;">
<div>
<h1 class="paper-page-title">
<a ng-href="{{conference.url}}" style="color: var(--color);">{{conference.name}}</a>
</h1>
<p class="paper-page-lede" ng-if="conference.url">
<a ng-href="{{conference.url}}" ng-bind="conference.url"></a>
</p>
</div>
<div class="col-2 font-weight-bold">From</div>
<div class="col-10">{{conference.startDate | date}}</div>
<div class="col-2 font-weight-bold">End</div>
<div class="col-10">{{conference.endDate | date}}</div>
<div class="col-2 font-weight-bold"># Repositories</div>
<div class="col-10">{{::conference.repositories.length | number}}</div>
<div class="col-2 font-weight-bold">Price</div>
<div class="col-10">{{conference.price || 0 | number}} €</div>
<span class="status-dot-wrap">
<span class="status-dot" ng-class="{'status-removed': conference.status == 'removed' || conference.status == 'expired', 'status-ready': conference.status == 'ready'}"></span>
<span ng-bind="conference.status | title"></span>
</span>
</div>
<div class="paper-meta-rule">
<span>ID <b ng-bind="conference.conferenceID"></b></span>
<span>From <b>{{conference.startDate | date}}</b></span>
<span>Ends <b>{{conference.endDate | date}}</b></span>
<span>Repos <b>{{::conference.repositories.length | number}}</b></span>
<span>Price <b>{{conference.price || 0 | number}} &euro;</b></span>
</div>
<h3>Repositories</h3>
<div class="border-bottom color-border-secondary py-3 w-100">
<div class="d-flex flex-items-start w-100">
<form class="w-100" aria-label="Repositories" accept-charset="UTF-8">
<div class="d-flex flex-column flex-lg-row flex-auto">
<div class="mb-1 mb-md-0 mr-md-3">
<input
type="search"
id="search"
class="form-control"
aria-label="Find repositories"
placeholder="Find repositories"
autocomplete="off"
ng-model="search"
/>
</div>
<div class="paper-section-eyebrow">Repositories</div>
<div class="d-flex flex-wrap">
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Select order</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="anonymizeDate"
value="-anonymizeDate"
ng-model="orderBy"
/>
<label class="form-check-label" for="anonymizeDate">
Anonymize Date
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortID"
value="repoId"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortID"> ID </label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortStatus"
value="-status"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortStatus">
Status
</label>
</div>
</div>
</div>
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Select status</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusReady"
value="ready"
ng-model="filters.status.ready"
/>
<label class="form-check-label" for="statusReady">
Ready
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusExpired"
value="expired"
ng-model="filters.status.expired"
/>
<label class="form-check-label" for="statusExpired">
Expired
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusRemoved"
value="removed"
ng-model="filters.status.removed"
/>
<label class="form-check-label" for="statusRemoved">
Removed
</label>
</div>
</div>
</div>
</div>
</div>
</form>
<div
class="d-none d-md-flex flex-md-items-center flex-md-justify-end"
></div>
<form class="w-100 dashboard-filter-row" aria-label="Repositories" accept-charset="UTF-8">
<div class="search-wrap">
<input
type="search"
id="search"
class="form-control"
aria-label="Find repositories"
placeholder="Find repositories…"
autocomplete="off"
ng-model="search"
/>
</div>
</div>
<ul class="p-0 m-0 w-100">
<li
class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary"
ng-class="{'expired': repo.status == 'expired','removed': repo.status == 'removed' }"
ng-repeat="repo in conference.repositories| filter:repoFiler| orderBy:orderBy as filteredRepositories"
>
<div class="w-100">
<div class="">
<h3>
<a ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
<span
class="badge"
ng-class="{'badge-warning': repo.status == 'removed' || repo.status == 'expired', 'badge-success': repo.status == 'ready', 'badge-danger': ''}"
ng-bind="repo.status | title"
></span>
</h3>
<span class="color-text-secondary mb-1">
<span class="repository">
<i class="fab fa-github" aria-hidden="true"></i>
<a
href="https://github.com/{{repo.source.fullName}}/"
class="fullName"
ng-bind="repo.source.fullName"
></a>
</span>
<span class="branch" ng-if="repo.options.update">
<i class="fas fa-code-branch" aria-hidden="true"></i>
<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}"
class="branch"
ng-bind="repo.source.branch"
></a>
</span>
<span class="commit" ng-if="!repo.options.update">
@<a
href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}"
class="commit"
ng-bind="repo.source.commit.substring(0, 8)"
></a>
</span>
anonymized {{repo.anonymizeDate | humanTime}}
</span>
</div>
<div class="color-text-secondary mt-2">
<span
class="ml-0 mr-3"
class="terms"
title="Terms: {{::repo.options.terms.join(', ')}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="fas fa-shield-alt"></i>
{{::repo.options.terms.length | number}}
</span>
<span
class="ml-0 mr-3"
title="Size: {{::repo.size | humanFileSize}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="fas fa-database"></i> {{::repo.size.storage |
humanFileSize}}</span
>
<span
class="ml-0 mr-3"
title="View: {{::repo.pageView | number}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="far fa-eye" aria-hidden="true"></i>
{{::repo.pageView | number}}
</span>
<span
class="ml-0 mr-3"
title="Last view: {{::repo.lastView | date}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="far fa-calendar-alt" aria-hidden="true"></i>
Last view: {{::repo.lastView | humanTime}}</span
>
<span
class="ml-0 mr-3"
ng-if="repo.options.expirationMode!='never' && repo.status == 'ready'"
>
<i class="far fa-clock" aria-hidden="true"></i>
Expire: {{repo.options.expirationDate | humanTime}}</span
>
<div class="d-flex flex-wrap" style="gap: 8px">
<div class="dropdown">
<button
class="btn dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Select order</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="anonymizeDate" value="-anonymizeDate" ng-model="orderBy" />
<label class="form-check-label" for="anonymizeDate">Anonymize Date</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortID" value="repoId" ng-model="orderBy" />
<label class="form-check-label" for="sortID">ID</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortStatus" value="-status" ng-model="orderBy" />
<label class="form-check-label" for="sortStatus">Status</label>
</div>
</div>
</div>
</li>
<li
class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary"
ng-if="filteredRepositories.length == 0"
<div class="dropdown">
<button
class="btn dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Select status</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" name="sort" id="statusReady" value="ready" ng-model="filters.status.ready" />
<label class="form-check-label" for="statusReady">Ready</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" name="sort" id="statusExpired" value="expired" ng-model="filters.status.expired" />
<label class="form-check-label" for="statusExpired">Expired</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" name="sort" id="statusRemoved" value="removed" ng-model="filters.status.removed" />
<label class="form-check-label" for="statusRemoved">Removed</label>
</div>
</div>
</div>
</div>
</form>
<div class="paper-table paper-table-repos w-100" role="table" aria-label="Repositories">
<div class="paper-table-head" role="row">
<div role="columnheader">Repository</div>
<div role="columnheader">Status</div>
<div role="columnheader" class="num">Views</div>
<div role="columnheader">Expires</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div
class="paper-table-row"
role="row"
ng-class="{'repo-inactive': repo.status == 'expired' || repo.status == 'removed'}"
ng-repeat="repo in conference.repositories| filter:repoFiler| orderBy:orderBy as filteredRepositories"
>
There is no repository to display.
</li>
</ul>
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Repo</span>
<div class="anon-text">
<a class="repo-name" ng-href="/r/{{repo.repoId}}" ng-bind="repo.repoId"></a>
<div class="anon-sub">
<a href="https://github.com/{{repo.source.fullName}}/" ng-bind="repo.source.fullName"></a><span ng-if="repo.options.update">&nbsp;&middot;&nbsp;<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.branch}}" ng-bind="repo.source.branch"></a></span><span ng-if="!repo.options.update">&nbsp;&middot;&nbsp;@<a href="https://github.com/{{repo.source.fullName}}/tree/{{repo.source.commit}}" ng-bind="repo.source.commit.substring(0, 8)"></a></span>
</div>
</div>
</div>
<div class="cell-status" role="cell">
<span class="status-dot" ng-class="{'status-removed': repo.status == 'removed' || repo.status == 'expired', 'status-ready': repo.status == 'ready', 'status-error': repo.status == 'error'}"></span>
<span ng-bind="repo.status | title"></span>
</div>
<div class="cell-views num" role="cell" ng-bind="::repo.pageView | number"></div>
<div class="cell-expires" role="cell">
<span ng-if="repo.options.expirationMode !== 'never' && repo.status == 'ready'" ng-bind="repo.options.expirationDate | humanTime"></span>
<span class="empty-dash" ng-if="!(repo.options.expirationMode !== 'never' && repo.status == 'ready')">&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="Actions"
>
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="/anonymize/{{repo.repoId}}">
<i class="far fa-edit" aria-hidden="true"></i> Edit
</a>
<a class="dropdown-item" href="/r/{{repo.repoId}}/">
<i class="fa fa-eye" aria-hidden="true"></i> View
</a>
</div>
</div>
</div>
</div>
<div class="paper-table-empty" ng-if="filteredRepositories.length == 0">
<i class="fas fa-inbox"></i>
<span>There is no repository to display.</span>
</div>
</div>
</div>
</div>
+128 -200
View File
@@ -1,224 +1,152 @@
<div class="container page">
<div class="container page paper-page">
<div class="row">
<h1>Conferences</h1>
<div class="border-bottom color-border-secondary py-3 w-100">
<div class="d-flex flex-items-start w-100">
<form class="w-100" aria-label="Repositories" accept-charset="UTF-8">
<div class="d-flex flex-column flex-lg-row flex-auto">
<div class="mb-1 mb-md-0 mr-md-3">
<input
type="search"
id="search"
class="form-control"
aria-label="Find a conference…"
placeholder="Find a conference…"
autocomplete="off"
ng-model="search"
/>
</div>
<div class="d-flex flex-wrap">
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Select order</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortName"
value="name"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortName">
Name
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortID"
value="conferenceID"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortID"> ID </label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="radio"
name="sort"
id="sortStatus"
value="-status"
ng-model="orderBy"
/>
<label class="form-check-label" for="sortStatus">
Status
</label>
</div>
</div>
</div>
<div class="dropdown mt-1 mt-lg-0 mr-1">
<button
class="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Select status</h6>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusReady"
value="ready"
ng-model="filters.status.ready"
/>
<label class="form-check-label" for="statusReady">
Ready
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusExpired"
value="expired"
ng-model="filters.status.expired"
/>
<label class="form-check-label" for="statusExpired">
Expired
</label>
</div>
<div class="form-check dropdown-item">
<input
class="form-check-input"
type="checkbox"
name="sort"
id="statusRemoved"
value="removed"
ng-model="filters.status.removed"
/>
<label class="form-check-label" for="statusRemoved">
Removed
</label>
</div>
</div>
</div>
</div>
</div>
</form>
<div class="d-none d-md-flex flex-md-items-center flex-md-justify-end">
<a href="/conference/new" class="text-center btn btn-primary ml-3">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add
</a>
<div class="w-100">
<div class="paper-crumbs">Reference &nbsp;/&nbsp; <span class="here">Conferences</span></div>
<div class="d-flex align-items-end justify-content-between flex-wrap" style="gap: 12px;">
<div>
<h1 class="paper-page-title">Your <em>conferences</em></h1>
<p class="paper-page-lede">Group anonymizations by venue; set shared expiry and defaults.</p>
</div>
<a href="/conference/new" class="btn btn-ink">
<i class="fa fa-plus-circle mr-1"></i> New conference
</a>
</div>
</div>
<ul class="p-0 m-0 w-100">
<li
class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary"
ng-class="{'expired': conference.status == 'expired','removed': conference.status == 'removed' }"
ng-repeat="conference in conferences| filter:conferenceFilter| orderBy:orderBy as filteredConferences"
>
<div class="w-100">
<div class="">
<h3>
<a
ng-href="/conference/{{conference.conferenceID}}"
ng-bind="conference.name"
></a>
<span
class="badge"
ng-class="{'badge-warning': conference.status == 'removed' || conference.status == 'expired', 'badge-success': conference.status == 'ready', 'badge-danger': ''}"
ng-bind="conference.status | title"
></span>
</h3>
<span class="color-text-secondary mb-1 ng-binding">
Conference ID: '{{conference.conferenceID}}'
</span>
</div>
<div class="color-text-secondary mt-2">
<span
class="ml-0 mr-3"
title="# repository: {{conference.nbRepository || 0 | number}}"
data-toggle="tooltip"
data-placement="bottom"
>
<i class="fas fa-table"></i>
{{::conference.nbRepositories || 0 | number}}</span
>
<span class="ml-0 mr-3">
<i class="fas fa-euro-sign"></i>
Total: {{conference.price || 0 | number}} €
</span>
<span class="ml-0 mr-3">
<i class="fas fa-calendar-alt"></i>
From {{conference.startDate | date}} to {{conference.endDate |
date}}</span
>
</div>
<form class="w-100 dashboard-filter-row" aria-label="Conferences" accept-charset="UTF-8">
<div class="search-wrap">
<input
type="search"
id="search"
class="form-control"
aria-label="Find a conference…"
placeholder="Find a conference…"
autocomplete="off"
ng-model="search"
/>
</div>
<div class="d-flex">
<div class="d-flex flex-wrap" style="gap: 8px">
<div class="dropdown">
<button
class="btn black_border dropdown-toggle btn-sm"
class="btn dropdown-toggle"
type="button"
id="dropdownSort"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Sort
</button>
<div class="dropdown-menu" aria-labelledby="dropdownSort">
<h6 class="dropdown-header">Select order</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortName" value="name" ng-model="orderBy" />
<label class="form-check-label" for="sortName">Name</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortID" value="conferenceID" ng-model="orderBy" />
<label class="form-check-label" for="sortID">ID</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="radio" name="sort" id="sortStatus" value="-status" ng-model="orderBy" />
<label class="form-check-label" for="sortStatus">Status</label>
</div>
</div>
</div>
<div class="dropdown">
<button
class="btn dropdown-toggle"
type="button"
id="dropdownStatus"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Status
</button>
<div class="dropdown-menu" aria-labelledby="dropdownStatus">
<h6 class="dropdown-header">Select status</h6>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" name="sort" id="statusReady" value="ready" ng-model="filters.status.ready" />
<label class="form-check-label" for="statusReady">Ready</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" name="sort" id="statusExpired" value="expired" ng-model="filters.status.expired" />
<label class="form-check-label" for="statusExpired">Expired</label>
</div>
<div class="form-check dropdown-item">
<input class="form-check-input" type="checkbox" name="sort" id="statusRemoved" value="removed" ng-model="filters.status.removed" />
<label class="form-check-label" for="statusRemoved">Removed</label>
</div>
</div>
</div>
</div>
</form>
</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>
<div role="columnheader">Status</div>
<div role="columnheader" class="num">Repos</div>
<div role="columnheader">Window</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div
class="paper-table-row"
role="row"
ng-class="{'repo-inactive': conference.status == 'expired' || conference.status == 'removed'}"
ng-repeat="conference in conferences| filter:conferenceFilter| orderBy:orderBy as filteredConferences"
>
<div class="cell-anon" role="cell">
<span class="type-badge type-repo">Conf</span>
<div class="anon-text">
<a class="repo-name" ng-href="/conference/{{conference.conferenceID}}" ng-bind="conference.name"></a>
<div class="anon-sub">
ID <a ng-href="/conference/{{conference.conferenceID}}" ng-bind="conference.conferenceID"></a>
</div>
</div>
</div>
<div class="cell-status" role="cell">
<span class="status-dot" ng-class="{'status-removed': conference.status == 'removed' || conference.status == 'expired', 'status-ready': conference.status == 'ready'}"></span>
<span ng-bind="conference.status | title"></span>
</div>
<div class="cell-views num" role="cell" ng-bind="::conference.nbRepositories || 0 | number"></div>
<div class="cell-expires" role="cell">
<span ng-if="conference.startDate">{{conference.startDate | date}} &ndash; {{conference.endDate | date}}</span>
<span class="empty-dash" ng-if="!conference.startDate">&mdash;</span>
</div>
<div class="cell-actions" role="cell">
<div class="dropdown">
<button
class="btn btn-icon-dots"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
aria-label="Actions"
>
Actions
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<a
class="dropdown-item"
href="/conference/{{conference.conferenceID}}/edit"
>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="/conference/{{conference.conferenceID}}/edit">
<i class="far fa-edit" aria-hidden="true"></i> Edit
</a>
<a
class="dropdown-item"
href="#"
ng-show="conference.status != 'removed'"
ng-click="removeConference(conference)"
>
<a class="dropdown-item" href="#" ng-show="conference.status != 'removed'" ng-click="removeConference(conference)">
<i class="fas fa-trash-alt"></i> Remove
</a>
<a
class="dropdown-item"
href="/conference/{{conference.conferenceID}}/"
>
<a class="dropdown-item" href="/conference/{{conference.conferenceID}}/">
<i class="fa fa-eye" aria-hidden="true"></i> View conference
</a>
</div>
</div>
</div>
</li>
<li class="col-12 d-flex px-0 py-3 border-bottom color-border-secondary" ng-if="filteredConferences.length == 0">
There are no conference entries. You can create a&nbsp;<a href="/conference/new">new one</a>.
</li>
</ul>
</div>
<div class="paper-table-empty" ng-if="filteredConferences.length == 0">
<i class="fas fa-inbox"></i>
<span>There are no conference entries. You can create a <a href="/conference/new">new one</a>.</span>
</div>
</div>
</div>
</div>
+67 -97
View File
@@ -1,11 +1,14 @@
<div class="container page dashboard-page">
<div class="container page dashboard-page paper-page">
<div class="row">
<div class="w-100 py-3">
<!-- Header row: title + action button -->
<div class="d-flex align-items-center justify-content-between mb-3">
<h2 class="dashboard-title mb-0">Dashboard</h2>
<a href="/anonymize" class="btn btn-primary btn-sm">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Anonymize
<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>
<h1 class="paper-page-title">Your <em>anonymizations</em></h1>
<p class="paper-page-lede">Every repository and pull request you&rsquo;ve mirrored, with live status and stats.</p>
</div>
<a href="/anonymize" class="btn btn-ink">
<i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> New anonymization
</a>
</div>
@@ -65,20 +68,19 @@
</div>
<!-- Search + filters row -->
<form class="w-100" aria-label="Dashboard" accept-charset="UTF-8">
<div class="d-flex flex-column flex-md-row align-items-md-center" style="gap: 8px">
<div class="flex-grow-1">
<input
type="search"
id="search"
class="form-control"
aria-label="Search..."
placeholder="Search..."
autocomplete="off"
ng-model="search"
/>
</div>
<div class="d-flex flex-wrap" style="gap: 6px">
<form class="w-100 dashboard-filter-row" aria-label="Dashboard" accept-charset="UTF-8">
<div class="search-wrap">
<input
type="search"
id="search"
class="form-control"
aria-label="Search..."
placeholder="Search anonymizations…"
autocomplete="off"
ng-model="search"
/>
</div>
<div class="d-flex flex-wrap" style="gap: 8px">
<!-- Type filter -->
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn" ng-class="{'btn-primary': typeFilter === 'all'}" ng-click="typeFilter = 'all'">All</button>
@@ -150,7 +152,6 @@
</div>
</div>
</div>
</div>
</form>
<!-- Active filter chips -->
@@ -164,90 +165,59 @@
</div>
</div>
<!-- Unified item list -->
<ul class="repo-list w-100">
<li
class="repo-list-item"
<!-- Table -->
<div class="paper-table w-100" role="table" aria-label="Anonymizations">
<div class="paper-table-head" role="row">
<div role="columnheader">Anonymization</div>
<div role="columnheader">Conference</div>
<div role="columnheader">Status</div>
<div role="columnheader" class="num">Views</div>
<div role="columnheader">Expires</div>
<div role="columnheader" aria-label="Actions"></div>
</div>
<div
class="paper-table-row"
role="row"
ng-class="{'repo-inactive': item.status == 'expired' || item.status == 'removed' || item.status == 'error'}"
ng-repeat="item in items | filter:itemFilter | orderBy:orderBy as filteredItems"
>
<div class="repo-list-item-content">
<div class="repo-list-item-main">
<div class="repo-list-item-header">
<span class="type-badge" ng-class="{'type-repo': item._type === 'repo', 'type-pr': item._type === 'pr'}">
<i ng-class="{'fas fa-code-branch': item._type === 'repo', 'fas fa-code-merge': item._type === 'pr'}"></i>
{{item._type === 'repo' ? 'Repo' : 'PR'}}
</span>
<a ng-href="{{item._viewUrl}}" class="repo-name" ng-bind="item._name"></a>
<span
class="status-badge"
ng-class="{'status-removed': item.status == 'removed' || item.status == 'expired' || item.status == 'removing' || item.status == 'expiring', 'status-preparing': item.status == 'preparing' || item.status == 'download', 'status-ready': item.status == 'ready', 'status-error': item.status == 'error'}"
><span ng-bind="item.status | title"></span><span
ng-if="item.status == 'error' && item.statusMessage"
ng-bind="': ' + item.statusMessage"
></span></span>
<div class="cell-anon" role="cell">
<span class="type-badge" ng-class="{'type-repo': item._type === 'repo', 'type-pr': item._type === 'pr'}">{{item._type === 'repo' ? 'Repo' : 'PR'}}</span>
<div class="anon-text">
<a ng-href="{{item._viewUrl}}" class="repo-name" ng-bind="item._name"></a>
<div class="anon-sub">
<a ng-if="item._type === 'repo'" href="https://github.com/{{item.source.fullName}}/" ng-bind="item.source.fullName"></a><span ng-if="item._type === 'repo' && item.options.update">&nbsp;&middot;&nbsp;<a href="https://github.com/{{item.source.fullName}}/tree/{{item.source.branch}}" ng-bind="item.source.branch"></a></span><span ng-if="item._type === 'repo' && !item.options.update">&nbsp;&middot;&nbsp;@<a href="https://github.com/{{item.source.fullName}}/tree/{{item.source.commit}}" ng-bind="item.source.commit.substring(0, 8)"></a></span>
<a ng-if="item._type === 'pr'" href="https://github.com/{{item.source.repositoryFullName}}/pull/{{item.source.pullRequestId}}" ng-bind="item._source"></a>
</div>
<div class="repo-source">
<span>
<i class="fab fa-github" aria-hidden="true"></i>
<a
ng-if="item._type === 'repo'"
href="https://github.com/{{item.source.fullName}}/"
ng-bind="item.source.fullName"
></a>
<a
ng-if="item._type === 'pr'"
href="https://github.com/{{item.source.repositoryFullName}}/pull/{{item.source.pullRequestId}}"
ng-bind="item._source"
></a>
</span>
<span ng-if="item._type === 'repo' && item.options.update">
<i class="fas fa-code-branch" aria-hidden="true"></i>
<a
href="https://github.com/{{item.source.fullName}}/tree/{{item.source.branch}}"
ng-bind="item.source.branch"
></a>
</span>
<span ng-if="item._type === 'repo' && !item.options.update">
@<a
href="https://github.com/{{item.source.fullName}}/tree/{{item.source.commit}}"
ng-bind="item.source.commit.substring(0, 8)"
></a>
</span>
<span class="repo-date">anonymized {{item.anonymizeDate | humanTime}}</span>
</div>
</div>
<div class="repo-meta">
<span ng-if="item.conference" title="Conference">
<i class="fas fa-chalkboard-teacher"></i> {{item.conference}}
</span>
<span title="Terms" data-toggle="tooltip" data-placement="bottom">
<i class="fas fa-shield-alt"></i> {{item.options.terms.length | number}}
</span>
<span ng-if="item._type === 'repo' && item.size" title="Size" data-toggle="tooltip" data-placement="bottom">
<i class="fas fa-database"></i> {{item.size.storage | humanFileSize}}
</span>
<span title="Views" data-toggle="tooltip" data-placement="bottom">
<i class="far fa-eye"></i> {{item.pageView | number}}
</span>
<span title="Last view" data-toggle="tooltip" data-placement="bottom">
<i class="far fa-calendar-alt"></i> {{item.lastView | humanTime}}
</span>
<span ng-if="item.options.expirationMode !== 'never' && item.status == 'ready'">
<i class="far fa-clock"></i> Expire: {{item.options.expirationDate | humanTime}}
</span>
</div>
</div>
<div class="repo-list-item-actions">
<div class="cell-conf" role="cell">
<span ng-if="item.conference" ng-bind="item.conference"></span>
<span class="empty-dash" ng-if="!item.conference">&mdash;</span>
</div>
<div class="cell-status" role="cell">
<span
class="status-dot"
ng-class="{'status-removed': item.status == 'removed' || item.status == 'expired' || item.status == 'removing' || item.status == 'expiring', 'status-preparing': item.status == 'preparing' || item.status == 'download', 'status-ready': item.status == 'ready', 'status-error': item.status == 'error'}"
></span>
<span ng-bind="item.status | title"></span>
</div>
<div class="cell-views num" role="cell" ng-bind="item.pageView | number"></div>
<div class="cell-expires" role="cell">
<span ng-if="item.options.expirationMode !== 'never' && item.status == 'ready'" ng-bind="item.options.expirationDate | humanTime"></span>
<span class="empty-dash" ng-if="!(item.options.expirationMode !== 'never' && item.status == 'ready')">&mdash;</span>
</div>
<div class="cell-actions" role="cell">
<div class="dropdown">
<button
class="btn btn-sm dropdown-toggle"
class="btn btn-icon-dots"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
aria-label="Actions"
>
Actions
<i class="fas fa-ellipsis-h" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" ng-href="{{item._editUrl}}">
@@ -271,11 +241,11 @@
</div>
</div>
</div>
</li>
<li class="repo-list-empty" ng-if="filteredItems.length == 0">
</div>
<div class="paper-table-empty" ng-if="filteredItems.length == 0">
<i class="fas fa-inbox"></i>
<span>Nothing to display.</span>
</li>
</ul>
</div>
</div>
</div>
</div>
+5 -5
View File
@@ -36,35 +36,35 @@
<a
ng-if="options.isAdmin || options.isOwner"
ng-href="/anonymize/{{repoId}}"
class="btn btn-outline-primary btn-sm"
class="btn btn-sm"
>Edit</a
>
<a
ng-show="content != null"
ng-href="{{url}}"
target="__self"
class="btn btn-outline-primary btn-sm"
class="btn btn-sm"
>Raw</a
>
<a
ng-show="content != null"
ng-href="{{url}}&download=true"
target="__self"
class="btn btn-outline-primary btn-sm"
class="btn btn-sm"
><i class="fas fa-download"></i><span class="d-none d-md-inline"> Download</span></a
>
<a
ng-if="options.download"
ng-href="/api/repo/{{repoId}}/zip"
target="__self"
class="btn btn-outline-primary btn-sm"
class="btn btn-sm"
><i class="fas fa-file-archive"></i><span class="d-none d-md-inline"> ZIP</span></a
>
<a
ng-if="options.hasWebsite"
ng-href="/w/{{repoId}}/"
target="__self"
class="btn btn-outline-primary btn-sm"
class="btn btn-sm"
>Website</a
>
</div>
+27 -17
View File
@@ -1,18 +1,28 @@
<div class="container-fluid h-100">
<div class="row h-100">
<div class="container px-md-3 px-sm-0">
<section
class="faq-section clearfix mt-3"
aria-label="FAQs"
>
<div class="container">
<h2 class="text-center pt-4 pb-2">Frequently Asked Questions</h2>
<p class="text-center text-muted mb-4" style="font-size: 1.1rem;">
Everything you need to know about using Anonymous GitHub for double-anonymous peer review.
</p>
<div class="paper-faq">
<div class="paper-faq-hero">
<div class="paper-faq-eyebrow">Help</div>
<h1 class="paper-faq-title">Answers to the <em>questions</em> that come up most.</h1>
<p class="paper-faq-lede">
If something's missing, write to us &mdash;
<a href="https://github.com/tdurieux/anonymous_github/issues">open an issue on GitHub</a>.
</p>
</div>
<div class="paper-faq-body">
<aside class="paper-faq-toc">
<div class="paper-faq-toc-head">Contents</div>
<nav>
<a href="#faq-general">General</a>
<a href="#faq-features">Features</a>
<a href="#faq-limitations">Limitations</a>
<a href="#faq-privacy">Privacy &amp; Security</a>
<a href="#faq-hosting">Self-Hosting</a>
</nav>
</aside>
<section class="faq-section" aria-label="FAQs">
<div>
<!-- General -->
<h4 class="mt-4 mb-3" style="color: var(--link-color); font-weight: 600; border-bottom: 2px solid var(--border-color); padding-bottom: 0.5rem;">
<h4 class="faq-cat">
<i class="fas fa-info-circle mr-2"></i>General
</h4>
<div
@@ -159,7 +169,7 @@
</div>
<!-- Features -->
<h4 class="mt-5 mb-3" style="color: var(--link-color); font-weight: 600; border-bottom: 2px solid var(--border-color); padding-bottom: 0.5rem;">
<h4 class="faq-cat">
<i class="fas fa-cogs mr-2"></i>Features
</h4>
<div
@@ -376,7 +386,7 @@ anonymous_github</code></pre>
</div>
<!-- Limitations -->
<h4 class="mt-5 mb-3" style="color: var(--link-color); font-weight: 600; border-bottom: 2px solid var(--border-color); padding-bottom: 0.5rem;">
<h4 class="faq-cat">
<i class="fas fa-exclamation-triangle mr-2"></i>Limitations
</h4>
<div
@@ -431,7 +441,7 @@ anonymous_github</code></pre>
</div>
<!-- Privacy & Security -->
<h4 class="mt-5 mb-3" style="color: var(--link-color); font-weight: 600; border-bottom: 2px solid var(--border-color); padding-bottom: 0.5rem;">
<h4 class="faq-cat">
<i class="fas fa-shield-alt mr-2"></i>Privacy &amp; Security
</h4>
<div
@@ -507,7 +517,7 @@ anonymous_github</code></pre>
</div>
<!-- Self-Hosting -->
<h4 class="mt-5 mb-3" style="color: var(--link-color); font-weight: 600; border-bottom: 2px solid var(--border-color); padding-bottom: 0.5rem;">
<h4 class="faq-cat">
<i class="fas fa-server mr-2"></i>Self-Hosting
</h4>
<div
+22 -23
View File
@@ -1,8 +1,8 @@
<nav
class="navbar navbar-expand-lg navbar-dark bg-dark"
ng-class="{'shadow': !generalMessage}"
class="navbar navbar-expand-lg"
ng-class="{'navbar-dark': isDarkMode}"
>
<a class="navbar-brand" href="/"> Anonymous GitHub </a>
<a class="navbar-brand" href="/">Anonymous <em>GitHub</em></a>
<button
class="navbar-toggler"
type="button"
@@ -29,7 +29,7 @@
href="/dashboard"
>
<i class="fas fa-th-large d-lg-none mr-1"></i>
Dashboard
My work
</a>
</li>
<li class="nav-item" ng-if="user">
@@ -52,6 +52,11 @@
Conferences
</a>
</li>
<li class="nav-item">
<a class="nav-link" ng-class="{'active':path == '/faq'}" href="/faq"
>FAQ</a
>
</li>
<li class="nav-item" ng-if="user && user.isAdmin">
<a
class="nav-link"
@@ -69,15 +74,11 @@
class="nav-link nav-icon"
target="_blank"
href="https://github.com/tdurieux/anonymous_github/"
title="GitHub repository"
data-offset="30"
><i class="fab fa-github" aria-hidden="true"></i
></a>
</li>
<li class="nav-item">
<a class="nav-link" ng-class="{'active':path == '/faq'}" href="/faq"
>FAQ</a
>
</li>
<li class="nav-item d-none d-lg-block">
<a
class="nav-link"
@@ -89,28 +90,26 @@
</li>
<li class="nav-item" ng-if="!isDarkMode">
<a class="nav-link" href="#" ng-click="darkMode(true);">
<i class="fas fa-moon d-lg-none mr-1"></i>
Dark Mode
<a class="nav-link nav-icon" href="#" ng-click="darkMode(true);" title="Dark mode">
<i class="fas fa-moon"></i>
</a>
</li>
<li class="nav-item" ng-if="isDarkMode">
<a class="nav-link" href="#" ng-click="darkMode(false);">
<i class="fas fa-sun d-lg-none mr-1"></i>
Light Mode
<a class="nav-link nav-icon" href="#" ng-click="darkMode(false);" title="Light mode">
<i class="fas fa-sun"></i>
</a>
</li>
<li class="nav-item" ng-if="!user">
<a class="nav-link" target="_self" href="/github/login" data-offset="30"
><i class="fab fa-github d-lg-none mr-1"></i>
Login
<a class="nav-link btn-signin" target="_self" href="/github/login" data-offset="30"
><i class="fab fa-github mr-1"></i>
Sign in
</a>
</li>
<li class="nav-item dropdown" ng-if="user">
<li class="nav-item dropdown user-chip-wrap" ng-if="user">
<a
class="nav-link dropdown-toggle"
class="nav-link user-chip dropdown-toggle"
href="#"
id="navbarDropdownMenuLink"
role="button"
@@ -121,11 +120,11 @@
<img
ng-src="{{user.photo}}"
ng-if="user.photo"
width="30"
height="30"
width="22"
height="22"
class="rounded-circle"
/>
{{user.username}}
<span class="user-chip-name">{{user.username}}</span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" href="/profile">Default settings</a>
+246 -130
View File
@@ -1,142 +1,258 @@
<div class="container-fluid">
<div
id="home"
class="row view rgba-gradient d-flex align-self-stretch justify-content-center align-items-center"
>
<div class="container px-md-3 px-sm-0">
<div class="row fadeIn main-options">
<div class="col-md-12 mb-4 white-text text-center fadeIn">
<h1 class="hero-title pt-5">
Anonymous GitHub
</h1>
<p class="hero-subtitle mt-3 mb-4">
Anonymize your repository in 5 minutes &mdash; join {{stat.nbUsers | number}} users.
</p>
<span ng-if="!user">
<a
href="/github/login"
target="_self"
class="btn btn-hero"
><i class="fab fa-github mr-2"></i>Login with GitHub</a
>
</span>
<span ng-if="user">
<a
href="/anonymize"
class="btn btn-hero"
><i class="fa fa-plus-circle mr-2"></i>Anonymize a Repository</a
>
</span>
<div class="paper-landing">
<!-- Hero -->
<section class="paper-hero">
<div class="paper-eyebrow">For double-blind peer review</div>
<h1 class="hero-title">
Share the code,<br />
<span class="accent">not the author.</span>
</h1>
<p class="hero-subtitle">
Anonymous GitHub makes a read-only mirror of your repository or pull
request with every trace of identity removed &mdash; so you can attach a
link to your submission without breaking anonymity.
</p>
<div class="paper-cta-row">
<span ng-if="!user">
<a href="/github/login" target="_self" class="btn-hero">
<i class="fab fa-github mr-2"></i>Sign in with GitHub
</a>
</span>
<span ng-if="user">
<a href="/anonymize" class="btn-hero">
Anonymize a repository &rarr;
</a>
</span>
<a href="/anonymize" ng-if="user" class="btn-hero-ghost">
Anonymize a pull request
</a>
<span class="meta">Free &middot; Open source &middot; Self-hostable</span>
</div>
<!-- Before / after -->
<div class="paper-preview" aria-label="Before and after example">
<div class="pane">
<div class="pane-header">
<span class="pane-label">Before &middot; your repo</span>
<span class="pane-url">github.com/jane-smith/DeepLearnUtils</span>
</div>
<div class="pane-body">
<div><span class="tok">#</span> <strong>DeepLearnUtils</strong></div>
<div>Developed by Jane Smith at MIT CSAIL.</div>
<div>See Smith et&nbsp;al., &ldquo;Attention That Works,&rdquo; 2026.</div>
<div>Contact: jane.smith@mit.edu</div>
</div>
</div>
<div class="pane">
<div class="pane-header">
<span class="pane-label">After &middot; anonymous mirror</span>
<span class="pane-url">anonymous.4open.science/r/paper-7f2a</span>
</div>
<div class="pane-body">
<div><span class="tok">#</span> <strong>DeepLearnUtils</strong></div>
<div>Developed by <span class="redact">Jane Smith</span> at <span class="redact">MIT CSAIL</span>.</div>
<div>See <span class="redact">Smith et al.</span>, &ldquo;Attention That Works,&rdquo; 2026.</div>
<div>Contact: <span class="redact">jane.smith@mit.edu</span></div>
</div>
</div>
</div>
</div>
<main>
<div class="container home-content">
<div class="row pt-5">
<div class="col-md-12">
<h2 id="usage">How it works</h2>
<ol class="usage-steps">
<li>
<a href="/github/login" target="_self">Login</a> with GitHub to access your dashboard and anonymize your repositories.
</li>
<li>Configure which terms, links, and images to anonymize.</li>
<li>Share the anonymous link in your double-blind paper submission.</li>
</ol>
<p class="usage-example">
</section>
<!-- Stats strip -->
<section class="paper-stats" id="metrics">
<div class="paper-stats-inner">
<div>
<div class="paper-stat-value">{{stat.nbRepositories | number}}</div>
<div class="paper-stat-label">repositories anonymized</div>
</div>
<div>
<div class="paper-stat-value">{{stat.nbUsers | number}}</div>
<div class="paper-stat-label">researchers</div>
</div>
<div>
<div class="paper-stat-value">{{stat.nbPageViews | number}}</div>
<div class="paper-stat-label">page views</div>
</div>
</div>
</section>
<!-- How it works -->
<section class="paper-how" id="usage">
<div>
<div class="paper-eyebrow">How it works</div>
<h2 class="paper-how-heading">
Three steps,<br />about a minute.
</h2>
</div>
<div>
<div class="paper-step">
<div class="step-num">01</div>
<div>
<div class="step-title">Authenticate</div>
<div class="step-desc">
<a href="/github/login" target="_self">Sign in with GitHub</a> to
access your dashboard. We ask for read-only access to the
repositories you pick &mdash; nothing else.
</div>
</div>
</div>
<div class="paper-step">
<div class="step-num">02</div>
<div>
<div class="step-title">Configure</div>
<div class="step-desc">
Point us at a repo, branch, commit, or pull request. List the
terms to redact (names, affiliation, emails). Set an expiry.
</div>
</div>
</div>
<div class="paper-step">
<div class="step-num">03</div>
<div>
<div class="step-title">Share</div>
<div class="step-desc">
You get a stable link with a randomised slug. Reviewers browse,
diff, and download &mdash; never seeing who you are.
</div>
</div>
</div>
<div class="paper-step" style="border-top: none;">
<div class="step-num">&nbsp;</div>
<div>
<div class="step-desc">
Example:
<a
target="_self"
href="https://anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389/"
>https://anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389/</a
>
</p>
</div>
</div>
<hr class="featurette-divider" />
<div class="row featurette align-items-center">
<div class="col-md-7 order-md-2">
<h2 class="featurette-heading">
Double-anonymous
</h2>
<p class="featurette-lead">
Anonymize your GitHub repository with options to remove links,
images, or specific terms. Keep full control &mdash; set an
expiration date to make your repository unavailable after review.
</p>
</div>
<div class="col-md-5 order-md-1">
<img
width="500"
src="/imgs/anonymize.png"
class="featurette-image img-fluid mx-auto"
/>
</div>
</div>
<hr class="featurette-divider" />
<div class="row featurette align-items-center">
<div class="col-md-7">
<h2 class="featurette-heading">
Explorer
</h2>
<p class="featurette-lead">
Reviewers can browse your repository with highlighted source code,
rendered PDFs, images, and notebooks.
<a href="https://pages.github.com">GitHub Pages</a>
is also supported.
</p>
</div>
<div class="col-md-5">
<img
width="500"
src="/imgs/explorer.png"
class="featurette-image img-fluid mx-auto"
/>
</div>
</div>
<hr class="featurette-divider" />
<div class="row featurette align-items-center">
<div class="col-md-7 order-md-2">
<h2 class="featurette-heading">
Manage
</h2>
<p class="featurette-lead">
Monitor views, edit configuration, remove or update your repository &mdash;
all from a clean dashboard.
</p>
</div>
<div class="col-md-5 order-md-1">
<img
width="500"
src="/imgs/dashboard.png"
class="featurette-image img-fluid mx-auto"
/>
</div>
</div>
<hr class="featurette-divider" />
<h2 id="about" class="mb-4">Metrics</h2>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-value">{{stat.nbUsers | number}}</div>
<div class="metric-label">Users</div>
</div>
<div class="metric-card">
<div class="metric-value">{{stat.nbRepositories | number}}</div>
<div class="metric-label">Repositories</div>
</div>
<div class="metric-card">
<div class="metric-value">{{stat.nbPageViews | number}}</div>
<div class="metric-label">Page Views</div>
>anonymous.4open.science/r/840c8c57-3c32-451e-bf12-0e20be300389</a>
</div>
</div>
</div>
</div>
</section>
<!-- Trust row -->
<section class="paper-trust">
<div class="paper-trust-inner">
<div>
<div class="paper-eyebrow">Trusted by the community</div>
<div class="paper-trust-lede">
Used by authors submitting to major software-engineering and systems
venues.
</div>
</div>
<div class="paper-trust-item">
<div class="short">ICSE</div>
<div class="long">International Conference on Software Engineering</div>
</div>
<div class="paper-trust-item">
<div class="short">FSE</div>
<div class="long">Foundations of Software Engineering</div>
</div>
<div class="paper-trust-item">
<div class="short">OOPSLA</div>
<div class="long">
Object-Oriented Programming, Systems, Languages, and Applications
</div>
</div>
</div>
</section>
<!-- Featurettes — existing content, kept but restyled -->
<main class="container home-content" id="about">
<div class="row featurette align-items-center">
<div class="col-md-7 order-md-2">
<h2 class="featurette-heading">Double-anonymous</h2>
<p class="featurette-lead">
Anonymize your GitHub repository with options to remove links,
images, or specific terms. Keep full control &mdash; set an
expiration date to make your repository unavailable after review.
</p>
</div>
<div class="col-md-5 order-md-1">
<img
width="500"
src="/imgs/anonymize.png"
class="featurette-image img-fluid mx-auto"
alt="Anonymize form"
/>
</div>
</div>
<hr class="featurette-divider" />
<div class="row featurette align-items-center">
<div class="col-md-7">
<h2 class="featurette-heading">Explorer</h2>
<p class="featurette-lead">
Reviewers can browse your repository with highlighted source code,
rendered PDFs, images, and notebooks.
<a href="https://pages.github.com">GitHub Pages</a> is also supported.
</p>
</div>
<div class="col-md-5">
<img
width="500"
src="/imgs/explorer.png"
class="featurette-image img-fluid mx-auto"
alt="Repository explorer"
/>
</div>
</div>
<hr class="featurette-divider" />
<div class="row featurette align-items-center">
<div class="col-md-7 order-md-2">
<h2 class="featurette-heading">Manage</h2>
<p class="featurette-lead">
Monitor views, edit configuration, remove or update your repository
&mdash; all from a clean dashboard.
</p>
</div>
<div class="col-md-5 order-md-1">
<img
width="500"
src="/imgs/dashboard.png"
class="featurette-image img-fluid mx-auto"
alt="Dashboard"
/>
</div>
</div>
</main>
<footer class="paper-footer">
<div class="paper-footer-inner">
<div class="paper-footer-brand">
<div class="paper-footer-mark">Anonymous <em>GitHub</em></div>
<p class="paper-footer-tag">
A read-only, identity-stripped mirror of your repository &mdash;
built for double-blind peer review.
</p>
</div>
<div class="paper-footer-col">
<div class="paper-footer-head">Product</div>
<a href="/anonymize">Anonymize</a>
<a href="/dashboard" ng-if="user">My work</a>
<a href="/conferences" ng-if="user">Conferences</a>
<a href="/faq">FAQ</a>
</div>
<div class="paper-footer-col">
<div class="paper-footer-head">Project</div>
<a href="https://github.com/tdurieux/anonymous_github/" target="_blank">Source code</a>
<a href="https://github.com/tdurieux/anonymous_github/issues/new" target="_blank">Report an issue</a>
<a href="https://github.com/sponsors/tdurieux/" target="_blank">Sponsor</a>
</div>
<div class="paper-footer-col">
<div class="paper-footer-head">Contact</div>
<a href="mailto:tdurieux@anonymous.4open.science">Email maintainer</a>
<a href="https://github.com/tdurieux" target="_blank">@tdurieux</a>
</div>
</div>
<div class="paper-footer-rule"></div>
<div class="paper-footer-foot">
<span>&copy; {{year || 2026}} Anonymous GitHub &middot; MIT licensed</span>
<span class="paper-footer-meta">Built for reviewers, by researchers.</span>
</div>
</footer>
</div>
+9 -3
View File
@@ -1,4 +1,10 @@
<div class="container d-flex h-100">
<h1 class="display-1 m-auto" ng-if="!error">Loading...</h1>
<h1 class="display-1 m-auto" ng-if="error" translate="{{error}}"></h1>
<div class="paper-empty">
<div class="paper-empty-inner">
<div class="paper-eyebrow" ng-if="!error">Working</div>
<div class="paper-eyebrow" ng-if="error">Error</div>
<h1 class="paper-empty-title" ng-if="!error">
Loading<span class="dot-anim">&hellip;</span>
</h1>
<h1 class="paper-empty-title" ng-if="error" translate="{{error}}"></h1>
</div>
</div>
+248 -423
View File
@@ -1,443 +1,268 @@
<div class="container py-4">
<h2>Create a conference</h2>
<p>
A conference allow the the chairs to access the complete list of anonymized
repositories. It also allows to increase the quota for authors.
<div class="container paper-page paper-settings">
<div class="paper-crumbs"><a href="/conferences">Conferences</a> &nbsp;/&nbsp; <span class="here">{{editionMode ? 'Edit' : 'New'}}</span></div>
<h1 class="paper-page-title">{{editionMode ? 'Edit' : 'Create a'}} <em>conference</em></h1>
<p class="paper-page-lede">
Give chairs access to every anonymization submitted to a venue, and lift
author quotas during the review window.
</p>
<form class="form needs-validation" name="conference" novalidate>
<!-- name -->
<div class="form-group">
<label for="name">The name of the conference</label>
<input
type="text"
class="form-control"
name="name"
id="name"
required
ng-class="{'is-invalid': conference.name.$invalid}"
ng-model="options.name"
placeholder="The name of the conference"
/>
<div class="invalid-feedback" ng-show="conference.name.$error.invalid">
The name of the conference is invalid.
</div>
<div class="invalid-feedback" ng-show="conference.name.$error.required">
The name of the conference is required.
</div>
</div>
<!-- url -->
<div class="form-group">
<label for="url">The url of the conference</label>
<input
type="url"
class="form-control"
name="url"
id="url"
ng-class="{'is-invalid': conference.url.$invalid}"
ng-model="options.url"
placeholder="The url of the conference"
/>
<div class="invalid-feedback" ng-show="conference.url.$error.invalid">
The url of the conference is invalid.
</div>
</div>
<!-- conferenceID -->
<div class="form-group">
<label for="conferenceID">The conference ID</label>
<input
type="text"
class="form-control"
name="conferenceID"
id="conferenceID"
required
pattern="[a-zA-Z0-9\-_]{3,10}"
ng-class="{'is-invalid': conference.conferenceID.$invalid}"
ng-model="options.conferenceID"
placeholder="The conference ID that the authors will reference"
/>
<div
class="invalid-feedback"
ng-show="conference.conferenceID.$error.used"
>
The conference ID '{{options.conferenceID}}' is already used.
</div>
<div
class="invalid-feedback"
ng-show="conference.conferenceID.$error.required"
>
The conference ID is required.
</div>
<div
class="invalid-feedback"
ng-show="conference.conferenceID.$error.pattern"
>
The format of the conference ID is incorrect ([a-zA-Z0-9-_]{3,10}).
</div>
</div>
<!-- startDate -->
<div class="form-group">
<label for="startDate">Start date of the conference</label>
<input
type="date"
class="form-control"
name="startDate"
id="startDate"
required
ng-class="{'is-invalid': conference.startDate.$invalid}"
ng-model="options.startDate"
/>
<small class="form-text text-muted"
>The beginning date of the reviewing process.</small
>
<div
class="invalid-feedback"
ng-show="conference.startDate.$error.required"
>
The start date of the conference is required.
</div>
<div
class="invalid-feedback"
ng-show="conference.startDate.$error.invalid"
>
The start date of the conference is invalid. The start date should
always be smaller than the end date.
</div>
</div>
<!-- endDate -->
<div class="form-group">
<label for="endDate">End date of the conference</label>
<input
type="date"
class="form-control"
name="endDate"
id="endDate"
required
ng-class="{'is-invalid': conference.endDate.$invalid}"
ng-model="options.endDate"
/>
<small class="form-text text-muted"
>The end date of the reviewing process. All the repositories will expire
and they will not be accessible after this date.</small
>
<div
class="invalid-feedback"
ng-show="conference.endDate.$error.required"
>
The end date of the conference is required.
</div>
<div class="invalid-feedback" ng-show="conference.endDate.$error.invalid">
The end date of the conference is invalid. The end date should always be
smaller than today.
</div>
</div>
<div class="paper-settings-body">
<aside class="paper-settings-toc">
<div class="paper-settings-toc-head">Contents</div>
<nav>
<a href="#conf-basics">Basics</a>
<a href="#conf-window">Review window</a>
<a href="#conf-rendering">Rendering defaults</a>
<a href="#conf-features">Features</a>
<a href="#conf-plan">Plan</a>
<a href="#conf-billing" ng-show="plan.pricePerRepo > 0">Billing</a>
</nav>
</aside>
<h4>Anonymization options</h4>
<form class="form needs-validation paper-settings-main" name="conference" novalidate>
<section id="conf-basics" class="paper-settings-section">
<div class="paper-section-eyebrow">Basics</div>
<div class="accordion mb-3" id="options">
<div class="card">
<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"
>
Rendering options
</button>
</h2>
<div class="form-group">
<label class="paper-field-label" for="name">Conference name</label>
<input
type="text"
class="form-control"
name="name"
id="name"
required
ng-class="{'is-invalid': conference.name.$invalid}"
ng-model="options.name"
placeholder="e.g. International Conference on Software Engineering"
/>
<div class="invalid-feedback" ng-show="conference.name.$error.invalid">
The name of the conference is invalid.
</div>
<div class="invalid-feedback" ng-show="conference.name.$error.required">
The name of the conference is required.
</div>
</div>
<div
id="collapseOne"
class="collapse show"
aria-labelledby="headingOne"
data-parent="#options"
>
<div class="card-body">
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="link"
name="link"
ng-model="options.options.link"
/>
<label class="form-check-label" for="link">Keep links</label>
<small id="linkHelp" class="form-text text-muted"
>Keep or remove all links from text files.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="image"
name="image"
ng-model="options.options.image"
/>
<label class="form-check-label" for="image"
>Display images</label
>
<small id="imageHelp" class="form-text text-muted"
>Display or hide images from the repositories and text
files.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="pdf"
name="pdf"
ng-model="options.options.pdf"
/>
<label class="form-check-label" for="pdf">Display PDFs</label>
<small id="pdfHelp" class="form-text text-muted"
>Display or hide PDF from the repositories.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="notebook"
name="notebook"
ng-model="options.options.notebook"
/>
<label class="form-check-label" for="notebook"
>Display Notebooks</label
>
<small id="notebookHelp" class="form-text text-muted"
>Display or hide Notebooks from the repositories.</small
>
</div>
<div class="form-group">
<label class="paper-field-label" for="url">Website</label>
<input
type="url"
class="form-control"
name="url"
id="url"
ng-class="{'is-invalid': conference.url.$invalid}"
ng-model="options.url"
placeholder="https://example.org"
/>
<div class="invalid-feedback" ng-show="conference.url.$error.invalid">
The url of the conference is invalid.
</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="conferenceID">Conference ID</label>
<input
type="text"
class="form-control"
name="conferenceID"
id="conferenceID"
required
pattern="[a-zA-Z0-9\-_]{3,10}"
ng-class="{'is-invalid': conference.conferenceID.$invalid}"
ng-model="options.conferenceID"
placeholder="ICSE26"
/>
<small class="form-text text-muted">3&ndash;10 characters, letters, numbers, <code>-</code> and <code>_</code>. Authors reference this when they anonymize.</small>
<div class="invalid-feedback" ng-show="conference.conferenceID.$error.used">
The conference ID '{{options.conferenceID}}' is already used.
</div>
<div class="invalid-feedback" ng-show="conference.conferenceID.$error.required">
The conference ID is required.
</div>
<div class="invalid-feedback" ng-show="conference.conferenceID.$error.pattern">
The format of the conference ID is incorrect ([a-zA-Z0-9-_]{3,10}).
</div>
</div>
</section>
<section id="conf-window" class="paper-settings-section">
<div class="paper-section-eyebrow">Review window</div>
<div class="form-grid-2">
<div class="form-group">
<label class="paper-field-label" for="startDate">Start date</label>
<input
type="date"
class="form-control"
name="startDate"
id="startDate"
required
ng-class="{'is-invalid': conference.startDate.$invalid}"
ng-model="options.startDate"
/>
<small class="form-text text-muted">Beginning of the review process.</small>
<div class="invalid-feedback" ng-show="conference.startDate.$error.required">
Start date is required.
</div>
<div class="invalid-feedback" ng-show="conference.startDate.$error.invalid">
Start date must be before end date.
</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="endDate">End date</label>
<input
type="date"
class="form-control"
name="endDate"
id="endDate"
required
ng-class="{'is-invalid': conference.endDate.$invalid}"
ng-model="options.endDate"
/>
<small class="form-text text-muted">All repositories expire on this date.</small>
<div class="invalid-feedback" ng-show="conference.endDate.$error.required">
End date is required.
</div>
<div class="invalid-feedback" ng-show="conference.endDate.$error.invalid">
End date is invalid.
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingTwo">
<h2 class="mb-0">
<button
class="btn btn-block text-left collapsed"
type="button"
data-toggle="collapse"
data-target="#collapseTwo"
aria-expanded="false"
aria-controls="collapseTwo"
>
Features
</button>
</h2>
</div>
<div
id="collapseTwo"
class="collapse"
aria-labelledby="headingTwo"
data-parent="#options"
>
<div class="card-body">
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="update"
name="update"
ng-model="options.options.update"
/>
<label class="form-check-label" for="update">Auto update</label>
<small id="termsHelp" class="form-text text-muted"
>Automatically update the anonymized repository with the
latest commit of the repository. The repository is updated
once per hour maximum.</small
>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="page"
name="page"
ng-model="options.options.page"
/>
<label class="form-check-label" for="page">Github page</label>
<small id="termsHelp" class="form-text text-muted"
>Enable anonymized Github pages.</small
>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<h4>Plan</h4>
<small class="text-muted">The repositories are bill per hour.</small>
<div class="card-deck mb-3 text-center">
<div class="card mb-4 shadow-sm" ng-repeat="plan in plans">
<div class="card-header">
<h4 class="my-0 font-weight-normal" ng-bind="plan.name"></h4>
<section id="conf-rendering" class="paper-settings-section">
<div class="paper-section-eyebrow">Rendering defaults</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="link" name="link" ng-model="options.options.link" />
<label class="form-check-label" for="link">Keep links</label>
<small class="form-text text-muted">Keep or remove all links from text files.</small>
</div>
<div class="card-body">
<h1 class="card-title pricing-card-title">
{{plan.pricePerRepo | number}}€
<small class="text-muted"> / repository / month</small>
</h1>
<ul
class="list-unstyled mt-3 mb-4"
ng-bind-html="plan.description"
></ul>
<button
type="button"
class="btn btn-lg btn-block"
ng-class="{'btn-primary': options.plan.planID == plan.id}"
ng-click="options.plan.planID = plan.id"
<div class="form-check">
<input class="form-check-input" type="checkbox" id="image" name="image" ng-model="options.options.image" />
<label class="form-check-label" for="image">Display images</label>
<small class="form-text text-muted">Images are not anonymized.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="pdf" name="pdf" ng-model="options.options.pdf" />
<label class="form-check-label" for="pdf">Display PDFs</label>
<small class="form-text text-muted">PDFs are not anonymized.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notebook" name="notebook" ng-model="options.options.notebook" />
<label class="form-check-label" for="notebook">Display Notebooks</label>
</div>
</section>
<section id="conf-features" class="paper-settings-section">
<div class="paper-section-eyebrow">Features</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="update" name="update" ng-model="options.options.update" />
<label class="form-check-label" for="update">Auto update</label>
<small class="form-text text-muted">Pull the latest commit automatically (hourly maximum).</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="page" name="page" ng-model="options.options.page" />
<label class="form-check-label" for="page">GitHub pages</label>
<small class="form-text text-muted">Enable anonymized GitHub pages.</small>
</div>
</section>
<section id="conf-plan" class="paper-settings-section">
<div class="paper-section-eyebrow">Plan</div>
<p class="paper-settings-copy">Repositories are billed per hour.</p>
<div class="paper-plan-grid">
<label
class="paper-plan-card"
ng-class="{'selected': options.plan.planID == plan.id}"
ng-repeat="plan in plans"
>
Select
</button>
<input
type="radio"
name="planPicker"
ng-value="plan.id"
ng-model="options.plan.planID"
/>
<div class="paper-plan-head" ng-bind="plan.name"></div>
<div class="paper-plan-price">
{{plan.pricePerRepo | number}}&euro;
<span class="paper-plan-per">/ repo / month</span>
</div>
<div class="paper-plan-desc" ng-bind-html="plan.description"></div>
</label>
</div>
</div>
</div>
<div ng-if="plan.pricePerRepo > 0">
<h3>Billing</h3>
</section>
<!-- name -->
<div class="form-group">
<label for="billing_name">The name & last name</label>
<input
type="text"
class="form-control"
name="billing_name"
id="billing_name"
required
ng-class="{'is-invalid': conference.billing_name.$invalid}"
ng-model="options.billing.name"
placeholder="Name & last name"
/>
</div>
<section id="conf-billing" class="paper-settings-section" ng-if="plan.pricePerRepo > 0">
<div class="paper-section-eyebrow">Billing</div>
<!-- email -->
<div class="form-group">
<label for="email">Email</label>
<input
type="email"
class="form-control"
name="email"
id="email"
required
ng-class="{'is-invalid': conference.email.$invalid}"
ng-model="options.billing.email"
placeholder="Email"
/>
</div>
<!-- address -->
<div class="form-group">
<label for="inputAddress">Address</label>
<input
type="text"
class="form-control"
name="inputAddress"
id="inputAddress"
required
placeholder="1234 Main St"
ng-model="options.billing.address"
ng-class="{'is-invalid': conference.inputAddress.$invalid}"
/>
</div>
<div class="form-group">
<input
type="text"
class="form-control"
name="inputAddress2"
id="inputAddress2"
ng-model="options.billing.address2"
ng-class="{'is-invalid': conference.inputAddress2.$invalid}"
placeholder="Apartment, studio, or floor"
/>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
type="text"
class="form-control"
name="city"
id="city"
ng-model="options.billing.city"
required
ng-class="{'is-invalid': conference.city.$invalid}"
placeholder="City"
/>
<div class="form-group">
<label class="paper-field-label" for="billing_name">Name</label>
<input type="text" class="form-control" name="billing_name" id="billing_name" required
ng-class="{'is-invalid': conference.billing_name.$invalid}"
ng-model="options.billing.name" placeholder="First &amp; last name" />
</div>
<div class="form-group col-md-4">
<input
type="text"
class="form-control"
name="country"
id="country"
ng-model="options.billing.country"
required
ng-class="{'is-invalid': conference.country.$invalid}"
placeholder="Country"
/>
<div class="form-group">
<label class="paper-field-label" for="email">Email</label>
<input type="email" class="form-control" name="email" id="email" required
ng-class="{'is-invalid': conference.email.$invalid}"
ng-model="options.billing.email" placeholder="you@example.org" />
</div>
<div class="form-group col-md-2">
<input
type="text"
class="form-control"
name="zip"
id="zip"
ng-model="options.billing.zip"
required
ng-class="{'is-invalid': conference.zip.$invalid}"
placeholder="Zip"
/>
<div class="form-group">
<label class="paper-field-label" for="inputAddress">Address</label>
<input type="text" class="form-control" name="inputAddress" id="inputAddress" required
placeholder="1234 Main St"
ng-model="options.billing.address"
ng-class="{'is-invalid': conference.inputAddress.$invalid}" />
</div>
<div class="form-group">
<input type="text" class="form-control" name="inputAddress2" id="inputAddress2"
ng-model="options.billing.address2"
ng-class="{'is-invalid': conference.inputAddress2.$invalid}"
placeholder="Apartment, studio, or floor" />
</div>
<div class="form-grid-3">
<div class="form-group">
<label class="paper-field-label" for="city">City</label>
<input type="text" class="form-control" name="city" id="city"
ng-model="options.billing.city" required
ng-class="{'is-invalid': conference.city.$invalid}" placeholder="City" />
</div>
<div class="form-group">
<label class="paper-field-label" for="country">Country</label>
<input type="text" class="form-control" name="country" id="country"
ng-model="options.billing.country" required
ng-class="{'is-invalid': conference.country.$invalid}" placeholder="Country" />
</div>
<div class="form-group">
<label class="paper-field-label" for="zip">Zip</label>
<input type="text" class="form-control" name="zip" id="zip"
ng-model="options.billing.zip" required
ng-class="{'is-invalid': conference.zip.$invalid}" placeholder="Zip" />
</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="vat">VAT number</label>
<input type="text" class="form-control" name="vat" id="vat"
ng-class="{'is-invalid': conference.vat.$invalid}"
ng-model="options.billing.vat" placeholder="VAT Number" />
</div>
</section>
<div class="paper-settings-footer">
<div class="alert alert-danger" role="alert" ng-if="error" ng-bind="error"></div>
<div class="alert alert-success" role="alert" ng-if="message" ng-bind="message"></div>
<button id="send" type="submit" class="btn btn-ink" ng-click="submit($event)">
{{editionMode ? "Update" : "Create"}} conference
</button>
</div>
<!-- VAT -->
<div class="form-group">
<label for="vat">VAT Number</label>
<input
type="text"
class="form-control"
name="vat"
id="vat"
ng-class="{'is-invalid': conference.vat.$invalid}"
ng-model="options.billing.vat"
placeholder="VAT Number"
/>
</div>
</div>
<div
class="alert alert-danger"
role="alert"
ng-if="error"
ng-bind="error"
></div>
<div
class="alert alert-success"
role="alert"
ng-if="message"
ng-bind="message"
></div>
<button
id="send"
type="submit"
class="btn btn-primary"
ng-click="submit($event)"
>
{{editionMode ? "Update" : "Create"}}
</button>
</form>
</form>
</div>
</div>
+5 -5
View File
@@ -8,8 +8,8 @@
</div>
<div ng-if="type == 'audio'"><audio controls="controls"><source src="{{url}}" /></audio></div>
<div ng-if="type == 'IPython'"><notebook file="url"></notebook></div>
<div ng-if="type == 'error'" class="file-error container d-flex h-100"><h1 class="display-1 m-auto" translate="ERRORS.{{content}}">Error</h1></div></div>
<div ng-if="type == 'loading' && !error" class="file-error container d-flex h-100"><h1 class="display-1 m-auto">Loading...</h1></div></div>
<div ng-if="type == 'empty'" class="file-error container d-flex h-100"><h1 class="display-1 m-auto">Empty repository!</h1></div>
<div ng-if="content == null && type != 'empty'" class="file-error container d-flex h-100"><h1 class="display-1 m-auto">Empty file!</h1></div>
<div ng-if="type == 'binary'" class="file-error container d-flex h-100"><h1 class="display-1 m-auto">Unsupported binary file. You can download the file: <a target="_blank" ng-href="{{url}}&download=true">here</a>.</h1></div>
<div ng-if="type == 'error'" class="file-error container d-flex h-100"><h1 class="paper-empty-title m-auto" translate="ERRORS.{{content}}">Error</h1></div>
<div ng-if="type == 'loading' && !error" class="file-error container d-flex h-100"><h1 class="paper-empty-title m-auto">Loading&hellip;</h1></div>
<div ng-if="type == 'empty'" class="file-error container d-flex h-100"><h1 class="paper-empty-title m-auto">Empty <em>repository</em>.</h1></div>
<div ng-if="content == null && type != 'empty'" class="file-error container d-flex h-100"><h1 class="paper-empty-title m-auto">Empty <em>file</em>.</h1></div>
<div ng-if="type == 'binary'" class="file-error container d-flex h-100"><h1 class="paper-empty-title m-auto">Unsupported <em>binary file</em>. You can <a target="_blank" ng-href="{{url}}&download=true">download it</a>.</h1></div>
+13 -10
View File
@@ -1,22 +1,25 @@
<div class="container page dashboard-page">
<div class="container page dashboard-page paper-page">
<div class="row">
<div class="w-100 py-3">
<!-- Header row: title + action buttons -->
<div class="d-flex align-items-center justify-content-between mb-3">
<h2 class="dashboard-title mb-0">Pull Requests</h2>
<div class="w-100">
<div class="paper-crumbs">My work &nbsp;/&nbsp; <span class="here">Pull requests</span></div>
<div class="d-flex align-items-end justify-content-between flex-wrap" style="gap: 12px;">
<div>
<h1 class="paper-page-title">Anonymized <em>pull requests</em></h1>
<p class="paper-page-lede">Track PR mirrors you&rsquo;ve created and their reviewer traffic.</p>
</div>
<div class="d-flex flex-wrap" style="gap: 6px">
<a href="/anonymize" class="btn btn-primary btn-sm">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Anonymize Repo
<a href="/anonymize" class="btn btn-ink">
<i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> Anonymize Repo
</a>
<a href="/pull-request-anonymize" class="btn btn-primary btn-sm">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Anonymize PR
<a href="/pull-request-anonymize" class="btn btn-ink">
<i class="fa fa-plus-circle mr-1" aria-hidden="true"></i> Anonymize PR
</a>
<a
title="Claim the ownership of an existing anonymized repository."
data-toggle="tooltip"
data-placement="bottom"
href="/claim"
class="btn btn-sm"
class="btn"
>
Claim
</a>
+161 -301
View File
@@ -1,313 +1,173 @@
<div class="container py-4">
<h2>Quota</h2>
<h3>Quota</h3>
<h5>Repository</h5>
<div class="progress">
<div
class="progress-bar"
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.repository.percent < 25 || quota.repository.total == 0, 'bg-danger': quota.repository.percent > 95 && quota.repository.total > 0, 'bg-warning': quota.repository.percent > 75 && quota.repository.total > 0 }"
role="progressbar"
style="width: {{quota.repository.percent}}%;"
aria-valuenow="{{quota.repository.used}}"
aria-valuemin="0"
aria-valuemax="{{quota.repository.total}}"
>
<span ng-show="quota"
>{{quota.repository.used | number}}/{{quota.repository.total}}</span
>
</div>
</div>
<h5>Storage</h5>
<div class="progress">
<div
class="progress-bar"
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.storage.percent < 25 || quota.storage.total == 0, 'bg-danger': quota.storage.percent > 95 && quota.storage.total > 0, 'bg-warning': quota.storage.percent > 75 && quota.storage.total > 0 }"
role="progressbar"
style="width: {{quota.storage.percent}}%;"
aria-valuenow="{{quota.storage.used}}"
aria-valuemin="0"
aria-valuemax="{{quota.storage.total}}"
>
<span ng-show="quota"
>{{quota.storage.used | humanFileSize}}/{{quota.storage.total|
humanFileSize}}</span
>
</div>
</div>
<h5>File</h5>
<div class="progress">
<div
class="progress-bar"
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.file.percent < 25 || quota.file.total == 0, 'bg-danger': quota.file.percent > 95 && quota.file.total > 0, 'bg-warning': quota.file.percent > 75 && quota.file.total > 0 }"
role="progressbar"
style="width: {{quota.file.percent}}%;"
aria-valuenow="{{quota.file.used}}"
aria-valuemin="0"
aria-valuemax="{{quota.file.total}}"
>
<span ng-show="quota"
>{{quota.file.used | number}}/{{quota.file.total || "∞"}}</span
>
</div>
</div>
<div class="container paper-page paper-settings">
<div class="paper-crumbs">Reference &nbsp;/&nbsp; <span class="here">Settings</span></div>
<h1 class="paper-page-title">Your <em>settings</em></h1>
<p class="paper-page-lede">Review quotas and set defaults applied to every new anonymization.</p>
<h2>Default anonymization options</h2>
<form class="form needs-validation" name="default" novalidate>
<!-- Terms -->
<div class="form-group">
<label for="terms">Terms to anonymize</label>
<textarea
class="form-control"
id="terms"
name="terms"
rows="3"
ng-model="terms"
ng-model-options="{ debounce: 250 }"
></textarea>
<small id="termsHelp" class="form-text text-muted"
>One term per line. Each term will be replaced by XXX</small
>
<div class="invalid-feedback" ng-show="anonymize.terms.$error.format">
Terms are in an invalid format
</div>
</div>
<div class="accordion mb-3" id="options">
<div class="card">
<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"
>
Rendering options
</button>
</h2>
</div>
<div class="paper-settings-body">
<aside class="paper-settings-toc">
<div class="paper-settings-toc-head">Contents</div>
<nav>
<a href="#settings-quota">Quota</a>
<a href="#settings-terms">Terms</a>
<a href="#settings-rendering">Rendering</a>
<a href="#settings-features">Features</a>
<a href="#settings-expiration">Expiration</a>
</nav>
</aside>
<div
id="collapseOne"
class="collapse show"
aria-labelledby="headingOne"
data-parent="#options"
>
<div class="card-body">
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="link"
name="link"
ng-model="options.link"
/>
<label class="form-check-label" for="link">Keep links</label>
<small id="termsHelp" class="form-text text-muted"
>Keep or remove all the links.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="image"
name="image"
ng-model="options.image"
/>
<label class="form-check-label" for="image"
>Display images</label
>
<small id="termsHelp" class="form-text text-muted"
>Images are not anonymized</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="pdf"
name="pdf"
ng-model="options.pdf"
/>
<label class="form-check-label" for="pdf">Display PDFs</label>
<small id="termsHelp" class="form-text text-muted"
>PDF are not anonymized</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="notebook"
name="notebook"
ng-model="options.notebook"
/>
<label class="form-check-label" for="notebook"
>Display Notebooks</label
>
</div>
<div class="paper-settings-main">
<section id="settings-quota" class="paper-settings-section">
<div class="paper-section-eyebrow">Quota</div>
<div class="quota-row">
<div class="quota-item">
<div class="quota-header">
<span class="quota-label">Repositories</span>
<span class="quota-value" ng-show="quota">{{quota.repository.used | number}}/{{quota.repository.total}}</span>
</div>
<div class="progress quota-progress">
<div
class="progress-bar"
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.repository.percent < 25 || quota.repository.total == 0, 'bg-danger': quota.repository.percent > 95 && quota.repository.total > 0, 'bg-warning': quota.repository.percent > 75 && quota.repository.total > 0 }"
role="progressbar"
style="width: {{quota.repository.percent}}%;"
aria-valuenow="{{quota.repository.used}}"
aria-valuemin="0"
aria-valuemax="{{quota.repository.total}}"
></div>
</div>
</div>
<div class="quota-item">
<div class="quota-header">
<span class="quota-label">Storage</span>
<span class="quota-value" ng-show="quota">{{quota.storage.used | humanFileSize}}/{{quota.storage.total | humanFileSize}}</span>
</div>
<div class="progress quota-progress">
<div
class="progress-bar"
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.storage.percent < 25 || quota.storage.total == 0, 'bg-danger': quota.storage.percent > 95 && quota.storage.total > 0, 'bg-warning': quota.storage.percent > 75 && quota.storage.total > 0 }"
role="progressbar"
style="width: {{quota.storage.percent}}%;"
aria-valuenow="{{quota.storage.used}}"
aria-valuemin="0"
aria-valuemax="{{quota.storage.total}}"
></div>
</div>
</div>
<div class="quota-item">
<div class="quota-header">
<span class="quota-label">Files</span>
<span class="quota-value" ng-show="quota">{{quota.file.used | number}}/{{quota.file.total || "&#8734;"}}</span>
</div>
<div class="progress quota-progress">
<div
class="progress-bar"
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.file.percent < 25 || quota.file.total == 0, 'bg-danger': quota.file.percent > 95 && quota.file.total > 0, 'bg-warning': quota.file.percent > 75 && quota.file.total > 0 }"
role="progressbar"
style="width: {{quota.file.percent}}%;"
aria-valuenow="{{quota.file.used}}"
aria-valuemin="0"
aria-valuemax="{{quota.file.total}}"
></div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingTwo">
<h2 class="mb-0">
<button
class="btn btn-block text-left collapsed"
type="button"
data-toggle="collapse"
data-target="#collapseTwo"
aria-expanded="false"
aria-controls="collapseTwo"
>
Features
</button>
</h2>
</div>
<div
id="collapseTwo"
class="collapse"
aria-labelledby="headingTwo"
data-parent="#options"
>
<div class="card-body">
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="page"
name="page"
ng-model="options.page"
/>
<label class="form-check-label" for="page">Github page</label>
<small id="termsHelp" class="form-text text-muted"
>Enable anonymized Github pages. It currently only supported
for Github pages that are defined in the same branch.</small
>
</div>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="loc"
name="loc"
ng-model="options.loc"
/>
<label class="form-check-label" for="page">Line of code</label>
<small id="termsHelp" class="form-text text-muted"
>Display the number of line of code in the repository</small
>
</div>
<div class="form-group">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="update"
name="update"
ng-model="options.update"
/>
<label class="form-check-label" for="update"
>Auto update</label
>
<small id="termsHelp" class="form-text text-muted"
>Automatically update the anonymized repository with the
latest commit of the repository. The repository is updated
once per hour maximum.</small
>
</div>
</div>
<div class="form-group">
<label for="mode">Proxy mode</label>
<select
class="form-control"
id="mode"
name="mode"
ng-model="options.mode"
>
<option value="GitHubStream" selected>Stream</option>
<option value="GitHubDownload">Download</option>
</select>
<small class="form-text text-muted"
>How the repository will be anonymized. Stream mode
will request the content on the flight. This is the
only option for repositories bigger than
{{site_options.MAX_REPO_SIZE * 1024| humanFileSize}}.
Download will download the repository the repository
on the anonymous.4open.science server, it is faster
and offer more features.</small
>
</div>
</section>
<form class="form needs-validation" name="default" novalidate>
<section id="settings-terms" class="paper-settings-section">
<div class="paper-section-eyebrow">Default anonymization options</div>
<div class="form-group">
<label class="paper-field-label" for="terms">Terms to anonymize</label>
<textarea
class="form-control"
id="terms"
name="terms"
rows="4"
ng-model="terms"
ng-model-options="{ debounce: 250 }"
></textarea>
<small id="termsHelp" class="form-text text-muted">One term per line. Each term will be replaced by XXX.</small>
<div class="invalid-feedback" ng-show="anonymize.terms.$error.format">
Terms are in an invalid format
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="headingThree">
<h2 class="mb-0">
<button
class="btn btn-block text-left collapsed"
type="button"
data-toggle="collapse"
data-target="#collapseThree"
aria-expanded="false"
aria-controls="collapseThree"
>
Expiration
</button>
</h2>
</div>
<div
id="collapseThree"
class="collapse"
aria-labelledby="headingThree"
data-parent="#options"
>
<div class="card-body">
<div class="form-group">
<label for="expiration">Expiration options</label>
<select
class="form-control"
id="expiration"
name="expiration"
ng-model="options.expirationMode"
>
<option value="never" selected>Never expire</option>
<option value="redirect">Redirect to GitHub</option>
<option value="remove">Remove anonymized repository</option>
</select>
<small class="form-text text-muted"
>Define the expiration strategy for the repository.</small
>
</section>
<section id="settings-rendering" class="paper-settings-section">
<div class="paper-section-eyebrow">Rendering</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="link" name="link" ng-model="options.link" />
<label class="form-check-label" for="link">Keep links</label>
<small class="form-text text-muted">Keep or remove all the links.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="image" name="image" ng-model="options.image" />
<label class="form-check-label" for="image">Display images</label>
<small class="form-text text-muted">Images are not anonymized.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="pdf" name="pdf" ng-model="options.pdf" />
<label class="form-check-label" for="pdf">Display PDFs</label>
<small class="form-text text-muted">PDFs are not anonymized.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notebook" name="notebook" ng-model="options.notebook" />
<label class="form-check-label" for="notebook">Display Notebooks</label>
</div>
</div>
</section>
<section id="settings-features" class="paper-settings-section">
<div class="paper-section-eyebrow">Features</div>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="page" name="page" ng-model="options.page" />
<label class="form-check-label" for="page">Github page</label>
<small class="form-text text-muted">Enable anonymized GitHub pages. Currently only supported for pages defined in the same branch.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="loc" name="loc" ng-model="options.loc" />
<label class="form-check-label" for="loc">Line of code</label>
<small class="form-text text-muted">Display the number of lines of code in the repository.</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="update" name="update" ng-model="options.update" />
<label class="form-check-label" for="update">Auto update</label>
<small class="form-text text-muted">Automatically update the anonymized repository with the latest commit (once per hour maximum).</small>
</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="mode">Proxy mode</label>
<select class="form-control" id="mode" name="mode" ng-model="options.mode">
<option value="GitHubStream" selected>Stream</option>
<option value="GitHubDownload">Download</option>
</select>
<small class="form-text text-muted">Stream mode requests content on the fly. This is the only option for repositories bigger than {{site_options.MAX_REPO_SIZE * 1024| humanFileSize}}. Download fetches to the server — faster and with more features.</small>
</div>
</section>
<section id="settings-expiration" class="paper-settings-section">
<div class="paper-section-eyebrow">Expiration</div>
<div class="form-group">
<label class="paper-field-label" for="expiration">Expiration strategy</label>
<select class="form-control" id="expiration" name="expiration" ng-model="options.expirationMode">
<option value="never" selected>Never expire</option>
<option value="redirect">Redirect to GitHub</option>
<option value="remove">Remove anonymized repository</option>
</select>
<small class="form-text text-muted">Define the expiration strategy for new repositories.</small>
</div>
</section>
<div class="paper-settings-footer">
<div class="alert alert-danger" role="alert" ng-if="error" ng-bind="error"></div>
<div class="alert alert-success" role="alert" ng-if="message" ng-bind="message"></div>
<button id="save" type="submit" class="btn btn-ink" ng-click="saveDefault($event)">
Save defaults
</button>
</div>
</div>
</form>
</div>
<div
class="alert alert-danger"
role="alert"
ng-if="error"
ng-bind="error"
></div>
<div
class="alert alert-success"
role="alert"
ng-if="message"
ng-bind="message"
></div>
<button
id="save"
type="submit"
class="btn btn-primary"
ng-click="saveDefault($event)"
>
Save
</button>
</form>
</div>
</div>
+10 -15
View File
@@ -6,25 +6,20 @@
Anonymization Date: {{details.anonymizeDate|date}}
</div>
</div>
<div class="overflow-auto mx-3">
<div class="d-flex w-100 justify-content-between align-items-center">
<h2 class="pr-title">
<div class="overflow-auto paper-page" style="padding-top: 18px;">
<div class="paper-crumbs">Reviewer &nbsp;/&nbsp; <span class="here">Pull request</span></div>
<div class="d-flex w-100 justify-content-between align-items-end flex-wrap" style="gap: 12px;">
<h1 class="paper-page-title pr-title" style="margin: 6px 0;">
<span ng-if="details.title">{{details.title}}</span>
<span
class="badge"
ng-class="{'badge-success':details.merged, 'badge-warning':details.state=='open', 'badge-danger':details.state=='closed' &&!details.merged}"
>
<span class="paper-pill" ng-class="{'good':details.merged, 'warn':details.state=='open', 'bad':details.state=='closed' && !details.merged}">
{{details.merged?"merged":details.state | title}}
</span>
</h2>
<small
ng-if="details.updatedDate"
ng-bind="details.updatedDate | date"
></small>
</h1>
<small class="paper-pill" ng-if="details.updatedDate" ng-bind="details.updatedDate | date"></small>
</div>
<div class="paper-meta-rule" ng-if="details.baseRepositoryFullName">
<span>on <b>{{details.baseRepositoryFullName}}</b></span>
</div>
<small ng-if="details.baseRepositoryFullName"
>Pull Request on {{details.baseRepositoryFullName}}</small
>
<div
class="pr-body shadow-sm p-3 mb-4 rounded border"
ng-if="details.body"
+8 -5
View File
@@ -1,8 +1,11 @@
<div class="container py-4">
<h2>Status of <strong ng-bind="repoId"></strong></h2>
<div class="container paper-page">
<div class="paper-crumbs">Anonymization &nbsp;/&nbsp; <span class="here">Status</span></div>
<h1 class="paper-page-title">Status of <em>{{repoId}}</em></h1>
<p class="paper-page-lede">Track progress as your anonymization is prepared.</p>
<div class="paper-meta-rule"></div>
<section class="py-4">
<h2>Progress</h2>
<div class="paper-section-eyebrow">Progress</div>
<p>
The current status of your repository. The repository will take few
@@ -39,7 +42,7 @@
<p class="text-center">
<a
class="btn btn-primary"
class="btn btn-ink"
href="/r/{{repoId}}/"
target="__self"
ng-if="repo.status == 'ready'"
@@ -56,7 +59,7 @@
</section>
<section class="py-4">
<h2 class="md-1">Support Anonymous GitHub</h2>
<div class="paper-section-eyebrow">Support Anonymous GitHub</div>
<iframe
id="kofiframe"
+29 -20
View File
@@ -726,10 +726,19 @@ angular
}
getQuota();
let loadedRepos = null;
let loadedPRs = null;
function mergeItems() {
$scope.items = (loadedRepos || []).concat(loadedPRs || []);
}
function loadAll() {
loadedRepos = null;
loadedPRs = null;
$http.get("/api/user/anonymized_repositories").then(
(res) => {
const repos = res.data.map((repo) => {
loadedRepos = res.data.map((repo) => {
if (!repo.pageView) repo.pageView = 0;
if (!repo.lastView) repo.lastView = "";
repo.options.terms = repo.options.terms.filter((f) => f);
@@ -741,25 +750,25 @@ angular
repo._viewUrl = "/r/" + repo.repoId + "/";
return repo;
});
$http.get("/api/user/anonymized_pull_requests").then(
(res2) => {
const prs = res2.data.map((pr) => {
if (!pr.pageView) pr.pageView = 0;
if (!pr.lastView) pr.lastView = "";
pr.options.terms = pr.options.terms.filter((f) => f);
pr._type = "pr";
pr._id = pr.pullRequestId;
pr._name = pr.pullRequestId;
pr._source = pr.source.repositoryFullName + "#" + pr.source.pullRequestId;
pr._editUrl = "/pull-request-anonymize/" + pr.pullRequestId;
pr._viewUrl = "/pr/" + pr.pullRequestId + "/";
pr.anonymizeDate = pr.anonymizeDate;
return pr;
});
$scope.items = repos.concat(prs);
},
(err) => { console.error(err); }
);
mergeItems();
},
(err) => { console.error(err); }
);
$http.get("/api/user/anonymized_pull_requests").then(
(res2) => {
loadedPRs = res2.data.map((pr) => {
if (!pr.pageView) pr.pageView = 0;
if (!pr.lastView) pr.lastView = "";
pr.options.terms = pr.options.terms.filter((f) => f);
pr._type = "pr";
pr._id = pr.pullRequestId;
pr._name = pr.pullRequestId;
pr._source = pr.source.repositoryFullName + "#" + pr.source.pullRequestId;
pr._editUrl = "/pull-request-anonymize/" + pr.pullRequestId;
pr._viewUrl = "/pr/" + pr.pullRequestId + "/";
return pr;
});
mergeItems();
},
(err) => { console.error(err); }
);
+1 -1
View File
File diff suppressed because one or more lines are too long
+51 -8
View File
@@ -4,6 +4,8 @@ import { ensureAuthenticated } from "./connection";
import { handleError, getUser, isOwnerOrAdmin } from "./route-utils";
import UserModel from "../../core/model/users/users.model";
import User from "../../core/User";
import FileModel from "../../core/model/files/files.model";
import { isConnected } from "../database";
const router = express.Router();
@@ -40,22 +42,63 @@ router.get("/quota", async (req: express.Request, res: express.Response) => {
try {
const user = await getUser(req);
const repositories = await user.getRepositories();
const sizes = await Promise.all(
repositories
.filter((r) => r.status == "ready")
.map((r) => r.computeSize())
);
const ready = repositories.filter((r) => r.status == "ready");
let totalStorage = 0;
let totalFiles = 0;
const uncachedIds: string[] = [];
for (const r of ready) {
const cached = r.model.size;
if (cached && cached.file) {
totalStorage += cached.storage;
totalFiles += cached.file;
} else {
uncachedIds.push(r.repoId);
}
}
if (uncachedIds.length) {
const agg = await FileModel.aggregate([
{ $match: { repoId: { $in: uncachedIds } } },
{
$group: {
_id: "$repoId",
storage: { $sum: "$size" },
file: { $sum: 1 },
},
},
]);
const byId = new Map<string, { storage: number; file: number }>();
for (const row of agg) {
byId.set(row._id, { storage: row.storage || 0, file: row.file || 0 });
}
for (const r of ready) {
if (!uncachedIds.includes(r.repoId)) continue;
const size = byId.get(r.repoId) || { storage: 0, file: 0 };
totalStorage += size.storage;
totalFiles += size.file;
r.model.size = size;
}
if (isConnected) {
await Promise.all(
ready
.filter((r) => uncachedIds.includes(r.repoId))
.map((r) => r.model.save())
);
}
}
res.json({
storage: {
used: sizes.reduce((sum, i) => sum + i.storage, 0),
used: totalStorage,
total: config.DEFAULT_QUOTA,
},
file: {
used: sizes.reduce((sum, i) => sum + i.file, 0),
used: totalFiles,
total: 0,
},
repository: {
used: repositories.filter((f) => f.status == "ready").length,
used: ready.length,
total: 20,
},
});