feat: push token generation UI in settings
Author: Aaron Steven White
Commit
6b408bbc3c7e0d8ff39912d270f285a595e5b94cParent: d57a1de96c
Structural diff unavailable
These commits were pushed via plain git push, so no pre-parsed
schemas are available. Install git-remote-cospan and re-push via panproto:// to
see scope-level changes, breaking change detection, and semantic diffs.
brew install panproto/tap/git-remote-cospan1 file changed +103 -1
@@ -3,6 +3,7 @@
33 import { getAuth } from '$lib/stores/auth.svelte'; 44 import KeyForm from '$lib/components/settings/KeyForm.svelte'; 55 import { listKeys, addKey, deleteKey, type Key, type KeyType } from '$lib/api/keys.js'; 6+ import { xrpcProcedure } from '$lib/api/client.js'; 67 import { formatDate } from '$lib/utils/time.js'; 78 89 let auth = $derived(getAuth());
@@ -10,10 +11,40 @@
1011 let loading = $state(true); 1112 let deletingKey = $state<string | null>(null); 1213 let showForm = $state(false); 13- let activeTab: KeyType = $state('ssh'); 14+ let activeTab: KeyType | 'push' = $state('ssh'); 1415 1516 let filteredKeys = $derived(keys.filter((k) => k.type === activeTab)); 1617 18+ // Push token state 19+ let pushToken = $state<string | null>(null); 20+ let pushTokenLoading = $state(false); 21+ let pushTokenError = $state<string | null>(null); 22+ let pushTokenCopied = $state(false); 23+ 24+ async function generatePushToken() { 25+ pushTokenLoading = true; 26+ pushTokenError = null; 27+ pushToken = null; 28+ try { 29+ const result = await xrpcProcedure<{ token: string; did: string; expiresIn: number }>( 30+ 'dev.cospan.repo.createPushToken', 31+ {} 32+ ); 33+ pushToken = result.token; 34+ } catch (e: any) { 35+ pushTokenError = e.message ?? 'Failed to generate token'; 36+ } finally { 37+ pushTokenLoading = false; 38+ } 39+ } 40+ 41+ async function copyToken() { 42+ if (!pushToken) return; 43+ await navigator.clipboard.writeText(pushToken); 44+ pushTokenCopied = true; 45+ setTimeout(() => { pushTokenCopied = false; }, 2000); 46+ } 47+ 1748 onMount(async () => { 1849 if (!auth.authenticated || !auth.did) { 1950 loading = false;
@@ -102,8 +133,78 @@
102133 > 103134 GPG Keys 104135 </button> 136+ <button 137+ onclick={() => { activeTab = 'push'; }} 138+ class="border-b-2 px-4 py-2 text-sm font-medium transition-colors 139+ {activeTab === 'push' 140+ ? 'border-accent text-text-primary' 141+ : 'border-transparent text-text-secondary hover:text-text-primary'}" 142+ > 143+ Push Tokens 144+ </button> 105145 </div> 106146 147+ {#if activeTab === 'push'} 148+ <!-- Push Tokens tab --> 149+ <div class="rounded-lg border border-border bg-surface-1 p-6"> 150+ <h2 class="mb-2 text-sm font-medium text-text-primary">Git Push Token</h2> 151+ <p class="mb-4 text-xs text-text-secondary"> 152+ Generate a short-lived token to authenticate <code class="rounded bg-surface-2 px-1">git push</code> to cospan-node. 153+ Tokens expire after 1 hour. 154+ </p> 155+ 156+ <div class="mb-4 rounded-md border border-border bg-surface-0 p-3 text-xs text-text-secondary"> 157+ <p class="mb-2 font-medium text-text-primary">Usage:</p> 158+ <ol class="list-inside list-decimal space-y-1"> 159+ <li>Click "Generate Token" below</li> 160+ <li>Copy the token</li> 161+ <li>When git prompts for credentials:</li> 162+ </ol> 163+ <div class="mt-2 rounded bg-surface-2 px-3 py-2 font-mono text-[11px]"> 164+ <div>Username: <span class="text-accent">{auth.did ?? 'your-did'}</span></div> 165+ <div>Password: <span class="text-accent">(paste the token)</span></div> 166+ </div> 167+ <div class="mt-2 font-mono text-[11px] text-text-muted"> 168+ git remote add cospan https://node.cospan.dev/{auth.did ?? 'your-did'}/REPO<br/> 169+ git push cospan main 170+ </div> 171+ </div> 172+ 173+ {#if pushToken} 174+ <div class="mb-4 rounded-md border border-emerald-500/20 bg-emerald-500/5 p-3"> 175+ <div class="mb-2 flex items-center justify-between"> 176+ <span class="text-xs font-medium text-emerald-400">Token generated (expires in 1 hour)</span> 177+ <button 178+ onclick={copyToken} 179+ class="rounded bg-emerald-500/15 px-2 py-1 text-[11px] font-medium text-emerald-400 transition-colors hover:bg-emerald-500/25" 180+ > 181+ {pushTokenCopied ? 'Copied!' : 'Copy'} 182+ </button> 183+ </div> 184+ <code class="block break-all rounded bg-surface-2 px-3 py-2 font-mono text-[11px] text-text-primary"> 185+ {pushToken} 186+ </code> 187+ <p class="mt-2 text-[10px] text-text-muted"> 188+ This token will not be shown again. Generate a new one if it expires. 189+ </p> 190+ </div> 191+ {/if} 192+ 193+ {#if pushTokenError} 194+ <div class="mb-4 rounded-md bg-red-500/10 px-3 py-2 text-xs text-red-400"> 195+ {pushTokenError} 196+ </div> 197+ {/if} 198+ 199+ <button 200+ onclick={generatePushToken} 201+ disabled={pushTokenLoading} 202+ class="rounded-md bg-accent px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-hover disabled:opacity-50" 203+ > 204+ {pushTokenLoading ? 'Generating...' : 'Generate Token'} 205+ </button> 206+ </div> 207+ {:else} 107208 <!-- Add key button / form --> 108209 {#if showForm} 109210 <div class="mb-6 rounded-lg border border-border bg-surface-1 p-6">
@@ -172,5 +273,6 @@
172273 {/each} 173274 </div> 174275 {/if} 276+ {/if}<!-- close push tab {:else} --> 175277 {/if} 176278 </section>