@authrim/setup 0.1.61 → 0.1.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/ui.js CHANGED
@@ -382,6 +382,77 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
382
382
  word-break: break-word;
383
383
  }
384
384
 
385
+ /* Progress UI Components */
386
+ .progress-container {
387
+ margin: 1rem 0;
388
+ }
389
+
390
+ .progress-status {
391
+ display: flex;
392
+ align-items: center;
393
+ gap: 0.75rem;
394
+ margin-bottom: 0.75rem;
395
+ }
396
+
397
+ .progress-status .spinner {
398
+ width: 20px;
399
+ height: 20px;
400
+ border: 2px solid #e2e8f0;
401
+ border-top-color: var(--primary);
402
+ border-radius: 50%;
403
+ animation: spin 0.8s linear infinite;
404
+ }
405
+
406
+ @keyframes spin {
407
+ to { transform: rotate(360deg); }
408
+ }
409
+
410
+ .progress-bar-wrapper {
411
+ background: #e2e8f0;
412
+ border-radius: 4px;
413
+ height: 8px;
414
+ overflow: hidden;
415
+ margin-bottom: 0.5rem;
416
+ }
417
+
418
+ .progress-bar {
419
+ height: 100%;
420
+ background: var(--primary);
421
+ border-radius: 4px;
422
+ transition: width 0.3s ease;
423
+ }
424
+
425
+ .progress-text {
426
+ font-size: 0.875rem;
427
+ color: var(--text-muted);
428
+ }
429
+
430
+ .log-toggle {
431
+ display: flex;
432
+ align-items: center;
433
+ gap: 0.5rem;
434
+ padding: 0.5rem 0.75rem;
435
+ background: #f1f5f9;
436
+ border: 1px solid var(--border);
437
+ border-radius: 6px;
438
+ cursor: pointer;
439
+ font-size: 0.875rem;
440
+ color: var(--text-muted);
441
+ margin-top: 1rem;
442
+ }
443
+
444
+ .log-toggle:hover {
445
+ background: #e2e8f0;
446
+ }
447
+
448
+ .log-toggle .arrow {
449
+ transition: transform 0.2s;
450
+ }
451
+
452
+ .log-toggle.open .arrow {
453
+ transform: rotate(90deg);
454
+ }
455
+
385
456
  .step-indicator {
386
457
  display: flex;
387
458
  justify-content: center;
@@ -998,7 +1069,19 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
998
1069
 
999
1070
  <div id="config-preview-section" class="hidden">
1000
1071
  <h3 style="font-size: 1rem; margin-bottom: 0.5rem;">Configuration Preview</h3>
1001
- <div class="config-preview" id="config-preview"></div>
1072
+ <div class="config-preview" id="config-preview-content"></div>
1073
+ </div>
1074
+
1075
+ <div id="config-validation-error" class="hidden" style="margin-top: 1rem; padding: 1rem; background: #fee2e2; border: 1px solid #fca5a5; border-radius: 8px;">
1076
+ <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
1077
+ <span style="font-size: 1.25rem;">⚠️</span>
1078
+ <strong style="color: #b91c1c;">Configuration Validation Failed</strong>
1079
+ </div>
1080
+ <ul id="config-validation-errors" style="margin: 0; padding-left: 1.5rem; color: #991b1b; font-size: 0.875rem;"></ul>
1081
+ </div>
1082
+
1083
+ <div id="config-validation-success" class="hidden" style="margin-top: 1rem; padding: 0.75rem 1rem; background: #d1fae5; border: 1px solid #6ee7b7; border-radius: 8px;">
1084
+ <span style="color: #065f46;">✓ Configuration is valid</span>
1002
1085
  </div>
1003
1086
 
1004
1087
  <div class="button-group">
@@ -1403,6 +1486,23 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1403
1486
  </div>
1404
1487
  </div>
1405
1488
 
1489
+ <!-- Progress UI (shown during provisioning) -->
1490
+ <div id="provision-progress-ui" class="progress-container hidden">
1491
+ <div class="progress-status">
1492
+ <div class="spinner" id="provision-spinner"></div>
1493
+ <span id="provision-current-task">Initializing...</span>
1494
+ </div>
1495
+ <div class="progress-bar-wrapper">
1496
+ <div class="progress-bar" id="provision-progress-bar" style="width: 0%"></div>
1497
+ </div>
1498
+ <div class="progress-text" id="provision-progress-text">0 / 0 resources</div>
1499
+
1500
+ <div class="log-toggle" id="provision-log-toggle">
1501
+ <span class="arrow">▶</span>
1502
+ <span>Show detailed log</span>
1503
+ </div>
1504
+ </div>
1505
+
1406
1506
  <div class="progress-log hidden" id="provision-log">
1407
1507
  <pre id="provision-output"></pre>
1408
1508
  </div>
@@ -1431,7 +1531,24 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1431
1531
  <span class="status-badge status-pending" id="deploy-status">Ready</span>
1432
1532
  </h2>
1433
1533
 
1434
- <p style="margin-bottom: 1rem;">Ready to deploy Authrim workers to Cloudflare.</p>
1534
+ <p id="deploy-ready-text" style="margin-bottom: 1rem;">Ready to deploy Authrim workers to Cloudflare.</p>
1535
+
1536
+ <!-- Progress UI (shown during deployment) -->
1537
+ <div id="deploy-progress-ui" class="progress-container hidden">
1538
+ <div class="progress-status">
1539
+ <div class="spinner" id="deploy-spinner"></div>
1540
+ <span id="deploy-current-task">Initializing...</span>
1541
+ </div>
1542
+ <div class="progress-bar-wrapper">
1543
+ <div class="progress-bar" id="deploy-progress-bar" style="width: 0%"></div>
1544
+ </div>
1545
+ <div class="progress-text" id="deploy-progress-text">0 / 0 components</div>
1546
+
1547
+ <div class="log-toggle" id="deploy-log-toggle">
1548
+ <span class="arrow">▶</span>
1549
+ <span>Show detailed log</span>
1550
+ </div>
1551
+ </div>
1435
1552
 
1436
1553
  <div class="progress-log hidden" id="deploy-log">
1437
1554
  <pre id="deploy-output"></pre>
@@ -1507,6 +1624,31 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1507
1624
  <code id="detail-env-name" style="background: var(--bg); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 1rem;"></code>
1508
1625
  </h2>
1509
1626
 
1627
+ <!-- Admin Setup Section -->
1628
+ <div id="admin-setup-section" class="hidden" style="margin-bottom: 1.5rem; padding: 1rem; background: #fef3c7; border-radius: 8px; border: 1px solid #fcd34d;">
1629
+ <div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;">
1630
+ <span style="font-size: 1.5rem;">⚠️</span>
1631
+ <div>
1632
+ <div style="font-weight: 600; color: #92400e;">Admin Account Not Configured</div>
1633
+ <div style="font-size: 0.875rem; color: #a16207;">Initial administrator has not been set up for this environment.</div>
1634
+ </div>
1635
+ </div>
1636
+ <button class="btn-primary" id="btn-start-admin-setup" style="margin-top: 0.5rem;">
1637
+ 🔐 Start Admin Account Setup with Passkey
1638
+ </button>
1639
+ <div id="admin-setup-result" class="hidden" style="margin-top: 1rem; padding: 0.75rem; background: white; border-radius: 6px;">
1640
+ <div style="font-weight: 500; margin-bottom: 0.5rem;">Setup URL Generated:</div>
1641
+ <div style="display: flex; gap: 0.5rem; align-items: center;">
1642
+ <input type="text" id="admin-setup-url" readonly style="flex: 1; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">
1643
+ <button class="btn-secondary" id="btn-copy-setup-url" style="white-space: nowrap;">📋 Copy</button>
1644
+ <a id="btn-open-setup-url" href="#" target="_blank" class="btn-primary" style="text-decoration: none; white-space: nowrap;">🔗 Open</a>
1645
+ </div>
1646
+ <div style="font-size: 0.75rem; color: #6b7280; margin-top: 0.5rem;">
1647
+ This URL is valid for 1 hour. Open it in a browser to register the first admin account.
1648
+ </div>
1649
+ </div>
1650
+ </div>
1651
+
1510
1652
  <div id="detail-resources">
1511
1653
  <!-- Workers -->
1512
1654
  <div class="resource-section">
@@ -1557,31 +1699,6 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1557
1699
  </div>
1558
1700
  </div>
1559
1701
 
1560
- <!-- Admin Setup Section -->
1561
- <div id="admin-setup-section" class="resource-section hidden" style="margin-top: 1.5rem; padding: 1rem; background: #fef3c7; border-radius: 8px; border: 1px solid #fcd34d;">
1562
- <div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;">
1563
- <span style="font-size: 1.5rem;">⚠️</span>
1564
- <div>
1565
- <div style="font-weight: 600; color: #92400e;">Admin Account Not Configured</div>
1566
- <div style="font-size: 0.875rem; color: #a16207;">Initial administrator has not been set up for this environment.</div>
1567
- </div>
1568
- </div>
1569
- <button class="btn-primary" id="btn-start-admin-setup" style="margin-top: 0.5rem;">
1570
- 🔐 Start Admin Account Setup with Passkey
1571
- </button>
1572
- <div id="admin-setup-result" class="hidden" style="margin-top: 1rem; padding: 0.75rem; background: white; border-radius: 6px;">
1573
- <div style="font-weight: 500; margin-bottom: 0.5rem;">Setup URL Generated:</div>
1574
- <div style="display: flex; gap: 0.5rem; align-items: center;">
1575
- <input type="text" id="admin-setup-url" readonly style="flex: 1; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 4px; font-family: monospace; font-size: 0.875rem;">
1576
- <button class="btn-secondary" id="btn-copy-setup-url" style="white-space: nowrap;">📋 Copy</button>
1577
- <a id="btn-open-setup-url" href="#" target="_blank" class="btn-primary" style="text-decoration: none; white-space: nowrap;">🔗 Open</a>
1578
- </div>
1579
- <div style="font-size: 0.75rem; color: #6b7280; margin-top: 0.5rem;">
1580
- This URL is valid for 1 hour. Open it in a browser to register the first admin account.
1581
- </div>
1582
- </div>
1583
- </div>
1584
-
1585
1702
  <div class="button-group">
1586
1703
  <button class="btn-secondary" id="btn-back-env-detail">← Back to List</button>
1587
1704
  <button class="btn-danger" id="btn-delete-from-detail">🗑️ Delete Environment...</button>
@@ -1603,6 +1720,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1603
1720
  Environment: <code id="delete-env-name" style="background: var(--bg); padding: 0.25rem 0.5rem; border-radius: 4px;"></code>
1604
1721
  </h3>
1605
1722
 
1723
+ <div id="delete-options-section">
1606
1724
  <p style="margin-bottom: 1rem; color: var(--text-muted);">Select resources to delete:</p>
1607
1725
 
1608
1726
  <div class="delete-options">
@@ -1654,6 +1772,24 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1654
1772
  </span>
1655
1773
  </label>
1656
1774
  </div>
1775
+ </div>
1776
+ </div>
1777
+
1778
+ <!-- Progress UI (shown during deletion) -->
1779
+ <div id="delete-progress-ui" class="progress-container hidden">
1780
+ <div class="progress-status">
1781
+ <div class="spinner" id="delete-spinner"></div>
1782
+ <span id="delete-current-task">Initializing...</span>
1783
+ </div>
1784
+ <div class="progress-bar-wrapper">
1785
+ <div class="progress-bar" id="delete-progress-bar" style="width: 0%"></div>
1786
+ </div>
1787
+ <div class="progress-text" id="delete-progress-text">0 / 0 resources</div>
1788
+
1789
+ <div class="log-toggle" id="delete-log-toggle">
1790
+ <span class="arrow">▶</span>
1791
+ <span>Show detailed log</span>
1792
+ </div>
1657
1793
  </div>
1658
1794
 
1659
1795
  <div class="progress-log hidden" id="delete-log">
@@ -1758,6 +1894,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1758
1894
  function showSection(name) {
1759
1895
  Object.values(sections).forEach(s => s.classList.add('hidden'));
1760
1896
  sections[name].classList.remove('hidden');
1897
+ window.scrollTo(0, 0);
1761
1898
  }
1762
1899
 
1763
1900
  // Auto-scroll helper for progress logs
@@ -1767,6 +1904,106 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1767
1904
  }
