@@ -91,6 +91,7 @@ public static class SetupPages
9191 font-size: 0.85rem; color: #94a3b8; margin-top: 0.5rem;
9292 }
9393 .device-code-box a { color: #60a5fa; text-decoration: underline; }
94+ .disabled-section { opacity: 0.4; pointer-events: none; }
9495 </style>
9596</head>
9697<body>
@@ -100,9 +101,21 @@ public static class SetupPages
100101
101102 <div id="status-banner" class="info-banner hidden"></div>
102103
103- <!-- Option 1: Automated Setup (Device Code Flow) -->
104- <div class="card" id="auto-section">
105- <h2>Automated Setup</h2>
104+ <!-- Step 1: First User -->
105+ <div class="card" id="user-section">
106+ <h2>Step 1: First User</h2>
107+ <p>Add the first superadmin user before configuring authentication. This user will have full access to the application.</p>
108+ <label for="seed-upn">User Principal Name (email)</label>
109+ <input type="text" id="seed-upn" placeholder="admin@contoso.com">
110+ <button class="btn btn-primary" id="btn-seed" onclick="seedFirstUser()" disabled>Add Superadmin</button>
111+ <div id="seed-status" class="status"></div>
112+ </div>
113+
114+ <div class="divider" id="divider-user"></div>
115+
116+ <!-- Step 2: Automated Setup (Device Code Flow) -->
117+ <div class="card disabled-section" id="auto-section">
118+ <h2>Step 2a: Automated Setup</h2>
106119 <p>Sign in with a Global Administrator account to automatically create the EasyAuth app registration and configure this App Service.</p>
107120
108121 <label style="margin-bottom: 0.25rem;">Tenant Access</label>
@@ -131,11 +144,11 @@ public static class SetupPages
131144 <div id="auto-status" class="status"></div>
132145 </div>
133146
134- <div class="divider"></div>
147+ <div class="divider" id="divider-auth" ></div>
135148
136- <!-- Option 2 : Manual Setup -->
137- <div class="card" id="manual-section">
138- <h2>Manual Setup</h2>
149+ <!-- Step 2b : Manual Setup -->
150+ <div class="card disabled-section " id="manual-section">
151+ <h2>Step 2b: Manual Setup</h2>
139152 <p>If you already have an app registration, enter the details below.</p>
140153 <label style="margin-bottom: 0.25rem;">Tenant Access</label>
141154 <div class="toggle-group" id="manual-tenant-toggle">
@@ -205,15 +218,84 @@ function setTenantMode(section, isMulti) {
205218 if (setupState.isEasyAuthConfigured) {
206219 showBanner('Authentication is already configured. Redirecting...');
207220 setTimeout(() => window.location.href = '/', 2000);
221+ return;
208222 }
209223 if (!setupState.isRunningInAppService || !setupState.hasManagedIdentity) {
210224 showBanner('Warning: No managed identity detected. ARM self-configuration may fail. Use manual setup instead.');
211225 }
226+
227+ // Handle user table status
228+ const us = setupState.usersStatus;
229+ if (!us || !us.connected) {
230+ // Connection error — disable user section
231+ document.getElementById('btn-seed').disabled = true;
232+ showStatus('seed-status', 'Cannot connect to storage: ' + (us?.error || 'Unknown error'), 'error');
233+ } else if (us.hasUsers) {
234+ // Users already exist — skip to auth setup
235+ document.getElementById('user-section').classList.add('disabled-section');
236+ showStatus('seed-status', 'Users already exist in the table. Proceed to authentication setup below.', 'success');
237+ enableAuthSections();
238+ } else {
239+ // No users — enable the seed form, keep auth disabled
240+ document.getElementById('btn-seed').disabled = false;
241+ }
212242 } catch (e) {
213243 console.error('Failed to load status', e);
214244 }
215245 })();
216246
247+ // Background poll — detect setup completion from any session
248+ let statusPollTimer = setInterval(async () => {
249+ try {
250+ const res = await fetch('/api/setup/status', { cache: 'no-store' });
251+ if (!res.ok) return;
252+ const status = await res.json();
253+ if (status.isSetupCompleted || status.isEasyAuthConfigured) {
254+ clearInterval(statusPollTimer);
255+ showRestartScreen();
256+ }
257+ } catch (e) {
258+ // Setup endpoint may be unavailable during restart — ignore
259+ }
260+ }, 5000);
261+
262+ function enableAuthSections() {
263+ document.getElementById('auto-section').classList.remove('disabled-section');
264+ document.getElementById('manual-section').classList.remove('disabled-section');
265+ }
266+
267+ async function seedFirstUser() {
268+ const upn = document.getElementById('seed-upn').value.trim();
269+ if (!upn) {
270+ showStatus('seed-status', 'Please enter a valid email address.', 'error');
271+ return;
272+ }
273+
274+ const btn = document.getElementById('btn-seed');
275+ btn.disabled = true;
276+ btn.textContent = 'Adding user...';
277+ showStatus('seed-status', 'Adding superadmin user...', 'info');
278+
279+ try {
280+ const res = await fetch('/api/setup/seed-user', {
281+ method: 'POST',
282+ headers: { 'Content-Type': 'application/json' },
283+ body: JSON.stringify({ upn })
284+ });
285+
286+ const data = await res.json();
287+ if (!res.ok || !data.success) throw new Error(data.message || 'Failed to add user');
288+
289+ showStatus('seed-status', data.message, 'success');
290+ document.getElementById('user-section').classList.add('disabled-section');
291+ enableAuthSections();
292+ } catch (e) {
293+ showStatus('seed-status', 'Error: ' + e.message, 'error');
294+ btn.disabled = false;
295+ btn.textContent = 'Add Superadmin';
296+ }
297+ }
298+
217299 function showBanner(msg) {
218300 const el = document.getElementById('status-banner');
219301 el.textContent = msg;
@@ -377,10 +459,14 @@ async function submitManual() {
377459 }
378460 }
379461 function showRestartScreen() {
462+ // Stop background status polling — restart screen has its own polling
463+ clearInterval(statusPollTimer);
380464 // Hide setup sections, show restart polling UI
465+ document.getElementById('user-section').classList.add('hidden');
381466 document.getElementById('auto-section').classList.add('hidden');
382467 document.getElementById('manual-section').classList.add('hidden');
383- document.querySelector('.divider').classList.add('hidden');
468+ document.getElementById('divider-user').classList.add('hidden');
469+ document.getElementById('divider-auth').classList.add('hidden');
384470 document.querySelector('.subtitle').textContent = '';
385471 document.getElementById('page-title').textContent = 'Restarting...';
386472
0 commit comments