mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 00:07:00 +01:00
105 lines
15 KiB
HTML
105 lines
15 KiB
HTML
<div class="container paper-page user-settings">
|
|
<header class="settings-title"><h1>Settings</h1><div class="settings-header-actions"><span>@{{ user?.username }}</span></div></header>
|
|
<nav class="settings-nav" aria-label="Settings">
|
|
<button type="button" v-for="tab in [{id:'defaults',label:'Anonymization defaults'},{id:'billing',label:'Hosting & billing'},{id:'account',label:'Account'}]" :aria-current="settingsTab === tab.id ? 'page' : null" @click="settingsTab = tab.id">{{ tab.label }}</button>
|
|
</nav>
|
|
<div v-show="settingsTab === 'defaults'">
|
|
<p class="settings-scope">For new anonymizations only. Existing repositories keep their settings.</p>
|
|
<form class="form needs-validation" name="defaultsForm" novalidate v-form="viewState" @submit.prevent="submitForm($event, () => { saveDefault($event) })">
|
|
<section class="settings-row"><div><label class="settings-label" for="terms">Terms to redact</label><p class="settings-help">One term per line.</p></div><div><textarea class="form-control" id="terms" name="terms" rows="3" placeholder="Jane Smith
|
|
MIT CSAIL
|
|
jane.smith@example.org" v-field="{ state: viewState, set: value => { terms = value }, value: terms, form: "defaultsForm", options: { debounce: 250 } }"></textarea>
|
|
<details class="settings-syntax"><summary>Regex & replacement syntax</summary><p>Matches become <code>{{ site_options?.ANONYMIZATION_MASK || 'XXX' }}-[N]</code>. Use <code>term=>replacement</code> for a custom replacement. Regex is supported.</p></details>
|
|
<div class="invalid-feedback" v-show="defaultsForm?.terms?.errors?.format">Terms are in an invalid format.</div>
|
|
</div></section>
|
|
<section class="settings-row"><h2>Reviewer display</h2><div><div class="settings-checks"><label class="settings-check"><input class="form-check-input" type="checkbox" id="link" name="link" v-field="{ state: viewState, set: value => { options.link = value }, value: options?.link, form: "defaultsForm", options: {} }">Keep links</label><label class="settings-check"><input class="form-check-input" type="checkbox" id="notebook" name="notebook" v-field="{ state: viewState, set: value => { options.notebook = value }, value: options?.notebook, form: "defaultsForm", options: {} }">Render notebooks</label><label class="settings-check"><input class="form-check-input" type="checkbox" id="image" name="image" v-field="{ state: viewState, set: value => { options.image = value }, value: options?.image, form: "defaultsForm", options: {} }">Show images</label><label class="settings-check"><input class="form-check-input" type="checkbox" id="pdf" name="pdf" v-field="{ state: viewState, set: value => { options.pdf = value }, value: options?.pdf, form: "defaultsForm", options: {} }">Show PDFs</label><label class="settings-check"><input class="form-check-input" type="checkbox" id="loc" name="loc" v-field="{ state: viewState, set: value => { options.loc = value }, value: options?.loc, form: "defaultsForm", options: {} }">Show line counts</label><label class="settings-check"><input class="form-check-input" type="checkbox" id="page" name="page" v-field="{ state: viewState, set: value => { options.page = value }, value: options?.page, form: "defaultsForm", options: {} }">GitHub Pages</label></div><p class="settings-help">Images and PDFs are not anonymized.</p><p class="settings-help">GitHub Pages must use the anonymized branch.</p></div></section>
|
|
<section class="settings-row"><h2>Source updates</h2><div><label class="settings-check"><input class="form-check-input" type="checkbox" id="update" name="update" v-field="{ state: viewState, set: value => { options.update = value }, value: options?.update, form: "defaultsForm", options: {} }">Auto-update from GitHub</label><p class="settings-help">Latest commit, at most once an hour.</p></div></section>
|
|
<section class="settings-row"><label class="settings-label" for="expiration">On expiration</label><div><select class="form-control" id="expiration" name="expiration" v-field="{ state: viewState, set: value => { options.expirationMode = value }, value: options?.expirationMode, form: "defaultsForm", options: {} }">
|
|
<option value="redirect">Redirect visitors to the GitHub repository</option>
|
|
<option value="remove" selected>Remove the anonymized repository</option>
|
|
</select><p class="settings-help">{{ options.expirationMode === 'redirect' ? 'Redirecting reveals the original repository. Choose the date per anonymization.' : 'Set the expiration date for each repository.' }}</p></div></section>
|
|
<footer class="settings-save">
|
|
<div class="settings-save-status draft-status" role="status"><span v-if="draftRestored">Your unsaved draft was restored.</span><span v-else-if="unsavedChanges">Unsaved changes{{ draftSaved ? ' · Draft saved in this tab' : '' }}</span><span v-else>{{ message ? 'Defaults saved' : 'All changes saved' }}</span></div>
|
|
<button type="button" class="draft-discard" v-if="draftRestored || unsavedChanges" @click="discardDraft()">Discard changes</button>
|
|
<button id="save" type="submit" class="btn btn-ink" :disabled="saving">{{ saving ? 'Saving…' : 'Save defaults' }}</button>
|
|
<div class="settings-save-error" role="alert" v-if="error">{{ error }}</div>
|
|
</footer>
|
|
</form>
|
|
</div>
|
|
<section id="settings-billing" v-show="settingsTab === 'billing'" :aria-busy="billingLoading || billingBusy">
|
|
<p class="settings-scope">Your plan and usage. Reviewer access is always free.</p>
|
|
<p v-if="billingLoading" role="status">Loading billing…</p>
|
|
<div v-if="billingError" class="settings-notice" role="alert">{{ billingError }} <button type="button" class="btn btn-outline-ink" @click="loadBilling()" :disabled="billingBusy">Try again</button></div>
|
|
<p v-if="billingNotice" class="settings-notice" role="status">{{ billingNotice }}</p>
|
|
<div v-if="billingPending" class="settings-notice" role="status"><strong>Confirming your payment</strong><p>Your allowance will update after confirmation. Please do not pay again.</p><button type="button" class="btn btn-outline-ink" @click="refreshBilling()" :disabled="billingBusy">Check payment status</button></div>
|
|
<section class="settings-row"><h2>Current plan</h2><div>
|
|
<div class="settings-inline"><div><strong>{{ profileAuthorGrant() ? 'Author subscription' : (billing?.enabled ? 'Free' : 'Account allowance') }}</strong><p class="settings-help" v-if="profileAuthorGrant()">{{ profileAuthorGrant().cancelAtPeriodEnd ? 'Renewal cancelled · Access until ' : 'Paid access through ' }}{{ billingDate(profileAuthorGrant().until) }}</p></div>
|
|
<button type="button" class="btn btn-outline-ink" v-if="billing?.enabled && !profileAuthorGrant() && !billingPending && !showAuthorPlans && profileAuthorPlans().length" @click="openProfileUpgrade()">View Author plan</button>
|
|
</div>
|
|
<p class="settings-help" v-if="billing?.allowance">{{ billing.allowance.repositories }} repositories · {{ billingBytes(billing.allowance.storage) }}</p>
|
|
<div id="settings-quota"><quota-usage :quota="quota" :fmt="fmt" v-if="quota"></quota-usage><p v-else role="status">Loading usage…</p></div>
|
|
<p class="settings-help">Coauthored repositories use their owner's allowance. Conference hosting is separate.</p>
|
|
<p class="settings-notice" v-if="quota?.repository && !quota.repository.unlimited && quota.repository.used >= quota.repository.total">Your repository allowance is full. <a href="/dashboard">Review your repositories</a> to remove work you no longer need.</p>
|
|
<p class="settings-notice" v-if="profilePastSubscription()">Your previous subscription is no longer active. Never-expiring repositories receive a 90-day grace period after paid access ends. <a href="/dashboard">Review repository expiration dates</a>.</p>
|
|
<p class="settings-notice" v-if="profileAuthorGrant()?.status === 'past_due'">A subscription payment is overdue. Use Manage billing to update your payment details.</p>
|
|
<p class="settings-help" v-if="profileAuthorGrant()?.cancelAtPeriodEnd">When paid access ends, never-expiring repositories enter a 90-day grace period. Dated repositories keep their expiration dates.</p>
|
|
<div class="settings-plan billing-plan" v-if="showAuthorPlans && !profileAuthorGrant() && !billingPending && profileAuthorPlans().length">
|
|
<h3 id="settings-plan-comparison" tabindex="-1">Compare plans</h3>
|
|
<table class="settings-comparison"><caption class="sr-only">Free and Author hosting</caption><thead><tr><th scope="col">Included</th><th scope="col">Free</th><th scope="col">Author</th></tr></thead><tbody>
|
|
<tr><th scope="row">Active repositories</th><td>{{ billing.allowance?.repositories }}</td><td>{{ profileSelectedPlan()?.repositories }}</td></tr>
|
|
<tr><th scope="row">Source storage</th><td>{{ billingBytes(billing.allowance?.storage) }}</td><td>{{ billingBytes(profileSelectedPlan()?.storage) }}</td></tr>
|
|
<tr><th scope="row">Expiration</th><td>Scheduled</td><td>Scheduled or never</td></tr>
|
|
<tr><th scope="row">Coauthors</th><td>Included</td><td>Included</td></tr>
|
|
<tr><th scope="row">Reviewer access</th><td>Free</td><td>Free</td></tr>
|
|
</tbody></table>
|
|
<fieldset class="settings-cycles"><legend class="sr-only">Billing frequency</legend><label v-for="plan in profileAuthorPlans()" :class="{selected: profileSelectedPlan()?.id === plan.id}"><input type="radio" name="profile-plan" :checked="profileSelectedPlan()?.id === plan.id" @change="profilePlan = plan.id">{{ billingPrice(plan) }}</label></fieldset>
|
|
<p class="settings-help">Renews {{ profileSelectedPlan()?.interval === 'year' ? 'annually' : 'monthly' }} until cancelled. Tax shown at checkout.</p><p class="settings-help">If paid access ends, never-expiring repositories expire after a 90-day grace period.</p>
|
|
<div class="settings-actions"><button type="button" class="btn btn-ink" :disabled="billingBusy || billing.checkoutEnabled === false" @click="checkout(profileSelectedPlan().id)">{{ billingBusy ? 'Please wait…' : 'Continue to checkout' }}</button><button type="button" class="btn btn-outline-ink" @click="showAuthorPlans = false">Keep current plan</button></div>
|
|
</div>
|
|
<p class="settings-help" v-if="billing && (!billing.enabled || billing.checkoutEnabled === false)">New purchases are not available. Existing billing can still be managed below.</p>
|
|
</div></section>
|
|
<section class="settings-row"><h2>Billing</h2><div><div class="settings-inline" v-if="billing?.portal"><p class="settings-help">Invoices, payment details, and renewal.</p><button type="button" class="btn btn-outline-ink" @click="billingPortal()" :disabled="billingBusy">Manage billing</button></div><p class="settings-help" v-else>No billing account available.</p><button type="button" class="settings-text-button" v-if="billing?.enabled && !billingPending" @click="refreshBilling()" :disabled="billingBusy">Refresh payment status</button></div></section>
|
|
<section class="settings-row" v-if="billing?.enabled"><h2>Repository passes</h2><div><div class="settings-inline"><p class="settings-help">One repository for 180 days.<br>No subscription or automatic renewal.</p><button type="button" class="btn btn-outline-ink" v-if="profilePassPlan()" @click="showRepositoryPass = !showRepositoryPass" :disabled="billingPending">Review a pass</button></div>
|
|
<div class="settings-pass" v-if="showRepositoryPass && profilePassPlan() && !billingPending"><label for="billing-repository">Repository ID you own</label><input id="billing-repository" class="form-control" v-model="billingRepository" placeholder="Existing anonymized repository ID" autocomplete="off"><p class="settings-help">{{ billingPrice(profilePassPlan()) }} · {{ billingBytes(profilePassPlan().storage) }} · Never expire is not included.</p><button type="button" class="btn btn-ink" :disabled="billingBusy || !billingRepository.trim() || billing.checkoutEnabled === false" @click="checkout('author_pass', billingRepository.trim())">Continue to checkout</button></div>
|
|
</div></section>
|
|
<section class="settings-row" v-if="billing?.grants?.length"><h2>Your purchases</h2><div><div class="settings-purchase" v-for="grant in billing.grants" :key="grant.scope + grant.planId + grant.until"><strong>{{ profileGrantName(grant) }}</strong><span>{{ grant.scope }}</span><p class="settings-help">{{ grant.status }}<template v-if="grant.status !== 'revoked'"> · Paid access through {{ billingDate(grant.until) }}{{ grant.cancelAtPeriodEnd ? ' · Renewal cancelled' : '' }}</template></p></div></div></section>
|
|
<section class="settings-row"><h2>Conference hosting</h2><div class="settings-inline"><p class="settings-help">Shared hosting is managed per conference.</p><a href="/conferences" class="btn btn-outline-ink">My conferences</a></div></section>
|
|
</section>
|
|
<section id="settings-account" class="settings-account-panel" v-show="settingsTab === 'account'">
|
|
<p class="settings-scope">Your identity, repository access, and account.</p>
|
|
<div class="paper-account-card">
|
|
<img width="40" height="40" class="rounded-circle" alt="" v-if="user?.photo" :src="safeUrl((user?.photo))">
|
|
<div class="paper-account-identity">
|
|
<div class="paper-account-name">@{{ user?.username }}</div>
|
|
<div class="paper-account-meta">Signed in with GitHub. Anonymous GitHub only reads your repositories.</div>
|
|
</div>
|
|
<a class="btn btn-outline-ink" href="/api/user/logout" target="_self">Sign out</a>
|
|
</div>
|
|
|
|
<a class="paper-settings-connection-link" href="/connections">
|
|
<i class="fab fa-github" aria-hidden="true"></i>
|
|
<span>GitHub connections<small>Manage repository access and legacy OAuth.</small></span>
|
|
<span aria-hidden="true">→</span>
|
|
</a>
|
|
|
|
<div class="paper-danger-zone">
|
|
<div class="paper-danger-head">Delete account</div>
|
|
<p class="paper-danger-copy">
|
|
Removes all your anonymized repositories, gists, and pull requests,
|
|
revokes the application's access to your GitHub account, and erases
|
|
your personal data (username, email, access tokens). This cannot be undone.
|
|
</p>
|
|
<div class="paper-inline-alert paper-inline-alert-error" role="alert" v-if="deleteError"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> <span v-text="deleteError"></span></div>
|
|
<button type="button" class="btn btn-outline-danger" @click="confirmDeleteAccount = true" :disabled="deletingAccount">
|
|
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-trash"></use></svg>{{ deletingAccount ? 'Deleting…' : 'Delete my account' }}
|
|
</button>
|
|
<div class="settings-delete-confirm" v-if="confirmDeleteAccount">
|
|
<label for="delete-username">Type {{ user?.username }} to confirm</label>
|
|
<input id="delete-username" class="form-control" v-model="deleteUsername" autocomplete="off">
|
|
<div class="settings-actions"><button type="button" class="btn btn-outline-danger" @click="deleteAccount()" :disabled="deletingAccount || deleteUsername !== user?.username">{{ deletingAccount ? 'Deleting…' : 'Delete permanently' }}</button><button type="button" class="btn btn-outline-ink" @click="confirmDeleteAccount = false; deleteUsername = ''" :disabled="deletingAccount">Cancel</button></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|