1768
1905
  }
1769
1906
 
1907
+ // Log toggle functionality
1908
+ function setupLogToggle(toggleId, logId) {
1909
+ const toggle = document.getElementById(toggleId);
1910
+ const log = document.getElementById(logId);
1911
+ if (toggle && log) {
1912
+ toggle.addEventListener('click', () => {
1913
+ const isHidden = log.classList.contains('hidden');
1914
+ if (isHidden) {
1915
+ log.classList.remove('hidden');
1916
+ toggle.classList.add('open');
1917
+ toggle.querySelector('span:last-child').textContent = 'Hide detailed log';
1918
+ } else {
1919
+ log.classList.add('hidden');
1920
+ toggle.classList.remove('open');
1921
+ toggle.querySelector('span:last-child').textContent = 'Show detailed log';
1922
+ }
1923
+ });
1924
+ }
1925
+ }
1926
+
1927
+ // Setup all log toggles
1928
+ setupLogToggle('deploy-log-toggle', 'deploy-log');
1929
+ setupLogToggle('provision-log-toggle', 'provision-log');
1930
+ setupLogToggle('delete-log-toggle', 'delete-log');
1931
+
1932
+ // Progress UI update helper
1933
+ function updateProgressUI(prefix, current, total, currentTask) {
1934
+ const progressBar = document.getElementById(prefix + '-progress-bar');
1935
+ const progressText = document.getElementById(prefix + '-progress-text');
1936
+ const currentTaskEl = document.getElementById(prefix + '-current-task');
1937
+
1938
+ if (progressBar && total > 0) {
1939
+ const percent = Math.round((current / total) * 100);
1940
+ progressBar.style.width = percent + '%';
1941
+ }
1942
+ if (progressText) {
1943
+ progressText.textContent = current + ' / ' + total + ' ' + (prefix === 'deploy' ? 'components' : 'resources');
1944
+ }
1945
+ if (currentTaskEl && currentTask) {
1946
+ currentTaskEl.textContent = currentTask;
1947
+ }
1948
+ }
1949
+
1950
+ // Parse progress message to extract current task
1951
+ function parseProgressMessage(message) {
1952
+ // Match patterns like "Deploying xxx...", "Creating xxx...", "Deleting xxx..."
1953
+ if (message.includes('Deploying ')) {
1954
+ const parts = message.split('Deploying ')[1];
1955
+ if (parts) {
1956
+ const name = parts.split('.')[0].split(' ')[0];
1957
+ if (name) return 'Deploying ' + name + '...';
1958
+ }
1959
+ }
1960
+
1961
+ if (message.includes('Creating ')) {
1962
+ const parts = message.split('Creating ')[1];
1963
+ if (parts) {
1964
+ const name = parts.split(' ')[0].split('.')[0];
1965
+ if (name) return 'Creating ' + name + '...';
1966
+ }
1967
+ }
1968
+
1969
+ if (message.includes('Deleting')) {
1970
+ const parts = message.split('Deleting')[1];
1971
+ if (parts) {
1972
+ const name = parts.trim().split(' ')[0].replace(':', '');
1973
+ if (name) return 'Deleting ' + name + '...';
1974
+ }
1975
+ }
1976
+
1977
+ if (message.includes('✓')) {
1978
+ const parts = message.split('✓')[1];
1979
+ if (parts) {
1980
+ const text = parts.trim().substring(0, 40);
1981
+ return '✓ ' + text;
1982
+ }
1983
+ }
1984
+
1985
+ if (message.includes('Level ')) {
1986
+ const parts = message.split('Level ')[1];
1987
+ if (parts) {
1988
+ const num = parts.trim().split(' ')[0];
1989
+ if (num) return 'Deployment Level ' + num;
1990
+ }
1991
+ }
1992
+
1993
+ if (message.includes('Generating')) {
1994
+ const parts = message.split('Generating')[1];
1995
+ if (parts) {
1996
+ const text = parts.trim().substring(0, 30);
1997
+ return 'Generating ' + text + '...';
1998
+ }
1999
+ }
2000
+
2001
+ if (message.includes('Uploading')) return 'Uploading secrets...';
2002
+ if (message.toLowerCase().includes('building')) return 'Building packages...';
2003
+
2004
+ return null;
2005
+ }
2006
+
1770
2007
  // Safe DOM element creation helpers
