Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion engraphis/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@
<div class="cfg-row" style="display:flex;justify-content:space-between;padding:6px 0"><span>Store</span><span id="cfg-store" style="font-family:var(--mono);font-size:11px;color:var(--text-dim)"></span></div>
</div>
</div>
<div class="card" style="margin-top:12px"><div class="card-head">🤖 Connect your agent</div><div id="tokens-body" class="lic-body">Loading…</div></div>
</div>

</div>
Expand Down Expand Up @@ -557,7 +558,12 @@

/* health + settings */
async function checkHealth(){try{await api('/health');const d=document.getElementById('health-dot'),t=document.getElementById('health-text');if(d)d.style.background='var(--green)';if(t)t.textContent='Connected'}catch(e){const d=document.getElementById('health-dot'),t=document.getElementById('health-text');if(d)d.style.background='var(--red)';if(t)t.textContent='Offline'}}
function loadSettings(){loadLicense();loadSyncStatus();const s=document.getElementById('cfg-store');if(s)s.textContent=location.host}
function loadSettings(){loadLicense();loadSyncStatus();loadApiTokens();const s=document.getElementById('cfg-store');if(s)s.textContent=location.host}

async function renderTokList(){try{const toks=(await api('/auth/tokens')).tokens||[];const el=document.getElementById('tok-list');if(!el)return;el.innerHTML=toks.length?toks.map(t=>`<div class="audit-row"><span style="flex:1">${esc(t.label||'(unlabelled)')} <span style="color:var(--text-dim);font-size:11px">· ${t.revoked?'revoked':fmtRel(t.created_at)}${t.last_used_at?' · used '+fmtRel(t.last_used_at):''}</span></span>${t.revoked?'':`<button class="btn btn-ghost btn-sm" onclick="revokeApiToken('${t.id}')">Revoke</button>`}</div>`).join(''):'<div class="empty" style="padding:10px">No tokens yet.</div>'}catch(e){}}
async function loadApiTokens(){const el=document.getElementById('tokens-body');if(!el)return;try{const st=await api('/auth/state');if(!st.enabled||!st.user){el.innerHTML='<div class="empty" style="padding:14px">Team mode is off — activate a Team license and sign in to connect agents to this instance.</div>';return}let ci={};try{ci=await api('/auth/connect-info')}catch(e){}const base=ci.api_base||(API||'');el.innerHTML=`<div class="cfg-row" style="padding:6px 0;font-size:12px;color:var(--text-muted)">Point an agent at this instance with a per-user bearer token (hashed at rest; shown once). <code style="font-family:var(--mono)">POST ${esc(base)}/remember</code> · <code style="font-family:var(--mono)">GET ${esc(base)}/recall</code></div><div style="display:flex;gap:8px;margin:10px 0"><input class="input" id="tok-label" placeholder="token label (e.g. claude-code)" style="flex:1"><button class="btn btn-primary btn-sm" onclick="createApiToken()">Create token</button></div><div id="tok-created" style="margin:8px 0"></div><div style="font-size:12px;color:var(--text-muted);margin:6px 0 4px">Your tokens</div><div id="tok-list"></div>`;renderTokList()}catch(e){el.innerHTML='<div class="empty" style="padding:14px">'+esc(e.message)+'</div>'}}
async function createApiToken(){const label=(document.getElementById('tok-label').value||'').trim();try{const d=await api('/auth/token',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({label})});document.getElementById('tok-created').innerHTML=`<div class="card" style="background:var(--surface2);padding:10px;margin:8px 0"><div style="font-size:12px;color:var(--text-muted);margin-bottom:4px">Copy this token now — it won't be shown again:</div><code style="font-family:var(--mono);font-size:11px;word-break:break-all">${esc(d.token)}</code><div style="font-size:12px;color:var(--text-muted);margin-top:8px">Add to your agent config: <code style="font-family:var(--mono)">Authorization: Bearer ${esc(d.token)}</code></div></div>`;document.getElementById('tok-label').value='';renderTokList()}catch(e){toast('Token: '+e.message,'err')}}
async function revokeApiToken(id){if(!confirm('Revoke this token? Agents using it will stop working immediately.'))return;try{await api('/auth/token/'+id,{method:'DELETE'});toast('Token revoked','ok');renderTokList()}catch(e){toast(e.message,'err')}}
async function loadSyncStatus(){try{const d=await api('/sync/status');renderSync(d);if(d&&d.available)loadAutoSync()}catch(e){const el=document.getElementById('sync-body');if(el)el.textContent='Cloud sync is unavailable right now.'}}
function renderSync(d){const el=document.getElementById('sync-body');if(!el)return;if(!d||!d.available){el.innerHTML=`<div class="field-hint" style="margin-bottom:10px">Sync your memories to the cloud and pull them on any device — just sign in with your license key.</div><label class="field-lbl">License key</label><div style="display:flex;gap:6px;margin:4px 0 8px"><input class="input" id="sync-key" placeholder="ENGR1.…" style="flex:1"><button class="btn btn-primary btn-sm" onclick="signInSync()">Sign in</button></div><div class="field-hint">No key yet? <a href="#" onclick="startTrial();return false" style="color:var(--accent)">Start a free 3-day trial</a> or <a href="${esc((d&&d.upgrade_url)||'#')}" target="_blank" rel="noopener" style="color:var(--accent)">get ${esc((d&&d.tier_required)||'pro')} →</a></div>`;return}const last=d.last;let status='Not synced yet on this device.';if(last){const when=new Date((last.at||0)*1000).toLocaleString();status='Last synced '+when+' — pushed '+(last.exported||0)+', +'+(last.added||0)+' new from your other devices'+((last.errors&&last.errors.length)?' · '+last.errors.length+' issue(s)':'')+'.'}el.innerHTML=`<div class="cfg-row"><span>Status</span><span class="pill pill-accent">${esc((d.plan||'pro').toUpperCase())} · sync on</span></div><div class="field-hint" style="margin:8px 0">Your memories sync to the cloud. On another device: install Engraphis, open this dashboard, sign in with the same license key, and click Sync — everything downloads automatically.</div><div style="display:flex;gap:10px;align-items:center;margin-top:6px"><button class="btn btn-primary" id="sync-btn" onclick="syncNow()">Sync now</button><span class="field-hint" id="sync-status">${esc(status)}</span></div><div id="autosync-row" style="margin-top:12px;border-top:1px solid var(--border);padding-top:10px"></div>`}
async function signInSync(){const i=document.getElementById('sync-key');const k=i?i.value.trim():'';if(!k){toast('Paste your license key','err');return}try{const d=await api('/license/activate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({key:k})});LIC=d;try{updateLicBadge();updateFeatureLocks()}catch(e){}if((!d.plan||d.plan==='free')&&d.error){toast('Key accepted but not active — '+d.error,'err')}else{toast('Signed in — '+(d.plan||'').toUpperCase()+' plan','ok')}await loadSyncStatus();try{loadLicense()}catch(e){}}catch(e){toast('Sign-in failed: '+e.message,'err')}}
Expand Down