// Auth service component. // // Declare before api-client in the shell: // // // Exposes: // gate.ready → Promise — awaited by api-client before any call // gate.credentials → current credentials or null // gate.logout() → clears session and reloads // // Credentials shape (extensible for Phase 2): // { // netbox_url: string, // netbox_token: string, // agent_url: string, // } // // Phase 2: swap for that resolves ready with a JWT — api-client unchanged. const SESSION_KEY = 'two:credentials' const CSS = new URL('./login-gate.css', import.meta.url).href class LoginGate extends HTMLElement { #resolve = null get credentials() { const raw = sessionStorage.getItem(SESSION_KEY) return raw ? JSON.parse(raw) : null } logout() { sessionStorage.removeItem(SESSION_KEY) window.location.reload() } async connectedCallback() { this.style.display = 'none' this.ready = new Promise(resolve => { this.#resolve = resolve }) const stored = this.credentials if (stored) { this.#resolve(stored) return } // Load config.json defaults to pre-fill the form let defaults = {} try { const r = await fetch('./config.json') if (r.ok) defaults = await r.json() } catch { /* no defaults */ } this.#renderOverlay(defaults) } #renderOverlay(defaults) { const overlay = document.createElement('div') overlay.attachShadow({ mode: 'open' }) overlay.shadowRoot.innerHTML = `

two — connect

Enter your Netbox and agent details to continue.

` document.body.appendChild(overlay) const form = overlay.shadowRoot.getElementById('form') form.addEventListener('submit', e => { e.preventDefault() const credentials = { netbox_url: overlay.shadowRoot.getElementById('netbox_url').value.replace(/\/$/, ''), netbox_token: overlay.shadowRoot.getElementById('netbox_token').value.trim(), agent_url: overlay.shadowRoot.getElementById('agent_url').value.replace(/\/$/, ''), } sessionStorage.setItem(SESSION_KEY, JSON.stringify(credentials)) overlay.remove() this.#resolve(credentials) }) } } customElements.define('login-gate', LoginGate)