1771
2008
  function createAlert(type, content) {
1772
2009
  const div = document.createElement('div');
@@ -1955,23 +2192,76 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
1955
2192
  });
1956
2193
 
1957
2194
  // Load config handlers
1958
- document.getElementById('config-file').addEventListener('change', (e) => {
2195
+ document.getElementById('config-file').addEventListener('change', async (e) => {
1959
2196
  const file = e.target.files[0];
1960
2197
  if (!file) return;
1961
2198
 
1962
2199
  document.getElementById('config-file-name').textContent = file.name;
1963
2200
 
2201
+ // Reset validation display
2202
+ document.getElementById('config-validation-error').classList.add('hidden');
2203
+ document.getElementById('config-validation-success').classList.add('hidden');
2204
+ document.getElementById('config-preview-section').classList.add('hidden');
2205
+ document.getElementById('btn-load-config').disabled = true;
2206
+
1964
2207
  const reader = new FileReader();
1965
- reader.onload = (event) => {
2208
+ reader.onload = async (event) => {
2209
+ let rawConfig;
1966
2210
  try {
1967
- loadedConfig = JSON.parse(event.target.result);
1968
- document.getElementById('config-preview').textContent = JSON.stringify(loadedConfig, null, 2);
1969
- document.getElementById('config-preview-section').classList.remove('hidden');
1970
- document.getElementById('btn-load-config').disabled = false;
2211
+ rawConfig = JSON.parse(event.target.result);
1971
2212
  } catch (err) {
1972
- alert('Invalid JSON file: ' + err.message);
2213
+ document.getElementById('config-validation-error').classList.remove('hidden');
2214
+ const errorList = document.getElementById('config-validation-errors');
2215
+ while (errorList.firstChild) errorList.removeChild(errorList.firstChild);
2216
+ const li = document.createElement('li');
2217
+ li.textContent = 'Invalid JSON: ' + err.message;
2218
+ errorList.appendChild(li);
2219
+ loadedConfig = null;
2220
+ return;
2221
+ }
2222
+
2223
+ // Show preview
2224
+ document.getElementById('config-preview-content').textContent = JSON.stringify(rawConfig, null, 2);
2225
+ document.getElementById('config-preview-section').classList.remove('hidden');
2226
+
2227
+ // Validate via API
2228
+ try {
2229
+ const response = await api('/config/validate', {
2230
+ method: 'POST',
2231
+ headers: { 'Content-Type': 'application/json' },
2232
+ body: JSON.stringify(rawConfig),
2233
+ });
2234
+
2235
+ if (response.valid) {
2236
+ loadedConfig = response.config;
2237
+ document.getElementById('config-validation-success').classList.remove('hidden');
2238
+ document.getElementById('btn-load-config').disabled = false;
2239
+ } else {
2240
+ document.getElementById('config-validation-error').classList.remove('hidden');
2241
+ const errorList = document.getElementById('config-validation-errors');
2242
+ while (errorList.firstChild) errorList.removeChild(errorList.firstChild);
2243
+
2244
+ if (response.errors) {
2245
+ for (const err of response.errors) {
2246
+ const li = document.createElement('li');
2247
+ li.textContent = (err.path ? err.path + ': ' : '') + err.message;
2248
+ errorList.appendChild(li);
2249
+ }
2250
+ } else if (response.error) {
2251
+ const li = document.createElement('li');
2252
+ li.textContent = response.error;
2253
+ errorList.appendChild(li);
2254
+ }
2255
+ loadedConfig = null;
2256
+ }
2257
+ } catch (err) {
2258
+ document.getElementById('config-validation-error').classList.remove('hidden');
2259
+ const errorList = document.getElementById('config-validation-errors');
2260
+ while (errorList.firstChild) errorList.removeChild(errorList.firstChild);
2261
+ const li = document.createElement('li');
2262
+ li.textContent = 'Validation request failed: ' + err.message;
2263
+ errorList.appendChild(li);
1973
2264
  loadedConfig = null;
1974
- document.getElementById('btn-load-config').disabled = true;
1975
2265
  }
1976
2266
  };
1977
2267
  reader.readAsText(file);
@@ -2056,14 +2346,13 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2056
2346
  // Trigger env input to update preview/default labels
2057
2347
  document.getElementById('env').dispatchEvent(new Event('input'));
2058
2348
 
2059
- // Skip to provisioning if resources already exist
2060
- if (loadedConfig.resources) {
2061
- setStep(4);
2062
- showSection('deploy');
2063
- } else {
2064
- setStep(3);
2065
- showSection('provision');
2066
- }
2349
+ // Show configuration screen for review/editing
2350
+ // User can modify settings before proceeding to provision
2351
+ setupMode = 'custom'; // Enable all options for editing
2352
+ document.getElementById('advanced-options').classList.remove('hidden');
2353
+ setStep(2);
2354
+ showSection('config');
2355
+ updatePreview();
2067
2356
  });
2068
2357
 
2069
2358
  // Configuration handlers
@@ -2514,16 +2803,23 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2514
2803
  const resourcePreview = document.getElementById('resource-preview');
2515
2804
  const keysSavedInfo = document.getElementById('keys-saved-info');
2516
2805
  const keysPath = document.getElementById('keys-path');
2806
+ const progressUI = document.getElementById('provision-progress-ui');
2517
2807
 
2518
2808
  btn.disabled = true;
2809
+ btn.classList.add('hidden');
2519
2810
  btnGotoDeploy.classList.add('hidden');
2520
2811
  status.textContent = 'Running...';
2521
2812
  status.className = 'status-badge status-running';
2522
- log.classList.remove('hidden');
2813
+ progressUI.classList.remove('hidden');
2814
+ log.classList.add('hidden'); // Log is hidden by default, toggled via button
2523
2815
  resourcePreview.classList.add('hidden');
2524
2816
  keysSavedInfo.classList.add('hidden');
2525
2817
  output.textContent = '';
2526
2818
 
2819
+ let provisionCompleted = 0;
2820
+ const totalResources = 8; // D1 Core, D1 PII, KV Settings, KV Cache, KV Tokens, R2 (optional), Queues (optional), Keys
2821
+ updateProgressUI('provision', 0, totalResources, 'Initializing...');
2822
+
2527
2823
  // Start polling for progress
2528
2824
  let lastProgressLength = 0;
2529
2825
  provisionPollInterval = setInterval(async () => {
@@ -2534,6 +2830,16 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2534
2830
  const newMessages = statusResult.progress.slice(lastProgressLength);
2535
2831
  newMessages.forEach(msg => {
2536
2832
  output.textContent += msg + '\\n';
2833
+ // Update progress UI based on message content
2834
+ const taskInfo = parseProgressMessage(msg);
2835
+ if (taskInfo) {
2836
+ updateProgressUI('provision', provisionCompleted, totalResources, taskInfo);
2837
+ }
2838
+ // Count completed items (lines with checkmark)
2839
+ if (msg.includes('✓') || msg.includes('✅')) {
2840
+ provisionCompleted++;
2841
+ updateProgressUI('provision', provisionCompleted, totalResources, taskInfo || ('Completed ' + provisionCompleted + ' items'));
2842
+ }
2537
2843
  });
2538
2844
  lastProgressLength = statusResult.progress.length;
2539
2845
  scrollToBottom(log);
@@ -2585,6 +2891,8 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2585
2891
  }
2586
2892
 
2587
2893
  if (result.success) {
2894
+ // Final progress update
2895
+ updateProgressUI('provision', totalResources, totalResources, '✅ Provisioning complete!');
2588
2896
  output.textContent += '\\n✅ Provisioning complete!\\n';
2589
2897
  scrollToBottom(log);
2590
2898
  status.textContent = 'Complete';
@@ -2598,6 +2906,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2598
2906
 
2599
2907
  // Update buttons
2600
2908
  btn.textContent = 'Re-provision (Delete & Create)';
2909
+ btn.classList.remove('hidden');
2601
2910
  btn.disabled = false;
2602
2911
  btnGotoDeploy.classList.remove('hidden');
2603
2912
  } else {
@@ -2614,6 +2923,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2614
2923
  scrollToBottom(log);
2615
2924
  status.textContent = 'Error';
2616
2925
  status.className = 'status-badge status-error';
2926
+ btn.classList.remove('hidden');
2617
2927
  btn.disabled = false;
2618
2928
  resourcePreview.classList.remove('hidden');
2619
2929
  }
@@ -2642,13 +2952,21 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2642
2952
  const status = document.getElementById('deploy-status');
2643
2953
  const log = document.getElementById('deploy-log');
2644
2954
  const output = document.getElementById('deploy-output');
2955
+ const progressUI = document.getElementById('deploy-progress-ui');
2956
+ const readyText = document.getElementById('deploy-ready-text');
2645
2957
 
2646
2958
  btn.disabled = true;
2959
+ btn.classList.add('hidden');
2647
2960
  status.textContent = 'Deploying...';
2648
2961
  status.className = 'status-badge status-running';
2649
- log.classList.remove('hidden');
2962
+ readyText.classList.add('hidden');
2963
+ progressUI.classList.remove('hidden');
2964
+ log.classList.add('hidden'); // Log is hidden by default, toggled via button
2650
2965
  output.textContent = 'Starting deployment...\\n\\n';
2651
- scrollToBottom(log);
2966
+
2967
+ let completedCount = 0;
2968
+ const totalComponents = 10; // Approximate total components
2969
+ updateProgressUI('deploy', 0, totalComponents, 'Initializing...');
2652
2970
 
2653
2971
  try {
2654
2972
  // Generate wrangler configs first
@@ -2666,10 +2984,25 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2666
2984
  scrollToBottom(log);
2667
2985
 
2668
2986
  // Poll for status updates
2987
+ let lastProgressLength = 0;
2669
2988
  const pollInterval = setInterval(async () => {
2670
2989
  const statusResult = await api('/deploy/status');
2671
- if (statusResult.progress && statusResult.progress.length > 0) {
2672
- output.textContent = statusResult.progress.join('\\n') + '\\n';
2990
+ if (statusResult.progress && statusResult.progress.length > lastProgressLength) {
2991
+ const newMessages = statusResult.progress.slice(lastProgressLength);
2992
+ newMessages.forEach(msg => {
2993
+ output.textContent += msg + '\\n';
2994
+ // Update progress UI based on message content
2995
+ const taskInfo = parseProgressMessage(msg);
2996
+ if (taskInfo) {
2997
+ updateProgressUI('deploy', completedCount, totalComponents, taskInfo);
2998
+ }
2999
+ // Count completed items (lines with checkmark)
3000
+ if (msg.includes('✓') || msg.includes('✅')) {
3001
+ completedCount++;
3002
+ updateProgressUI('deploy', completedCount, totalComponents, taskInfo || ('Completed ' + completedCount + ' items'));
3003
+ }
3004
+ });
3005
+ lastProgressLength = statusResult.progress.length;
2673
3006
  scrollToBottom(log);
2674
3007
  }
2675
3008
  }, 1000);
@@ -2685,6 +3018,8 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
2685
3018
  clearInterval(pollInterval);
2686
3019
 
2687
3020
  if (result.success) {
3021
+ // Final progress update
3022
+ updateProgressUI('deploy', totalComponents, totalComponents, '✓ Deployment complete!');
2688
3023
  output.textContent += '\\n✓ Deployment complete!\\n';
2689
3024
  scrollToBottom(log);
2690
3025
 
@@ -3446,9 +3781,11 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
3446
3781
  document.getElementById('delete-pages').checked = true;
3447
3782
 
3448
3783
  // Reset UI state
3784
+ document.getElementById('delete-options-section').classList.remove('hidden');
3449
3785
  document.getElementById('delete-log').classList.add('hidden');
3450
3786
  document.getElementById('delete-result').classList.add('hidden');
3451
- document.getElementById('delete-result').innerHTML = '';
3787
+ document.getElementById('delete-result').textContent = '';
3788
+ document.getElementById('btn-confirm-delete').classList.remove('hidden');
3452
3789
  document.getElementById('btn-confirm-delete').disabled = false;
3453
3790
 
3454
3791
  showSection('envDelete');
@@ -3572,9 +3909,13 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
3572
3909
  const log = document.getElementById('delete-log');
3573
3910
  const output = document.getElementById('delete-output');
3574
3911
  const result = document.getElementById('delete-result');
3912
+ const progressUI = document.getElementById('delete-progress-ui');
3575
3913
 
3576
3914
  btn.disabled = true;
3577
- log.classList.remove('hidden');
3915
+ btn.classList.add('hidden');
3916
+ document.getElementById('delete-options-section').classList.add('hidden');
3917
+ progressUI.classList.remove('hidden');
3918
+ log.classList.add('hidden'); // Log is hidden by default, toggled via button
3578
3919
  result.classList.add('hidden');
3579
3920
  output.textContent = '';
3580
3921
 
@@ -3587,6 +3928,11 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
3587
3928
  deletePages: document.getElementById('delete-pages').checked,
3588
3929
  };
3589
3930
 
3931
+ // Count selected options for progress tracking
3932
+ let deleteCompleted = 0;
3933
+ const totalToDelete = Object.values(deleteOptions).filter(v => v).length;
3934
+ updateProgressUI('delete', 0, totalToDelete, 'Starting deletion...');
3935
+
3590
3936
  // Poll for progress
3591
3937
  let lastProgressLength = 0;
3592
3938
  const pollInterval = setInterval(async () => {
@@ -3596,9 +3942,19 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
3596
3942
  const newMessages = statusResult.progress.slice(lastProgressLength);
3597
3943
  newMessages.forEach(msg => {
3598
3944
  output.textContent += msg + '\\n';
3945
+ // Update progress UI based on message content
3946
+ const taskInfo = parseProgressMessage(msg);
3947
+ if (taskInfo) {
3948
+ updateProgressUI('delete', deleteCompleted, totalToDelete, taskInfo);
3949
+ }
3950
+ // Count completed items (lines with checkmark)
3951
+ if (msg.includes('✓') || msg.includes('✅') || msg.includes('Deleted')) {
3952
+ deleteCompleted++;
3953
+ updateProgressUI('delete', deleteCompleted, totalToDelete, taskInfo || ('Deleted ' + deleteCompleted + ' items'));
3954
+ }
3599
3955
  });
3600
3956
  lastProgressLength = statusResult.progress.length;
3601
- log.scrollTop = log.scrollHeight;
3957
+ scrollToBottom(log);
3602
3958
  }
3603
3959
  } catch (e) {}
3604
3960
  }, 500);
@@ -3619,6 +3975,8 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
3619
3975
  result.classList.remove('hidden');
3620
3976
 
3621
3977
  if (deleteResult.success) {
3978
+ // Final progress update
3979
+ updateProgressUI('delete', totalToDelete, totalToDelete, '✅ Deletion complete!');
3622
3980
  result.textContent = '';
3623
3981
  result.appendChild(createAlert('success', '✅ Environment deleted successfully!'));
3624
3982
 
@@ -3630,6 +3988,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
3630
3988
  } else {
3631
3989
  result.textContent = '';
3632
3990
  result.appendChild(createAlert('error', '❌ Some errors occurred: ' + (deleteResult.errors || []).join(', ')));
3991
+ btn.classList.remove('hidden');
3633
3992
  btn.disabled = false;
3634
3993
  }
3635
3994
  } catch (error) {
@@ -3637,6 +3996,7 @@ export function getHtmlTemplate(sessionToken, manageOnly) {
3637
3996
  result.classList.remove('hidden');
3638
3997
  result.textContent = '';
3639
3998
  result.appendChild(createAlert('error', '❌ Error: ' + error.message));
3999
+ btn.classList.remove('hidden');
3640
4000
  btn.disabled = false;
3641
4001
  }
3642
4002
  });