@authrim/setup 0.1.4 → 0.1.7
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/README.md +163 -24
- package/dist/cli/commands/init.js +1 -1
- package/dist/core/cloudflare.d.ts +103 -0
- package/dist/core/cloudflare.d.ts.map +1 -1
- package/dist/core/cloudflare.js +465 -14
- package/dist/core/cloudflare.js.map +1 -1
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +82 -1
- package/dist/web/api.js.map +1 -1
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +2 -2
- package/dist/web/server.js.map +1 -1
- package/dist/web/ui.d.ts +2 -1
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +1429 -59
- package/dist/web/ui.js.map +1 -1
- package/package.json +1 -1
package/dist/web/ui.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* HTML Template for Authrim Setup Web UI
|
|
3
3
|
*
|
|
4
4
|
* A simple, self-contained UI for the setup wizard.
|
|
5
|
+
* Follows the setup flow defined in the design document.
|
|
5
6
|
*/
|
|
6
|
-
export function getHtmlTemplate(sessionToken) {
|
|
7
|
+
export function getHtmlTemplate(sessionToken, manageOnly) {
|
|
7
8
|
// Escape token for safe embedding in JavaScript
|
|
8
9
|
const safeToken = sessionToken ? sessionToken.replace(/['"\\]/g, '') : '';
|
|
10
|
+
const manageOnlyFlag = manageOnly ? 'true' : 'false';
|
|
9
11
|
return `<!DOCTYPE html>
|
|
10
12
|
<html lang="en">
|
|
11
13
|
<head>
|
|
@@ -90,6 +92,71 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
90
92
|
.status-success { background: #d1fae5; color: var(--success); }
|
|
91
93
|
.status-error { background: #fee2e2; color: var(--error); }
|
|
92
94
|
|
|
95
|
+
/* Mode selection cards */
|
|
96
|
+
.mode-cards {
|
|
97
|
+
display: grid;
|
|
98
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
99
|
+
gap: 1rem;
|
|
100
|
+
margin-bottom: 1rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.mode-card {
|
|
104
|
+
border: 2px solid var(--border);
|
|
105
|
+
border-radius: 12px;
|
|
106
|
+
padding: 1.5rem;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
transition: all 0.2s;
|
|
109
|
+
position: relative;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.mode-card:hover {
|
|
113
|
+
border-color: var(--primary);
|
|
114
|
+
background: #f8fafc;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.mode-card.selected {
|
|
118
|
+
border-color: var(--primary);
|
|
119
|
+
background: #eff6ff;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.mode-card .mode-icon {
|
|
123
|
+
font-size: 2rem;
|
|
124
|
+
margin-bottom: 0.5rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.mode-card h3 {
|
|
128
|
+
font-size: 1.1rem;
|
|
129
|
+
margin-bottom: 0.5rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.mode-card p {
|
|
133
|
+
font-size: 0.875rem;
|
|
134
|
+
color: var(--text-muted);
|
|
135
|
+
margin-bottom: 0.75rem;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.mode-card ul {
|
|
139
|
+
font-size: 0.8rem;
|
|
140
|
+
color: var(--text-muted);
|
|
141
|
+
margin-left: 1rem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.mode-card ul li {
|
|
145
|
+
margin-bottom: 0.25rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.mode-badge {
|
|
149
|
+
position: absolute;
|
|
150
|
+
top: -8px;
|
|
151
|
+
right: 10px;
|
|
152
|
+
background: var(--primary);
|
|
153
|
+
color: white;
|
|
154
|
+
font-size: 0.7rem;
|
|
155
|
+
padding: 0.2rem 0.5rem;
|
|
156
|
+
border-radius: 4px;
|
|
157
|
+
font-weight: 500;
|
|
158
|
+
}
|
|
159
|
+
|
|
93
160
|
.form-group {
|
|
94
161
|
margin-bottom: 1rem;
|
|
95
162
|
}
|
|
@@ -102,6 +169,7 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
102
169
|
|
|
103
170
|
input[type="text"],
|
|
104
171
|
input[type="password"],
|
|
172
|
+
input[type="file"],
|
|
105
173
|
select {
|
|
106
174
|
width: 100%;
|
|
107
175
|
padding: 0.75rem;
|
|
@@ -257,7 +325,297 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
257
325
|
color: var(--primary);
|
|
258
326
|
}
|
|
259
327
|
|
|
328
|
+
.file-input-wrapper {
|
|
329
|
+
position: relative;
|
|
330
|
+
overflow: hidden;
|
|
331
|
+
display: inline-block;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.file-input-wrapper input[type=file] {
|
|
335
|
+
position: absolute;
|
|
336
|
+
left: 0;
|
|
337
|
+
top: 0;
|
|
338
|
+
opacity: 0;
|
|
339
|
+
cursor: pointer;
|
|
340
|
+
width: 100%;
|
|
341
|
+
height: 100%;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.file-input-btn {
|
|
345
|
+
display: inline-block;
|
|
346
|
+
padding: 0.75rem 1.5rem;
|
|
347
|
+
background: var(--border);
|
|
348
|
+
color: var(--text);
|
|
349
|
+
border-radius: 8px;
|
|
350
|
+
cursor: pointer;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.file-input-btn:hover {
|
|
354
|
+
background: #cbd5e1;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.config-preview {
|
|
358
|
+
background: var(--bg);
|
|
359
|
+
border-radius: 8px;
|
|
360
|
+
padding: 1rem;
|
|
361
|
+
margin-top: 1rem;
|
|
362
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
363
|
+
font-size: 0.8rem;
|
|
364
|
+
max-height: 200px;
|
|
365
|
+
overflow-y: auto;
|
|
366
|
+
}
|
|
367
|
+
|
|
260
368
|
.hidden { display: none; }
|
|
369
|
+
|
|
370
|
+
/* Resource preview styles */
|
|
371
|
+
.resource-preview {
|
|
372
|
+
background: var(--bg);
|
|
373
|
+
border: 1px solid var(--border);
|
|
374
|
+
border-radius: 8px;
|
|
375
|
+
padding: 1rem;
|
|
376
|
+
margin-bottom: 1rem;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.resource-list {
|
|
380
|
+
display: grid;
|
|
381
|
+
gap: 1rem;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.resource-category {
|
|
385
|
+
font-size: 0.875rem;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.resource-category strong {
|
|
389
|
+
display: block;
|
|
390
|
+
margin-bottom: 0.5rem;
|
|
391
|
+
color: var(--text);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.resource-category ul {
|
|
395
|
+
margin: 0;
|
|
396
|
+
padding-left: 1.5rem;
|
|
397
|
+
color: var(--text-muted);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.resource-category li {
|
|
401
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
402
|
+
font-size: 0.8rem;
|
|
403
|
+
margin-bottom: 0.25rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/* Progress spinner */
|
|
407
|
+
.spinner {
|
|
408
|
+
display: inline-block;
|
|
409
|
+
width: 16px;
|
|
410
|
+
height: 16px;
|
|
411
|
+
border: 2px solid var(--border);
|
|
412
|
+
border-top-color: var(--primary);
|
|
413
|
+
border-radius: 50%;
|
|
414
|
+
animation: spin 1s linear infinite;
|
|
415
|
+
margin-right: 0.5rem;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
@keyframes spin {
|
|
419
|
+
to { transform: rotate(360deg); }
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.progress-item {
|
|
423
|
+
display: flex;
|
|
424
|
+
align-items: center;
|
|
425
|
+
margin-bottom: 0.5rem;
|
|
426
|
+
color: #e2e8f0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.progress-item.complete {
|
|
430
|
+
color: var(--success);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.progress-item.error {
|
|
434
|
+
color: var(--error);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* Environment cards */
|
|
438
|
+
.env-cards {
|
|
439
|
+
display: grid;
|
|
440
|
+
gap: 1rem;
|
|
441
|
+
margin-bottom: 1rem;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.env-card {
|
|
445
|
+
border: 1px solid var(--border);
|
|
446
|
+
border-radius: 8px;
|
|
447
|
+
padding: 1rem;
|
|
448
|
+
display: flex;
|
|
449
|
+
justify-content: space-between;
|
|
450
|
+
align-items: center;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.env-card:hover {
|
|
454
|
+
border-color: var(--primary);
|
|
455
|
+
background: #f8fafc;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.env-card-info {
|
|
459
|
+
flex: 1;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.env-card-name {
|
|
463
|
+
font-size: 1.1rem;
|
|
464
|
+
font-weight: 600;
|
|
465
|
+
margin-bottom: 0.5rem;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.env-card-stats {
|
|
469
|
+
display: flex;
|
|
470
|
+
gap: 1rem;
|
|
471
|
+
font-size: 0.8rem;
|
|
472
|
+
color: var(--text-muted);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.env-card-stat {
|
|
476
|
+
display: flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
gap: 0.25rem;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.env-card-actions {
|
|
482
|
+
display: flex;
|
|
483
|
+
gap: 0.5rem;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.btn-danger {
|
|
487
|
+
background: var(--error);
|
|
488
|
+
color: white;
|
|
489
|
+
padding: 0.5rem 1rem;
|
|
490
|
+
font-size: 0.875rem;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.btn-danger:hover {
|
|
494
|
+
background: #dc2626;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.btn-info {
|
|
498
|
+
background: var(--primary);
|
|
499
|
+
color: white;
|
|
500
|
+
padding: 0.5rem 1rem;
|
|
501
|
+
font-size: 0.875rem;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.btn-info:hover {
|
|
505
|
+
background: var(--primary-dark);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/* Resource list in details view */
|
|
509
|
+
.resource-section {
|
|
510
|
+
margin-bottom: 1.5rem;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.resource-section-title {
|
|
514
|
+
font-size: 1rem;
|
|
515
|
+
font-weight: 600;
|
|
516
|
+
margin-bottom: 0.75rem;
|
|
517
|
+
display: flex;
|
|
518
|
+
align-items: center;
|
|
519
|
+
gap: 0.5rem;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.resource-section-title .count {
|
|
523
|
+
font-size: 0.8rem;
|
|
524
|
+
color: var(--text-muted);
|
|
525
|
+
font-weight: normal;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.resource-list {
|
|
529
|
+
background: var(--bg);
|
|
530
|
+
border-radius: 8px;
|
|
531
|
+
padding: 0.75rem;
|
|
532
|
+
max-height: 200px;
|
|
533
|
+
overflow-y: auto;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.resource-item {
|
|
537
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
538
|
+
font-size: 0.8rem;
|
|
539
|
+
padding: 0.25rem 0.5rem;
|
|
540
|
+
margin-bottom: 0.25rem;
|
|
541
|
+
background: var(--card-bg);
|
|
542
|
+
border-radius: 4px;
|
|
543
|
+
border: 1px solid var(--border);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.resource-item:last-child {
|
|
547
|
+
margin-bottom: 0;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.resource-item-name {
|
|
551
|
+
font-weight: 500;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.resource-item-details {
|
|
555
|
+
font-size: 0.75rem;
|
|
556
|
+
color: var(--text-muted);
|
|
557
|
+
margin-top: 0.25rem;
|
|
558
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.resource-item-details span {
|
|
562
|
+
margin-right: 1rem;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.resource-item-loading {
|
|
566
|
+
color: var(--text-muted);
|
|
567
|
+
font-style: italic;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.resource-item-error {
|
|
571
|
+
color: var(--error);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.resource-item-not-deployed {
|
|
575
|
+
color: var(--warning);
|
|
576
|
+
font-style: italic;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.resource-empty {
|
|
580
|
+
color: var(--text-muted);
|
|
581
|
+
font-style: italic;
|
|
582
|
+
font-size: 0.875rem;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/* Delete options */
|
|
586
|
+
.delete-options {
|
|
587
|
+
display: grid;
|
|
588
|
+
gap: 0.75rem;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.delete-option {
|
|
592
|
+
display: flex;
|
|
593
|
+
align-items: center;
|
|
594
|
+
gap: 0.75rem;
|
|
595
|
+
padding: 0.75rem;
|
|
596
|
+
border: 1px solid var(--border);
|
|
597
|
+
border-radius: 8px;
|
|
598
|
+
cursor: pointer;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.delete-option:hover {
|
|
602
|
+
background: var(--bg);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.delete-option input[type="checkbox"] {
|
|
606
|
+
width: 18px;
|
|
607
|
+
height: 18px;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.delete-option span {
|
|
611
|
+
display: flex;
|
|
612
|
+
flex-direction: column;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.delete-option small {
|
|
616
|
+
color: var(--text-muted);
|
|
617
|
+
font-size: 0.8rem;
|
|
618
|
+
}
|
|
261
619
|
</style>
|
|
262
620
|
</head>
|
|
263
621
|
<body>
|
|
@@ -267,7 +625,7 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
267
625
|
<p class="subtitle">OIDC Provider on Cloudflare Workers</p>
|
|
268
626
|
</header>
|
|
269
627
|
|
|
270
|
-
<div class="step-indicator">
|
|
628
|
+
<div class="step-indicator" id="step-indicator">
|
|
271
629
|
<div class="step step-active" id="step-1">1</div>
|
|
272
630
|
<div class="step-connector"></div>
|
|
273
631
|
<div class="step step-pending" id="step-2">2</div>
|
|
@@ -288,6 +646,91 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
288
646
|
</div>
|
|
289
647
|
</div>
|
|
290
648
|
|
|
649
|
+
<!-- Step 1.5: Top Menu (New Setup / Load Config / Manage) -->
|
|
650
|
+
<div id="section-top-menu" class="card hidden">
|
|
651
|
+
<h2 class="card-title">Get Started</h2>
|
|
652
|
+
<p style="margin-bottom: 1.5rem; color: var(--text-muted);">Choose an option to continue:</p>
|
|
653
|
+
|
|
654
|
+
<div class="mode-cards" style="grid-template-columns: repeat(3, 1fr);">
|
|
655
|
+
<div class="mode-card" id="menu-new-setup">
|
|
656
|
+
<div class="mode-icon">🆕</div>
|
|
657
|
+
<h3>New Setup</h3>
|
|
658
|
+
<p>Create a new Authrim deployment from scratch</p>
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
<div class="mode-card" id="menu-load-config">
|
|
662
|
+
<div class="mode-icon">📂</div>
|
|
663
|
+
<h3>Load Config</h3>
|
|
664
|
+
<p>Resume or redeploy using existing config</p>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<div class="mode-card" id="menu-manage-env">
|
|
668
|
+
<div class="mode-icon">🗑️</div>
|
|
669
|
+
<h3>Manage Environments</h3>
|
|
670
|
+
<p>View, inspect, or delete existing environments</p>
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
<!-- Step 1.6: Setup Mode Selection (Quick / Custom) -->
|
|
676
|
+
<div id="section-mode" class="card hidden">
|
|
677
|
+
<h2 class="card-title">Setup Mode</h2>
|
|
678
|
+
<p style="margin-bottom: 1.5rem; color: var(--text-muted);">Choose how you want to set up Authrim:</p>
|
|
679
|
+
|
|
680
|
+
<div class="mode-cards">
|
|
681
|
+
<div class="mode-card" id="mode-quick">
|
|
682
|
+
<div class="mode-icon">⚡</div>
|
|
683
|
+
<h3>Quick Setup</h3>
|
|
684
|
+
<p>Get started in ~5 minutes</p>
|
|
685
|
+
<ul>
|
|
686
|
+
<li>Environment selection</li>
|
|
687
|
+
<li>Optional custom domain</li>
|
|
688
|
+
<li>Default components</li>
|
|
689
|
+
</ul>
|
|
690
|
+
<span class="mode-badge">Recommended</span>
|
|
691
|
+
</div>
|
|
692
|
+
|
|
693
|
+
<div class="mode-card" id="mode-custom">
|
|
694
|
+
<div class="mode-icon">🔧</div>
|
|
695
|
+
<h3>Custom Setup</h3>
|
|
696
|
+
<p>Full control over configuration</p>
|
|
697
|
+
<ul>
|
|
698
|
+
<li>Component selection</li>
|
|
699
|
+
<li>URL configuration</li>
|
|
700
|
+
<li>Advanced settings</li>
|
|
701
|
+
</ul>
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
704
|
+
|
|
705
|
+
<div class="button-group">
|
|
706
|
+
<button class="btn-secondary" id="btn-back-top">Back</button>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
|
|
710
|
+
<!-- Step 1.7: Load Config -->
|
|
711
|
+
<div id="section-load-config" class="card hidden">
|
|
712
|
+
<h2 class="card-title">Load Configuration</h2>
|
|
713
|
+
<p style="margin-bottom: 1rem; color: var(--text-muted);">Select your authrim-config.json file:</p>
|
|
714
|
+
|
|
715
|
+
<div class="form-group">
|
|
716
|
+
<div class="file-input-wrapper">
|
|
717
|
+
<span class="file-input-btn">📁 Choose File</span>
|
|
718
|
+
<input type="file" id="config-file" accept=".json">
|
|
719
|
+
</div>
|
|
720
|
+
<span id="config-file-name" style="margin-left: 1rem; color: var(--text-muted);"></span>
|
|
721
|
+
</div>
|
|
722
|
+
|
|
723
|
+
<div id="config-preview-section" class="hidden">
|
|
724
|
+
<h3 style="font-size: 1rem; margin-bottom: 0.5rem;">Configuration Preview</h3>
|
|
725
|
+
<div class="config-preview" id="config-preview"></div>
|
|
726
|
+
</div>
|
|
727
|
+
|
|
728
|
+
<div class="button-group">
|
|
729
|
+
<button class="btn-secondary" id="btn-back-top-2">Back</button>
|
|
730
|
+
<button class="btn-primary" id="btn-load-config" disabled>Load & Continue</button>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
|
|
291
734
|
<!-- Step 2: Configuration -->
|
|
292
735
|
<div id="section-config" class="card hidden">
|
|
293
736
|
<h2 class="card-title">Configuration</h2>
|
|
@@ -298,40 +741,55 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
298
741
|
<option value="prod">Production (prod)</option>
|
|
299
742
|
<option value="staging">Staging</option>
|
|
300
743
|
<option value="dev">Development (dev)</option>
|
|
744
|
+
<option value="custom">Custom...</option>
|
|
301
745
|
</select>
|
|
302
746
|
</div>
|
|
303
747
|
|
|
748
|
+
<div class="form-group hidden" id="custom-env-group">
|
|
749
|
+
<label for="custom-env">Custom Environment Name</label>
|
|
750
|
+
<input type="text" id="custom-env" placeholder="e.g., test, demo, myenv">
|
|
751
|
+
<small style="color: var(--text-muted)">Lowercase letters, numbers, and hyphens only</small>
|
|
752
|
+
</div>
|
|
753
|
+
|
|
304
754
|
<div class="form-group">
|
|
305
755
|
<label for="domain">Custom Domain (optional)</label>
|
|
306
756
|
<input type="text" id="domain" placeholder="auth.example.com">
|
|
307
757
|
<small style="color: var(--text-muted)">Leave empty to use workers.dev / pages.dev</small>
|
|
308
758
|
</div>
|
|
309
759
|
|
|
310
|
-
|
|
311
|
-
<div
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
760
|
+
<!-- Advanced options (shown in custom mode) -->
|
|
761
|
+
<div id="advanced-options" class="hidden">
|
|
762
|
+
<h3 style="margin: 1.5rem 0 1rem; font-size: 1rem;">Components</h3>
|
|
763
|
+
<div class="checkbox-group">
|
|
764
|
+
<label class="checkbox-item">
|
|
765
|
+
<input type="checkbox" id="comp-api" checked disabled>
|
|
766
|
+
API (required)
|
|
767
|
+
</label>
|
|
768
|
+
<label class="checkbox-item">
|
|
769
|
+
<input type="checkbox" id="comp-login-ui" checked>
|
|
770
|
+
Login UI
|
|
771
|
+
</label>
|
|
772
|
+
<label class="checkbox-item">
|
|
773
|
+
<input type="checkbox" id="comp-admin-ui" checked>
|
|
774
|
+
Admin UI
|
|
775
|
+
</label>
|
|
776
|
+
<label class="checkbox-item">
|
|
777
|
+
<input type="checkbox" id="comp-saml">
|
|
778
|
+
SAML IdP
|
|
779
|
+
</label>
|
|
780
|
+
<label class="checkbox-item">
|
|
781
|
+
<input type="checkbox" id="comp-async">
|
|
782
|
+
Device Flow / CIBA
|
|
783
|
+
</label>
|
|
784
|
+
<label class="checkbox-item">
|
|
785
|
+
<input type="checkbox" id="comp-vc">
|
|
786
|
+
Verifiable Credentials
|
|
787
|
+
</label>
|
|
788
|
+
</div>
|
|
332
789
|
</div>
|
|
333
790
|
|
|
334
791
|
<div class="button-group">
|
|
792
|
+
<button class="btn-secondary" id="btn-back-mode">Back</button>
|
|
335
793
|
<button class="btn-primary" id="btn-configure">Continue</button>
|
|
336
794
|
</div>
|
|
337
795
|
</div>
|
|
@@ -344,19 +802,43 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
344
802
|
</h2>
|
|
345
803
|
|
|
346
804
|
<p style="margin-bottom: 1rem;">The following resources will be created:</p>
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
<
|
|
351
|
-
|
|
805
|
+
|
|
806
|
+
<!-- Resource names preview -->
|
|
807
|
+
<div id="resource-preview" class="resource-preview">
|
|
808
|
+
<h4 style="font-size: 0.9rem; margin-bottom: 0.75rem; color: var(--text-muted);">📋 Resource Names:</h4>
|
|
809
|
+
<div class="resource-list">
|
|
810
|
+
<div class="resource-category">
|
|
811
|
+
<strong>D1 Databases:</strong>
|
|
812
|
+
<ul id="preview-d1"></ul>
|
|
813
|
+
</div>
|
|
814
|
+
<div class="resource-category">
|
|
815
|
+
<strong>KV Namespaces:</strong>
|
|
816
|
+
<ul id="preview-kv"></ul>
|
|
817
|
+
</div>
|
|
818
|
+
<div class="resource-category">
|
|
819
|
+
<strong>Cryptographic Keys:</strong>
|
|
820
|
+
<ul id="preview-keys"></ul>
|
|
821
|
+
</div>
|
|
822
|
+
</div>
|
|
823
|
+
</div>
|
|
352
824
|
|
|
353
825
|
<div class="progress-log hidden" id="provision-log">
|
|
354
826
|
<pre id="provision-output"></pre>
|
|
355
827
|
</div>
|
|
356
828
|
|
|
829
|
+
<!-- Keys saved location (shown after completion) -->
|
|
830
|
+
<div id="keys-saved-info" class="alert alert-info hidden" style="margin-top: 1rem;">
|
|
831
|
+
<strong>🔑 Keys saved to:</strong>
|
|
832
|
+
<code style="display: block; margin-top: 0.5rem; padding: 0.5rem; background: #f1f5f9; border-radius: 4px;" id="keys-path"></code>
|
|
833
|
+
<p style="margin-top: 0.5rem; font-size: 0.85rem; color: var(--text-muted);">
|
|
834
|
+
⚠️ Keep this directory safe and add it to .gitignore
|
|
835
|
+
</p>
|
|
836
|
+
</div>
|
|
837
|
+
|
|
357
838
|
<div class="button-group">
|
|
358
839
|
<button class="btn-secondary" id="btn-back-config">Back</button>
|
|
359
840
|
<button class="btn-primary" id="btn-provision">Create Resources</button>
|
|
841
|
+
<button class="btn-primary hidden" id="btn-goto-deploy">Continue to Deploy →</button>
|
|
360
842
|
</div>
|
|
361
843
|
</div>
|
|
362
844
|
|
|
@@ -382,7 +864,7 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
382
864
|
<!-- Complete -->
|
|
383
865
|
<div id="section-complete" class="card hidden">
|
|
384
866
|
<h2 class="card-title" style="color: var(--success);">
|
|
385
|
-
Setup Complete!
|
|
867
|
+
✅ Setup Complete!
|
|
386
868
|
</h2>
|
|
387
869
|
|
|
388
870
|
<p>Authrim has been successfully deployed.</p>
|
|
@@ -394,20 +876,184 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
394
876
|
<div class="alert alert-info" style="margin-top: 1rem;">
|
|
395
877
|
<strong>Next Steps:</strong>
|
|
396
878
|
<ol style="margin-left: 1.5rem; margin-top: 0.5rem;">
|
|
397
|
-
<li>Visit the
|
|
879
|
+
<li>Visit the setup URL to create your first admin account</li>
|
|
880
|
+
<li>Log in to the Admin UI to create OAuth clients</li>
|
|
398
881
|
<li>Configure your application to use the OIDC endpoints</li>
|
|
399
882
|
</ol>
|
|
400
883
|
</div>
|
|
401
884
|
</div>
|
|
885
|
+
|
|
886
|
+
<!-- Environment Management: List -->
|
|
887
|
+
<div id="section-env-list" class="card hidden">
|
|
888
|
+
<h2 class="card-title">
|
|
889
|
+
Manage Environments
|
|
890
|
+
<span class="status-badge status-pending" id="env-list-status">Loading...</span>
|
|
891
|
+
</h2>
|
|
892
|
+
|
|
893
|
+
<p style="margin-bottom: 1rem; color: var(--text-muted);">
|
|
894
|
+
Detected Authrim environments in your Cloudflare account:
|
|
895
|
+
</p>
|
|
896
|
+
|
|
897
|
+
<div id="env-list-loading" class="progress-log">
|
|
898
|
+
<pre id="env-scan-output"></pre>
|
|
899
|
+
</div>
|
|
900
|
+
|
|
901
|
+
<div id="env-list-content" class="hidden">
|
|
902
|
+
<div id="env-cards" class="env-cards">
|
|
903
|
+
<!-- Environment cards will be inserted here -->
|
|
904
|
+
</div>
|
|
905
|
+
|
|
906
|
+
<div id="no-envs-message" class="alert alert-info hidden">
|
|
907
|
+
No Authrim environments detected in this Cloudflare account.
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
|
|
911
|
+
<div class="button-group">
|
|
912
|
+
<button class="btn-secondary" id="btn-back-env-list">Back</button>
|
|
913
|
+
<button class="btn-secondary" id="btn-refresh-env-list">🔄 Refresh</button>
|
|
914
|
+
</div>
|
|
915
|
+
</div>
|
|
916
|
+
|
|
917
|
+
<!-- Environment Management: Details -->
|
|
918
|
+
<div id="section-env-detail" class="card hidden">
|
|
919
|
+
<h2 class="card-title">
|
|
920
|
+
📋 Environment Details
|
|
921
|
+
<code id="detail-env-name" style="background: var(--bg); padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 1rem;"></code>
|
|
922
|
+
</h2>
|
|
923
|
+
|
|
924
|
+
<div id="detail-resources">
|
|
925
|
+
<!-- Workers -->
|
|
926
|
+
<div class="resource-section">
|
|
927
|
+
<div class="resource-section-title">
|
|
928
|
+
🔧 Workers <span class="count" id="detail-workers-count">(0)</span>
|
|
929
|
+
</div>
|
|
930
|
+
<div class="resource-list" id="detail-workers-list"></div>
|
|
931
|
+
</div>
|
|
932
|
+
|
|
933
|
+
<!-- D1 Databases -->
|
|
934
|
+
<div class="resource-section">
|
|
935
|
+
<div class="resource-section-title">
|
|
936
|
+
📊 D1 Databases <span class="count" id="detail-d1-count">(0)</span>
|
|
937
|
+
</div>
|
|
938
|
+
<div class="resource-list" id="detail-d1-list"></div>
|
|
939
|
+
</div>
|
|
940
|
+
|
|
941
|
+
<!-- KV Namespaces -->
|
|
942
|
+
<div class="resource-section">
|
|
943
|
+
<div class="resource-section-title">
|
|
944
|
+
🗄️ KV Namespaces <span class="count" id="detail-kv-count">(0)</span>
|
|
945
|
+
</div>
|
|
946
|
+
<div class="resource-list" id="detail-kv-list"></div>
|
|
947
|
+
</div>
|
|
948
|
+
|
|
949
|
+
<!-- Queues -->
|
|
950
|
+
<div class="resource-section" id="detail-queues-section">
|
|
951
|
+
<div class="resource-section-title">
|
|
952
|
+
📨 Queues <span class="count" id="detail-queues-count">(0)</span>
|
|
953
|
+
</div>
|
|
954
|
+
<div class="resource-list" id="detail-queues-list"></div>
|
|
955
|
+
</div>
|
|
956
|
+
|
|
957
|
+
<!-- R2 Buckets -->
|
|
958
|
+
<div class="resource-section" id="detail-r2-section">
|
|
959
|
+
<div class="resource-section-title">
|
|
960
|
+
📁 R2 Buckets <span class="count" id="detail-r2-count">(0)</span>
|
|
961
|
+
</div>
|
|
962
|
+
<div class="resource-list" id="detail-r2-list"></div>
|
|
963
|
+
</div>
|
|
964
|
+
</div>
|
|
965
|
+
|
|
966
|
+
<div class="button-group">
|
|
967
|
+
<button class="btn-secondary" id="btn-back-env-detail">← Back to List</button>
|
|
968
|
+
<button class="btn-danger" id="btn-delete-from-detail">🗑️ Delete Environment...</button>
|
|
969
|
+
</div>
|
|
970
|
+
</div>
|
|
971
|
+
|
|
972
|
+
<!-- Environment Management: Delete Confirmation -->
|
|
973
|
+
<div id="section-env-delete" class="card hidden">
|
|
974
|
+
<h2 class="card-title" style="color: var(--error);">
|
|
975
|
+
⚠️ Delete Environment
|
|
976
|
+
</h2>
|
|
977
|
+
|
|
978
|
+
<div class="alert alert-warning">
|
|
979
|
+
<strong>Warning:</strong> This action is irreversible. All selected resources will be permanently deleted.
|
|
980
|
+
</div>
|
|
981
|
+
|
|
982
|
+
<div style="margin: 1.5rem 0;">
|
|
983
|
+
<h3 style="font-size: 1.1rem; margin-bottom: 1rem;">
|
|
984
|
+
Environment: <code id="delete-env-name" style="background: var(--bg); padding: 0.25rem 0.5rem; border-radius: 4px;"></code>
|
|
985
|
+
</h3>
|
|
986
|
+
|
|
987
|
+
<p style="margin-bottom: 1rem; color: var(--text-muted);">Select resources to delete:</p>
|
|
988
|
+
|
|
989
|
+
<div class="delete-options">
|
|
990
|
+
<label class="checkbox-item delete-option">
|
|
991
|
+
<input type="checkbox" id="delete-workers" checked>
|
|
992
|
+
<span>
|
|
993
|
+
<strong>Workers</strong>
|
|
994
|
+
<small id="delete-workers-count">(0 workers)</small>
|
|
995
|
+
</span>
|
|
996
|
+
</label>
|
|
997
|
+
|
|
998
|
+
<label class="checkbox-item delete-option">
|
|
999
|
+
<input type="checkbox" id="delete-d1" checked>
|
|
1000
|
+
<span>
|
|
1001
|
+
<strong>D1 Databases</strong>
|
|
1002
|
+
<small id="delete-d1-count">(0 databases)</small>
|
|
1003
|
+
</span>
|
|
1004
|
+
</label>
|
|
1005
|
+
|
|
1006
|
+
<label class="checkbox-item delete-option">
|
|
1007
|
+
<input type="checkbox" id="delete-kv" checked>
|
|
1008
|
+
<span>
|
|
1009
|
+
<strong>KV Namespaces</strong>
|
|
1010
|
+
<small id="delete-kv-count">(0 namespaces)</small>
|
|
1011
|
+
</span>
|
|
1012
|
+
</label>
|
|
1013
|
+
|
|
1014
|
+
<label class="checkbox-item delete-option">
|
|
1015
|
+
<input type="checkbox" id="delete-queues" checked>
|
|
1016
|
+
<span>
|
|
1017
|
+
<strong>Queues</strong>
|
|
1018
|
+
<small id="delete-queues-count">(0 queues)</small>
|
|
1019
|
+
</span>
|
|
1020
|
+
</label>
|
|
1021
|
+
|
|
1022
|
+
<label class="checkbox-item delete-option">
|
|
1023
|
+
<input type="checkbox" id="delete-r2" checked>
|
|
1024
|
+
<span>
|
|
1025
|
+
<strong>R2 Buckets</strong>
|
|
1026
|
+
<small id="delete-r2-count">(0 buckets)</small>
|
|
1027
|
+
</span>
|
|
1028
|
+
</label>
|
|
1029
|
+
</div>
|
|
1030
|
+
</div>
|
|
1031
|
+
|
|
1032
|
+
<div class="progress-log hidden" id="delete-log">
|
|
1033
|
+
<pre id="delete-output"></pre>
|
|
1034
|
+
</div>
|
|
1035
|
+
|
|
1036
|
+
<div id="delete-result" class="hidden"></div>
|
|
1037
|
+
|
|
1038
|
+
<div class="button-group">
|
|
1039
|
+
<button class="btn-secondary" id="btn-back-env-delete">Cancel</button>
|
|
1040
|
+
<button class="btn-primary" id="btn-confirm-delete" style="background: var(--error);">🗑️ Delete Selected</button>
|
|
1041
|
+
</div>
|
|
1042
|
+
</div>
|
|
402
1043
|
</div>
|
|
403
1044
|
|
|
404
1045
|
<script>
|
|
405
1046
|
// Session token for API authentication (embedded by server)
|
|
406
1047
|
const SESSION_TOKEN = '${safeToken}';
|
|
1048
|
+
const MANAGE_ONLY = ${manageOnlyFlag};
|
|
407
1049
|
|
|
408
1050
|
// State
|
|
409
1051
|
let currentStep = 1;
|
|
1052
|
+
let setupMode = 'quick'; // 'quick' or 'custom'
|
|
410
1053
|
let config = {};
|
|
1054
|
+
let loadedConfig = null;
|
|
1055
|
+
let provisioningCompleted = false;
|
|
1056
|
+
let provisionPollInterval = null;
|
|
411
1057
|
|
|
412
1058
|
// Elements
|
|
413
1059
|
const steps = {
|
|
@@ -419,12 +1065,23 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
419
1065
|
|
|
420
1066
|
const sections = {
|
|
421
1067
|
prerequisites: document.getElementById('section-prerequisites'),
|
|
1068
|
+
topMenu: document.getElementById('section-top-menu'),
|
|
1069
|
+
mode: document.getElementById('section-mode'),
|
|
1070
|
+
loadConfig: document.getElementById('section-load-config'),
|
|
422
1071
|
config: document.getElementById('section-config'),
|
|
423
1072
|
provision: document.getElementById('section-provision'),
|
|
424
1073
|
deploy: document.getElementById('section-deploy'),
|
|
425
1074
|
complete: document.getElementById('section-complete'),
|
|
1075
|
+
envList: document.getElementById('section-env-list'),
|
|
1076
|
+
envDetail: document.getElementById('section-env-detail'),
|
|
1077
|
+
envDelete: document.getElementById('section-env-delete'),
|
|
426
1078
|
};
|
|
427
1079
|
|
|
1080
|
+
// Environment management state
|
|
1081
|
+
let detectedEnvironments = [];
|
|
1082
|
+
let selectedEnvForDetail = null;
|
|
1083
|
+
let selectedEnvForDelete = null;
|
|
1084
|
+
|
|
428
1085
|
// API helpers (with session token authentication)
|
|
429
1086
|
async function api(endpoint, options = {}) {
|
|
430
1087
|
const response = await fetch('/api' + endpoint, {
|
|
@@ -512,6 +1169,9 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
512
1169
|
const code = document.createElement('code');
|
|
513
1170
|
code.style.display = 'block';
|
|
514
1171
|
code.style.marginTop = '0.5rem';
|
|
1172
|
+
code.style.padding = '0.5rem';
|
|
1173
|
+
code.style.background = '#f1f5f9';
|
|
1174
|
+
code.style.borderRadius = '4px';
|
|
515
1175
|
code.textContent = 'npm install -g wrangler';
|
|
516
1176
|
alertDiv.appendChild(code);
|
|
517
1177
|
|
|
@@ -537,6 +1197,9 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
537
1197
|
const code = document.createElement('code');
|
|
538
1198
|
code.style.display = 'block';
|
|
539
1199
|
code.style.marginTop = '0.5rem';
|
|
1200
|
+
code.style.padding = '0.5rem';
|
|
1201
|
+
code.style.background = '#f1f5f9';
|
|
1202
|
+
code.style.borderRadius = '4px';
|
|
540
1203
|
code.textContent = 'wrangler login';
|
|
541
1204
|
alertDiv.appendChild(code);
|
|
542
1205
|
|
|
@@ -556,11 +1219,11 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
556
1219
|
alertDiv.className = 'alert alert-success';
|
|
557
1220
|
|
|
558
1221
|
const p1 = document.createElement('p');
|
|
559
|
-
p1.textContent = 'Wrangler installed';
|
|
1222
|
+
p1.textContent = '✓ Wrangler installed';
|
|
560
1223
|
alertDiv.appendChild(p1);
|
|
561
1224
|
|
|
562
1225
|
const p2 = document.createElement('p');
|
|
563
|
-
p2.textContent = 'Logged in as ' + (result.auth.email || 'Unknown');
|
|
1226
|
+
p2.textContent = '✓ Logged in as ' + (result.auth.email || 'Unknown');
|
|
564
1227
|
alertDiv.appendChild(p2);
|
|
565
1228
|
|
|
566
1229
|
prereqContent.appendChild(alertDiv);
|
|
@@ -570,8 +1233,8 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
570
1233
|
|
|
571
1234
|
const btn = document.createElement('button');
|
|
572
1235
|
btn.className = 'btn-primary';
|
|
573
|
-
btn.textContent = '
|
|
574
|
-
btn.addEventListener('click',
|
|
1236
|
+
btn.textContent = 'Continue';
|
|
1237
|
+
btn.addEventListener('click', showTopMenu);
|
|
575
1238
|
buttonGroup.appendChild(btn);
|
|
576
1239
|
|
|
577
1240
|
prereqContent.appendChild(buttonGroup);
|
|
@@ -586,15 +1249,134 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
586
1249
|
}
|
|
587
1250
|
}
|
|
588
1251
|
|
|
589
|
-
//
|
|
590
|
-
function
|
|
1252
|
+
// Show top menu
|
|
1253
|
+
function showTopMenu() {
|
|
1254
|
+
showSection('topMenu');
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Top menu handlers
|
|
1258
|
+
document.getElementById('menu-new-setup').addEventListener('click', () => {
|
|
1259
|
+
showSection('mode');
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
document.getElementById('menu-load-config').addEventListener('click', () => {
|
|
1263
|
+
showSection('loadConfig');
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
// Setup mode handlers
|
|
1267
|
+
document.getElementById('mode-quick').addEventListener('click', () => {
|
|
1268
|
+
setupMode = 'quick';
|
|
1269
|
+
document.getElementById('mode-quick').classList.add('selected');
|
|
1270
|
+
document.getElementById('mode-custom').classList.remove('selected');
|
|
1271
|
+
document.getElementById('advanced-options').classList.add('hidden');
|
|
591
1272
|
setStep(2);
|
|
592
1273
|
showSection('config');
|
|
593
|
-
}
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
document.getElementById('mode-custom').addEventListener('click', () => {
|
|
1277
|
+
setupMode = 'custom';
|
|
1278
|
+
document.getElementById('mode-custom').classList.add('selected');
|
|
1279
|
+
document.getElementById('mode-quick').classList.remove('selected');
|
|
1280
|
+
document.getElementById('advanced-options').classList.remove('hidden');
|
|
1281
|
+
setStep(2);
|
|
1282
|
+
showSection('config');
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
document.getElementById('btn-back-top').addEventListener('click', () => {
|
|
1286
|
+
showSection('topMenu');
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
document.getElementById('btn-back-top-2').addEventListener('click', () => {
|
|
1290
|
+
showSection('topMenu');
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
// Load config handlers
|
|
1294
|
+
document.getElementById('config-file').addEventListener('change', (e) => {
|
|
1295
|
+
const file = e.target.files[0];
|
|
1296
|
+
if (!file) return;
|
|
1297
|
+
|
|
1298
|
+
document.getElementById('config-file-name').textContent = file.name;
|
|
1299
|
+
|
|
1300
|
+
const reader = new FileReader();
|
|
1301
|
+
reader.onload = (event) => {
|
|
1302
|
+
try {
|
|
1303
|
+
loadedConfig = JSON.parse(event.target.result);
|
|
1304
|
+
document.getElementById('config-preview').textContent = JSON.stringify(loadedConfig, null, 2);
|
|
1305
|
+
document.getElementById('config-preview-section').classList.remove('hidden');
|
|
1306
|
+
document.getElementById('btn-load-config').disabled = false;
|
|
1307
|
+
} catch (err) {
|
|
1308
|
+
alert('Invalid JSON file: ' + err.message);
|
|
1309
|
+
loadedConfig = null;
|
|
1310
|
+
document.getElementById('btn-load-config').disabled = true;
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
reader.readAsText(file);
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
document.getElementById('btn-load-config').addEventListener('click', async () => {
|
|
1317
|
+
if (!loadedConfig) return;
|
|
1318
|
+
|
|
1319
|
+
// Use loaded config
|
|
1320
|
+
config = {
|
|
1321
|
+
env: loadedConfig.environment?.prefix || 'prod',
|
|
1322
|
+
domain: loadedConfig.urls?.api?.custom || null,
|
|
1323
|
+
components: loadedConfig.components || {
|
|
1324
|
+
api: true,
|
|
1325
|
+
loginUi: true,
|
|
1326
|
+
adminUi: true,
|
|
1327
|
+
saml: false,
|
|
1328
|
+
async: false,
|
|
1329
|
+
vc: false,
|
|
1330
|
+
},
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
// Set form values
|
|
1334
|
+
const envSelect = document.getElementById('env');
|
|
1335
|
+
if (['prod', 'staging', 'dev'].includes(config.env)) {
|
|
1336
|
+
envSelect.value = config.env;
|
|
1337
|
+
} else {
|
|
1338
|
+
envSelect.value = 'custom';
|
|
1339
|
+
document.getElementById('custom-env').value = config.env;
|
|
1340
|
+
document.getElementById('custom-env-group').classList.remove('hidden');
|
|
1341
|
+
}
|
|
1342
|
+
document.getElementById('domain').value = config.domain || '';
|
|
1343
|
+
|
|
1344
|
+
// Skip to provisioning if resources already exist
|
|
1345
|
+
if (loadedConfig.resources) {
|
|
1346
|
+
setStep(4);
|
|
1347
|
+
showSection('deploy');
|
|
1348
|
+
} else {
|
|
1349
|
+
setStep(3);
|
|
1350
|
+
showSection('provision');
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
// Environment dropdown handler
|
|
1355
|
+
document.getElementById('env').addEventListener('change', (e) => {
|
|
1356
|
+
const customGroup = document.getElementById('custom-env-group');
|
|
1357
|
+
if (e.target.value === 'custom') {
|
|
1358
|
+
customGroup.classList.remove('hidden');
|
|
1359
|
+
} else {
|
|
1360
|
+
customGroup.classList.add('hidden');
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
// Configuration handlers
|
|
1365
|
+
document.getElementById('btn-back-mode').addEventListener('click', () => {
|
|
1366
|
+
setStep(1);
|
|
1367
|
+
showSection('mode');
|
|
1368
|
+
});
|
|
594
1369
|
|
|
595
|
-
// Configure
|
|
596
1370
|
document.getElementById('btn-configure').addEventListener('click', async () => {
|
|
597
|
-
|
|
1371
|
+
let env = document.getElementById('env').value;
|
|
1372
|
+
if (env === 'custom') {
|
|
1373
|
+
env = document.getElementById('custom-env').value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
1374
|
+
if (!env) {
|
|
1375
|
+
alert('Please enter a valid environment name');
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
598
1380
|
const domain = document.getElementById('domain').value;
|
|
599
1381
|
|
|
600
1382
|
config = {
|
|
@@ -602,10 +1384,11 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
602
1384
|
domain: domain || null,
|
|
603
1385
|
components: {
|
|
604
1386
|
api: true,
|
|
605
|
-
loginUi: document.getElementById('comp-login-ui').checked,
|
|
606
|
-
adminUi: document.getElementById('comp-admin-ui').checked,
|
|
607
|
-
saml: document.getElementById('comp-saml').checked,
|
|
608
|
-
|
|
1387
|
+
loginUi: setupMode === 'quick' || document.getElementById('comp-login-ui').checked,
|
|
1388
|
+
adminUi: setupMode === 'quick' || document.getElementById('comp-admin-ui').checked,
|
|
1389
|
+
saml: setupMode === 'custom' && document.getElementById('comp-saml').checked,
|
|
1390
|
+
async: setupMode === 'custom' && document.getElementById('comp-async').checked,
|
|
1391
|
+
vc: setupMode === 'custom' && document.getElementById('comp-vc').checked,
|
|
609
1392
|
},
|
|
610
1393
|
};
|
|
611
1394
|
|
|
@@ -615,6 +1398,10 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
615
1398
|
body: { env, domain },
|
|
616
1399
|
});
|
|
617
1400
|
|
|
1401
|
+
// Update resource preview with the selected env
|
|
1402
|
+
updateResourcePreview(env);
|
|
1403
|
+
updateProvisionButtons();
|
|
1404
|
+
|
|
618
1405
|
setStep(3);
|
|
619
1406
|
showSection('provision');
|
|
620
1407
|
});
|
|
@@ -627,52 +1414,119 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
627
1414
|
// Provision
|
|
628
1415
|
document.getElementById('btn-provision').addEventListener('click', async () => {
|
|
629
1416
|
const btn = document.getElementById('btn-provision');
|
|
1417
|
+
const btnGotoDeploy = document.getElementById('btn-goto-deploy');
|
|
630
1418
|
const status = document.getElementById('provision-status');
|
|
631
1419
|
const log = document.getElementById('provision-log');
|
|
632
1420
|
const output = document.getElementById('provision-output');
|
|
1421
|
+
const resourcePreview = document.getElementById('resource-preview');
|
|
1422
|
+
const keysSavedInfo = document.getElementById('keys-saved-info');
|
|
1423
|
+
const keysPath = document.getElementById('keys-path');
|
|
633
1424
|
|
|
634
1425
|
btn.disabled = true;
|
|
1426
|
+
btnGotoDeploy.classList.add('hidden');
|
|
635
1427
|
status.textContent = 'Running...';
|
|
636
1428
|
status.className = 'status-badge status-running';
|
|
637
1429
|
log.classList.remove('hidden');
|
|
1430
|
+
resourcePreview.classList.add('hidden');
|
|
1431
|
+
keysSavedInfo.classList.add('hidden');
|
|
638
1432
|
output.textContent = '';
|
|
639
1433
|
|
|
1434
|
+
// Start polling for progress
|
|
1435
|
+
let lastProgressLength = 0;
|
|
1436
|
+
provisionPollInterval = setInterval(async () => {
|
|
1437
|
+
try {
|
|
1438
|
+
const statusResult = await api('/deploy/status');
|
|
1439
|
+
if (statusResult.progress && statusResult.progress.length > lastProgressLength) {
|
|
1440
|
+
// Append new progress messages
|
|
1441
|
+
const newMessages = statusResult.progress.slice(lastProgressLength);
|
|
1442
|
+
newMessages.forEach(msg => {
|
|
1443
|
+
output.textContent += msg + '\\n';
|
|
1444
|
+
});
|
|
1445
|
+
lastProgressLength = statusResult.progress.length;
|
|
1446
|
+
// Auto-scroll to bottom
|
|
1447
|
+
log.scrollTop = log.scrollHeight;
|
|
1448
|
+
}
|
|
1449
|
+
} catch (e) {
|
|
1450
|
+
// Ignore polling errors
|
|
1451
|
+
}
|
|
1452
|
+
}, 500);
|
|
1453
|
+
|
|
640
1454
|
try {
|
|
641
1455
|
// Generate keys
|
|
642
|
-
output.textContent += 'Generating cryptographic keys...\\n';
|
|
643
|
-
await api('/keys/generate', {
|
|
1456
|
+
output.textContent += '🔐 Generating cryptographic keys...\\n';
|
|
1457
|
+
const keyResult = await api('/keys/generate', {
|
|
644
1458
|
method: 'POST',
|
|
645
1459
|
body: { keyId: config.env + '-key-' + Date.now() },
|
|
646
1460
|
});
|
|
647
|
-
output.textContent += '
|
|
1461
|
+
output.textContent += ' ✓ RSA key pair generated\\n';
|
|
1462
|
+
output.textContent += ' ✓ Encryption keys generated\\n';
|
|
1463
|
+
output.textContent += ' ✓ Admin secrets generated\\n';
|
|
1464
|
+
output.textContent += '\\n';
|
|
1465
|
+
|
|
1466
|
+
// Show keys saved location (relative to authrim source directory)
|
|
1467
|
+
keysPath.textContent = './.keys/';
|
|
648
1468
|
|
|
649
1469
|
// Provision resources
|
|
650
|
-
output.textContent += 'Provisioning Cloudflare resources...\\n';
|
|
1470
|
+
output.textContent += '☁️ Provisioning Cloudflare resources...\\n';
|
|
1471
|
+
|
|
651
1472
|
const result = await api('/provision', {
|
|
652
1473
|
method: 'POST',
|
|
653
1474
|
body: { env: config.env },
|
|
654
1475
|
});
|
|
655
1476
|
|
|
1477
|
+
// Stop polling
|
|
1478
|
+
if (provisionPollInterval) {
|
|
1479
|
+
clearInterval(provisionPollInterval);
|
|
1480
|
+
provisionPollInterval = null;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
656
1483
|
if (result.success) {
|
|
657
|
-
output.textContent += '\\
|
|
1484
|
+
output.textContent += '\\n✅ Provisioning complete!\\n';
|
|
658
1485
|
status.textContent = 'Complete';
|
|
659
1486
|
status.className = 'status-badge status-success';
|
|
660
1487
|
|
|
661
|
-
|
|
662
|
-
|
|
1488
|
+
// Mark provisioning as completed
|
|
1489
|
+
provisioningCompleted = true;
|
|
1490
|
+
|
|
1491
|
+
// Show keys saved info
|
|
1492
|
+
keysSavedInfo.classList.remove('hidden');
|
|
1493
|
+
|
|
1494
|
+
// Update buttons
|
|
1495
|
+
btn.textContent = 'Re-provision (Delete & Create)';
|
|
1496
|
+
btn.disabled = false;
|
|
1497
|
+
btnGotoDeploy.classList.remove('hidden');
|
|
663
1498
|
} else {
|
|
664
1499
|
throw new Error(result.error);
|
|
665
1500
|
}
|
|
666
1501
|
} catch (error) {
|
|
667
|
-
|
|
1502
|
+
// Stop polling
|
|
1503
|
+
if (provisionPollInterval) {
|
|
1504
|
+
clearInterval(provisionPollInterval);
|
|
1505
|
+
provisionPollInterval = null;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
output.textContent += '\\n❌ Error: ' + error.message + '\\n';
|
|
668
1509
|
status.textContent = 'Error';
|
|
669
1510
|
status.className = 'status-badge status-error';
|
|
670
1511
|
btn.disabled = false;
|
|
1512
|
+
resourcePreview.classList.remove('hidden');
|
|
671
1513
|
}
|
|
672
1514
|
});
|
|
673
1515
|
|
|
1516
|
+
// Continue to Deploy button
|
|
1517
|
+
document.getElementById('btn-goto-deploy').addEventListener('click', () => {
|
|
1518
|
+
setStep(4);
|
|
1519
|
+
showSection('deploy');
|
|
1520
|
+
});
|
|
1521
|
+
|
|
674
1522
|
document.getElementById('btn-back-provision').addEventListener('click', () => {
|
|
675
1523
|
setStep(3);
|
|
1524
|
+
// Update buttons based on provisioning status
|
|
1525
|
+
updateProvisionButtons();
|
|
1526
|
+
// Show resource preview if not completed
|
|
1527
|
+
if (!provisioningCompleted) {
|
|
1528
|
+
document.getElementById('resource-preview').classList.remove('hidden');
|
|
1529
|
+
}
|
|
676
1530
|
showSection('provision');
|
|
677
1531
|
});
|
|
678
1532
|
|
|
@@ -696,7 +1550,7 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
696
1550
|
method: 'POST',
|
|
697
1551
|
body: { env: config.env },
|
|
698
1552
|
});
|
|
699
|
-
output.textContent += 'Config files generated\\n\\n';
|
|
1553
|
+
output.textContent += '✓ Config files generated\\n\\n';
|
|
700
1554
|
|
|
701
1555
|
// Start deployment
|
|
702
1556
|
output.textContent += 'Deploying workers...\\n';
|
|
@@ -704,7 +1558,7 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
704
1558
|
// Poll for status updates
|
|
705
1559
|
const pollInterval = setInterval(async () => {
|
|
706
1560
|
const statusResult = await api('/deploy/status');
|
|
707
|
-
if (statusResult.progress.length > 0) {
|
|
1561
|
+
if (statusResult.progress && statusResult.progress.length > 0) {
|
|
708
1562
|
output.textContent = statusResult.progress.join('\\n') + '\\n';
|
|
709
1563
|
}
|
|
710
1564
|
}, 1000);
|
|
@@ -720,17 +1574,17 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
720
1574
|
clearInterval(pollInterval);
|
|
721
1575
|
|
|
722
1576
|
if (result.success) {
|
|
723
|
-
output.textContent += '\\
|
|
1577
|
+
output.textContent += '\\n✓ Deployment complete!\\n';
|
|
724
1578
|
status.textContent = 'Complete';
|
|
725
1579
|
status.className = 'status-badge status-success';
|
|
726
1580
|
|
|
727
1581
|
// Show completion
|
|
728
|
-
showComplete();
|
|
1582
|
+
showComplete(result);
|
|
729
1583
|
} else {
|
|
730
1584
|
throw new Error(result.error || 'Deployment failed');
|
|
731
1585
|
}
|
|
732
1586
|
} catch (error) {
|
|
733
|
-
output.textContent += '\\
|
|
1587
|
+
output.textContent += '\\n✗ Error: ' + error.message + '\\n';
|
|
734
1588
|
status.textContent = 'Error';
|
|
735
1589
|
status.className = 'status-badge status-error';
|
|
736
1590
|
btn.disabled = false;
|
|
@@ -738,7 +1592,7 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
738
1592
|
});
|
|
739
1593
|
|
|
740
1594
|
// Show completion
|
|
741
|
-
function showComplete() {
|
|
1595
|
+
function showComplete(result) {
|
|
742
1596
|
const urlsEl = document.getElementById('urls');
|
|
743
1597
|
const env = config.env;
|
|
744
1598
|
const domain = config.domain;
|
|
@@ -753,11 +1607,527 @@ export function getHtmlTemplate(sessionToken) {
|
|
|
753
1607
|
urlsEl.appendChild(createUrlItem('Login UI:', loginUrl));
|
|
754
1608
|
urlsEl.appendChild(createUrlItem('Admin UI:', adminUrl));
|
|
755
1609
|
|
|
1610
|
+
// Add setup URL if available
|
|
1611
|
+
if (result && result.setupUrl) {
|
|
1612
|
+
const setupItem = createUrlItem('Admin Setup:', result.setupUrl);
|
|
1613
|
+
setupItem.querySelector('a').style.fontWeight = 'bold';
|
|
1614
|
+
urlsEl.appendChild(setupItem);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
756
1617
|
showSection('complete');
|
|
757
1618
|
}
|
|
758
1619
|
|
|
1620
|
+
// Resource naming functions
|
|
1621
|
+
function getResourceNames(env) {
|
|
1622
|
+
const envUpper = env.toUpperCase();
|
|
1623
|
+
return {
|
|
1624
|
+
d1: [
|
|
1625
|
+
env + '-authrim-core-db',
|
|
1626
|
+
env + '-authrim-pii-db'
|
|
1627
|
+
],
|
|
1628
|
+
kv: [
|
|
1629
|
+
envUpper + '-CLIENTS_CACHE',
|
|
1630
|
+
envUpper + '-INITIAL_ACCESS_TOKENS',
|
|
1631
|
+
envUpper + '-SETTINGS',
|
|
1632
|
+
envUpper + '-REBAC_CACHE',
|
|
1633
|
+
envUpper + '-USER_CACHE',
|
|
1634
|
+
envUpper + '-AUTHRIM_CONFIG',
|
|
1635
|
+
envUpper + '-STATE_STORE',
|
|
1636
|
+
envUpper + '-CONSENT_CACHE'
|
|
1637
|
+
],
|
|
1638
|
+
keys: [
|
|
1639
|
+
'.keys/private.pem (RSA Private Key)',
|
|
1640
|
+
'.keys/public.jwk.json (JWK Public Key)',
|
|
1641
|
+
'.keys/rp_token_encryption_key.txt',
|
|
1642
|
+
'.keys/admin_api_secret.txt',
|
|
1643
|
+
'.keys/key_manager_secret.txt',
|
|
1644
|
+
'.keys/setup_token.txt'
|
|
1645
|
+
]
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function updateResourcePreview(env) {
|
|
1650
|
+
const resources = getResourceNames(env);
|
|
1651
|
+
|
|
1652
|
+
const d1List = document.getElementById('preview-d1');
|
|
1653
|
+
const kvList = document.getElementById('preview-kv');
|
|
1654
|
+
const keysList = document.getElementById('preview-keys');
|
|
1655
|
+
|
|
1656
|
+
d1List.innerHTML = '';
|
|
1657
|
+
kvList.innerHTML = '';
|
|
1658
|
+
keysList.innerHTML = '';
|
|
1659
|
+
|
|
1660
|
+
resources.d1.forEach(name => {
|
|
1661
|
+
const li = document.createElement('li');
|
|
1662
|
+
li.textContent = name;
|
|
1663
|
+
d1List.appendChild(li);
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
resources.kv.forEach(name => {
|
|
1667
|
+
const li = document.createElement('li');
|
|
1668
|
+
li.textContent = name;
|
|
1669
|
+
kvList.appendChild(li);
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
resources.keys.forEach(name => {
|
|
1673
|
+
const li = document.createElement('li');
|
|
1674
|
+
li.textContent = name;
|
|
1675
|
+
keysList.appendChild(li);
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// Update provision button state based on completion status
|
|
1680
|
+
function updateProvisionButtons() {
|
|
1681
|
+
const btnProvision = document.getElementById('btn-provision');
|
|
1682
|
+
const btnGotoDeploy = document.getElementById('btn-goto-deploy');
|
|
1683
|
+
|
|
1684
|
+
if (provisioningCompleted) {
|
|
1685
|
+
btnProvision.textContent = 'Re-provision (Delete & Create)';
|
|
1686
|
+
btnProvision.disabled = false;
|
|
1687
|
+
btnGotoDeploy.classList.remove('hidden');
|
|
1688
|
+
} else {
|
|
1689
|
+
btnProvision.textContent = 'Create Resources';
|
|
1690
|
+
btnProvision.disabled = false;
|
|
1691
|
+
btnGotoDeploy.classList.add('hidden');
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// =============================================================================
|
|
1696
|
+
// Environment Management
|
|
1697
|
+
// =============================================================================
|
|
1698
|
+
|
|
1699
|
+
// Menu handler for environment management
|
|
1700
|
+
document.getElementById('menu-manage-env').addEventListener('click', () => {
|
|
1701
|
+
loadEnvironments();
|
|
1702
|
+
showSection('envList');
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
// Load environments
|
|
1706
|
+
async function loadEnvironments() {
|
|
1707
|
+
const status = document.getElementById('env-list-status');
|
|
1708
|
+
const loading = document.getElementById('env-list-loading');
|
|
1709
|
+
const content = document.getElementById('env-list-content');
|
|
1710
|
+
const output = document.getElementById('env-scan-output');
|
|
1711
|
+
const noEnvsMessage = document.getElementById('no-envs-message');
|
|
1712
|
+
|
|
1713
|
+
status.textContent = 'Scanning...';
|
|
1714
|
+
status.className = 'status-badge status-running';
|
|
1715
|
+
loading.classList.remove('hidden');
|
|
1716
|
+
content.classList.add('hidden');
|
|
1717
|
+
output.textContent = '';
|
|
1718
|
+
|
|
1719
|
+
// Poll for progress
|
|
1720
|
+
let lastProgressLength = 0;
|
|
1721
|
+
const pollInterval = setInterval(async () => {
|
|
1722
|
+
try {
|
|
1723
|
+
const statusResult = await api('/deploy/status');
|
|
1724
|
+
if (statusResult.progress && statusResult.progress.length > lastProgressLength) {
|
|
1725
|
+
const newMessages = statusResult.progress.slice(lastProgressLength);
|
|
1726
|
+
newMessages.forEach(msg => {
|
|
1727
|
+
output.textContent += msg + '\\n';
|
|
1728
|
+
});
|
|
1729
|
+
lastProgressLength = statusResult.progress.length;
|
|
1730
|
+
}
|
|
1731
|
+
} catch (e) {}
|
|
1732
|
+
}, 500);
|
|
1733
|
+
|
|
1734
|
+
try {
|
|
1735
|
+
const result = await api('/environments');
|
|
1736
|
+
clearInterval(pollInterval);
|
|
1737
|
+
|
|
1738
|
+
if (result.success) {
|
|
1739
|
+
detectedEnvironments = result.environments || [];
|
|
1740
|
+
|
|
1741
|
+
status.textContent = detectedEnvironments.length + ' found';
|
|
1742
|
+
status.className = 'status-badge status-success';
|
|
1743
|
+
loading.classList.add('hidden');
|
|
1744
|
+
content.classList.remove('hidden');
|
|
1745
|
+
|
|
1746
|
+
renderEnvironmentCards();
|
|
1747
|
+
} else {
|
|
1748
|
+
throw new Error(result.error);
|
|
1749
|
+
}
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
clearInterval(pollInterval);
|
|
1752
|
+
status.textContent = 'Error';
|
|
1753
|
+
status.className = 'status-badge status-error';
|
|
1754
|
+
output.textContent += '\\n❌ Error: ' + error.message;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// Render environment cards
|
|
1759
|
+
function renderEnvironmentCards() {
|
|
1760
|
+
const container = document.getElementById('env-cards');
|
|
1761
|
+
const noEnvsMessage = document.getElementById('no-envs-message');
|
|
1762
|
+
|
|
1763
|
+
container.innerHTML = '';
|
|
1764
|
+
|
|
1765
|
+
if (detectedEnvironments.length === 0) {
|
|
1766
|
+
noEnvsMessage.classList.remove('hidden');
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
noEnvsMessage.classList.add('hidden');
|
|
1771
|
+
|
|
1772
|
+
for (const env of detectedEnvironments) {
|
|
1773
|
+
const card = document.createElement('div');
|
|
1774
|
+
card.className = 'env-card';
|
|
1775
|
+
|
|
1776
|
+
const info = document.createElement('div');
|
|
1777
|
+
info.className = 'env-card-info';
|
|
1778
|
+
|
|
1779
|
+
const name = document.createElement('div');
|
|
1780
|
+
name.className = 'env-card-name';
|
|
1781
|
+
name.textContent = env.env;
|
|
1782
|
+
info.appendChild(name);
|
|
1783
|
+
|
|
1784
|
+
const stats = document.createElement('div');
|
|
1785
|
+
stats.className = 'env-card-stats';
|
|
1786
|
+
|
|
1787
|
+
const statItems = [
|
|
1788
|
+
{ icon: '🔧', label: 'Workers', count: env.workers.length },
|
|
1789
|
+
{ icon: '📊', label: 'D1', count: env.d1.length },
|
|
1790
|
+
{ icon: '🗄️', label: 'KV', count: env.kv.length },
|
|
1791
|
+
{ icon: '📨', label: 'Queues', count: env.queues.length },
|
|
1792
|
+
{ icon: '📁', label: 'R2', count: env.r2.length },
|
|
1793
|
+
];
|
|
1794
|
+
|
|
1795
|
+
for (const item of statItems) {
|
|
1796
|
+
if (item.count > 0) {
|
|
1797
|
+
const stat = document.createElement('span');
|
|
1798
|
+
stat.className = 'env-card-stat';
|
|
1799
|
+
stat.textContent = item.icon + ' ' + item.count + ' ' + item.label;
|
|
1800
|
+
stats.appendChild(stat);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
info.appendChild(stats);
|
|
1805
|
+
card.appendChild(info);
|
|
1806
|
+
|
|
1807
|
+
const actions = document.createElement('div');
|
|
1808
|
+
actions.className = 'env-card-actions';
|
|
1809
|
+
|
|
1810
|
+
const detailBtn = document.createElement('button');
|
|
1811
|
+
detailBtn.className = 'btn-info';
|
|
1812
|
+
detailBtn.textContent = '📋 Details';
|
|
1813
|
+
detailBtn.addEventListener('click', () => showEnvDetail(env));
|
|
1814
|
+
actions.appendChild(detailBtn);
|
|
1815
|
+
|
|
1816
|
+
card.appendChild(actions);
|
|
1817
|
+
container.appendChild(card);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// Show environment details
|
|
1822
|
+
function showEnvDetail(env) {
|
|
1823
|
+
selectedEnvForDetail = env;
|
|
1824
|
+
|
|
1825
|
+
document.getElementById('detail-env-name').textContent = env.env;
|
|
1826
|
+
|
|
1827
|
+
// Render resource lists with loading state
|
|
1828
|
+
renderResourceList('detail-workers-list', 'detail-workers-count', env.workers, 'name', 'worker');
|
|
1829
|
+
renderResourceList('detail-d1-list', 'detail-d1-count', env.d1, 'name', 'd1');
|
|
1830
|
+
renderResourceList('detail-kv-list', 'detail-kv-count', env.kv, 'name', 'kv');
|
|
1831
|
+
renderResourceList('detail-queues-list', 'detail-queues-count', env.queues, 'name', 'queue');
|
|
1832
|
+
renderResourceList('detail-r2-list', 'detail-r2-count', env.r2, 'name', 'r2');
|
|
1833
|
+
|
|
1834
|
+
// Hide empty sections
|
|
1835
|
+
document.getElementById('detail-queues-section').style.display = env.queues.length === 0 ? 'none' : 'block';
|
|
1836
|
+
document.getElementById('detail-r2-section').style.display = env.r2.length === 0 ? 'none' : 'block';
|
|
1837
|
+
|
|
1838
|
+
showSection('envDetail');
|
|
1839
|
+
|
|
1840
|
+
// Load details asynchronously
|
|
1841
|
+
loadResourceDetails(env);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// Helper to render resource list
|
|
1845
|
+
function renderResourceList(listId, countId, resources, nameKey, resourceType) {
|
|
1846
|
+
const list = document.getElementById(listId);
|
|
1847
|
+
const count = document.getElementById(countId);
|
|
1848
|
+
|
|
1849
|
+
list.innerHTML = '';
|
|
1850
|
+
count.textContent = '(' + resources.length + ')';
|
|
1851
|
+
|
|
1852
|
+
if (resources.length === 0) {
|
|
1853
|
+
const empty = document.createElement('div');
|
|
1854
|
+
empty.className = 'resource-empty';
|
|
1855
|
+
empty.textContent = 'None';
|
|
1856
|
+
list.appendChild(empty);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
for (const resource of resources) {
|
|
1861
|
+
const item = document.createElement('div');
|
|
1862
|
+
item.className = 'resource-item';
|
|
1863
|
+
item.id = 'resource-' + resourceType + '-' + (resource.name || resource.title || '').replace(/[^a-zA-Z0-9-]/g, '_');
|
|
1864
|
+
|
|
1865
|
+
const nameDiv = document.createElement('div');
|
|
1866
|
+
nameDiv.className = 'resource-item-name';
|
|
1867
|
+
nameDiv.textContent = resource[nameKey] || resource.title || resource.id || 'Unknown';
|
|
1868
|
+
item.appendChild(nameDiv);
|
|
1869
|
+
|
|
1870
|
+
// Add loading placeholder for D1 and Workers
|
|
1871
|
+
if (resourceType === 'd1' || resourceType === 'worker') {
|
|
1872
|
+
const detailsDiv = document.createElement('div');
|
|
1873
|
+
detailsDiv.className = 'resource-item-details resource-item-loading';
|
|
1874
|
+
detailsDiv.textContent = 'Loading...';
|
|
1875
|
+
item.appendChild(detailsDiv);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
list.appendChild(item);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// Load resource details asynchronously
|
|
1883
|
+
async function loadResourceDetails(env) {
|
|
1884
|
+
// Load D1 and Worker details in parallel
|
|
1885
|
+
const d1Promises = env.d1.map(db => loadD1Details(db.name));
|
|
1886
|
+
const workerPromises = env.workers.map(w => loadWorkerDetails(w.name));
|
|
1887
|
+
|
|
1888
|
+
// Wait for all to complete (don't block on errors)
|
|
1889
|
+
await Promise.allSettled([...d1Promises, ...workerPromises]);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// Load D1 database details
|
|
1893
|
+
async function loadD1Details(name) {
|
|
1894
|
+
try {
|
|
1895
|
+
const result = await fetch('/api/d1/' + encodeURIComponent(name) + '/info').then(r => r.json());
|
|
1896
|
+
|
|
1897
|
+
const itemId = 'resource-d1-' + name.replace(/[^a-zA-Z0-9-]/g, '_');
|
|
1898
|
+
const item = document.getElementById(itemId);
|
|
1899
|
+
if (!item) return;
|
|
1900
|
+
|
|
1901
|
+
const detailsDiv = item.querySelector('.resource-item-details');
|
|
1902
|
+
if (!detailsDiv) return;
|
|
1903
|
+
|
|
1904
|
+
if (result.success && result.info) {
|
|
1905
|
+
const info = result.info;
|
|
1906
|
+
detailsDiv.className = 'resource-item-details';
|
|
1907
|
+
detailsDiv.innerHTML = '';
|
|
1908
|
+
|
|
1909
|
+
if (info.databaseSize) {
|
|
1910
|
+
const span = document.createElement('span');
|
|
1911
|
+
span.textContent = '📦 ' + info.databaseSize;
|
|
1912
|
+
detailsDiv.appendChild(span);
|
|
1913
|
+
}
|
|
1914
|
+
if (info.region) {
|
|
1915
|
+
const span = document.createElement('span');
|
|
1916
|
+
span.textContent = '🌍 ' + info.region;
|
|
1917
|
+
detailsDiv.appendChild(span);
|
|
1918
|
+
}
|
|
1919
|
+
if (info.createdAt) {
|
|
1920
|
+
const span = document.createElement('span');
|
|
1921
|
+
span.textContent = '📅 ' + formatDate(info.createdAt);
|
|
1922
|
+
detailsDiv.appendChild(span);
|
|
1923
|
+
}
|
|
1924
|
+
} else {
|
|
1925
|
+
detailsDiv.className = 'resource-item-details resource-item-error';
|
|
1926
|
+
detailsDiv.textContent = 'Failed to load';
|
|
1927
|
+
}
|
|
1928
|
+
} catch (e) {
|
|
1929
|
+
console.error('Failed to load D1 details:', e);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// Load Worker deployment details
|
|
1934
|
+
async function loadWorkerDetails(name) {
|
|
1935
|
+
try {
|
|
1936
|
+
const result = await fetch('/api/worker/' + encodeURIComponent(name) + '/deployments').then(r => r.json());
|
|
1937
|
+
|
|
1938
|
+
const itemId = 'resource-worker-' + name.replace(/[^a-zA-Z0-9-]/g, '_');
|
|
1939
|
+
const item = document.getElementById(itemId);
|
|
1940
|
+
if (!item) return;
|
|
1941
|
+
|
|
1942
|
+
const detailsDiv = item.querySelector('.resource-item-details');
|
|
1943
|
+
if (!detailsDiv) return;
|
|
1944
|
+
|
|
1945
|
+
if (result.success && result.deployments) {
|
|
1946
|
+
const info = result.deployments;
|
|
1947
|
+
detailsDiv.className = 'resource-item-details';
|
|
1948
|
+
detailsDiv.innerHTML = '';
|
|
1949
|
+
|
|
1950
|
+
if (!info.exists) {
|
|
1951
|
+
detailsDiv.className = 'resource-item-details resource-item-not-deployed';
|
|
1952
|
+
detailsDiv.textContent = '⚠️ Not deployed';
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
if (info.lastDeployedAt) {
|
|
1957
|
+
const span = document.createElement('span');
|
|
1958
|
+
span.textContent = '🚀 ' + formatDate(info.lastDeployedAt);
|
|
1959
|
+
detailsDiv.appendChild(span);
|
|
1960
|
+
}
|
|
1961
|
+
if (info.author) {
|
|
1962
|
+
const span = document.createElement('span');
|
|
1963
|
+
span.textContent = '👤 ' + info.author;
|
|
1964
|
+
detailsDiv.appendChild(span);
|
|
1965
|
+
}
|
|
1966
|
+
if (info.versionId) {
|
|
1967
|
+
const span = document.createElement('span');
|
|
1968
|
+
span.textContent = '🏷️ ' + info.versionId.substring(0, 8) + '...';
|
|
1969
|
+
detailsDiv.appendChild(span);
|
|
1970
|
+
}
|
|
1971
|
+
} else {
|
|
1972
|
+
detailsDiv.className = 'resource-item-details resource-item-not-deployed';
|
|
1973
|
+
detailsDiv.textContent = '⚠️ Not deployed';
|
|
1974
|
+
}
|
|
1975
|
+
} catch (e) {
|
|
1976
|
+
console.error('Failed to load Worker details:', e);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
// Format ISO date to readable format with timezone
|
|
1981
|
+
function formatDate(isoString) {
|
|
1982
|
+
try {
|
|
1983
|
+
const date = new Date(isoString);
|
|
1984
|
+
const dateStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
1985
|
+
// Get timezone abbreviation
|
|
1986
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1987
|
+
const tzAbbr = date.toLocaleTimeString('en-US', { timeZoneName: 'short' }).split(' ').pop();
|
|
1988
|
+
return dateStr + ' (' + tzAbbr + ')';
|
|
1989
|
+
} catch {
|
|
1990
|
+
return isoString;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// Show delete confirmation
|
|
1995
|
+
function showDeleteConfirmation(env) {
|
|
1996
|
+
selectedEnvForDelete = env;
|
|
1997
|
+
|
|
1998
|
+
document.getElementById('delete-env-name').textContent = env.env;
|
|
1999
|
+
document.getElementById('delete-workers-count').textContent = '(' + env.workers.length + ' workers)';
|
|
2000
|
+
document.getElementById('delete-d1-count').textContent = '(' + env.d1.length + ' databases)';
|
|
2001
|
+
document.getElementById('delete-kv-count').textContent = '(' + env.kv.length + ' namespaces)';
|
|
2002
|
+
document.getElementById('delete-queues-count').textContent = '(' + env.queues.length + ' queues)';
|
|
2003
|
+
document.getElementById('delete-r2-count').textContent = '(' + env.r2.length + ' buckets)';
|
|
2004
|
+
|
|
2005
|
+
// Reset checkboxes
|
|
2006
|
+
document.getElementById('delete-workers').checked = true;
|
|
2007
|
+
document.getElementById('delete-d1').checked = true;
|
|
2008
|
+
document.getElementById('delete-kv').checked = true;
|
|
2009
|
+
document.getElementById('delete-queues').checked = true;
|
|
2010
|
+
document.getElementById('delete-r2').checked = true;
|
|
2011
|
+
|
|
2012
|
+
// Reset UI state
|
|
2013
|
+
document.getElementById('delete-log').classList.add('hidden');
|
|
2014
|
+
document.getElementById('delete-result').classList.add('hidden');
|
|
2015
|
+
document.getElementById('delete-result').innerHTML = '';
|
|
2016
|
+
document.getElementById('btn-confirm-delete').disabled = false;
|
|
2017
|
+
|
|
2018
|
+
showSection('envDelete');
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// Back buttons for environment management
|
|
2022
|
+
document.getElementById('btn-back-env-list').addEventListener('click', () => {
|
|
2023
|
+
showSection('topMenu');
|
|
2024
|
+
});
|
|
2025
|
+
|
|
2026
|
+
document.getElementById('btn-refresh-env-list').addEventListener('click', () => {
|
|
2027
|
+
loadEnvironments();
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
document.getElementById('btn-back-env-detail').addEventListener('click', () => {
|
|
2031
|
+
showSection('envList');
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
document.getElementById('btn-delete-from-detail').addEventListener('click', () => {
|
|
2035
|
+
if (selectedEnvForDetail) {
|
|
2036
|
+
showDeleteConfirmation(selectedEnvForDetail);
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
|
|
2040
|
+
document.getElementById('btn-back-env-delete').addEventListener('click', () => {
|
|
2041
|
+
// Go back to detail view if we came from there
|
|
2042
|
+
if (selectedEnvForDetail) {
|
|
2043
|
+
showSection('envDetail');
|
|
2044
|
+
} else {
|
|
2045
|
+
showSection('envList');
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
// Delete environment
|
|
2050
|
+
document.getElementById('btn-confirm-delete').addEventListener('click', async () => {
|
|
2051
|
+
if (!selectedEnvForDelete) return;
|
|
2052
|
+
|
|
2053
|
+
const btn = document.getElementById('btn-confirm-delete');
|
|
2054
|
+
const log = document.getElementById('delete-log');
|
|
2055
|
+
const output = document.getElementById('delete-output');
|
|
2056
|
+
const result = document.getElementById('delete-result');
|
|
2057
|
+
|
|
2058
|
+
btn.disabled = true;
|
|
2059
|
+
log.classList.remove('hidden');
|
|
2060
|
+
result.classList.add('hidden');
|
|
2061
|
+
output.textContent = '';
|
|
2062
|
+
|
|
2063
|
+
const deleteOptions = {
|
|
2064
|
+
deleteWorkers: document.getElementById('delete-workers').checked,
|
|
2065
|
+
deleteD1: document.getElementById('delete-d1').checked,
|
|
2066
|
+
deleteKV: document.getElementById('delete-kv').checked,
|
|
2067
|
+
deleteQueues: document.getElementById('delete-queues').checked,
|
|
2068
|
+
deleteR2: document.getElementById('delete-r2').checked,
|
|
2069
|
+
};
|
|
2070
|
+
|
|
2071
|
+
// Poll for progress
|
|
2072
|
+
let lastProgressLength = 0;
|
|
2073
|
+
const pollInterval = setInterval(async () => {
|
|
2074
|
+
try {
|
|
2075
|
+
const statusResult = await api('/deploy/status');
|
|
2076
|
+
if (statusResult.progress && statusResult.progress.length > lastProgressLength) {
|
|
2077
|
+
const newMessages = statusResult.progress.slice(lastProgressLength);
|
|
2078
|
+
newMessages.forEach(msg => {
|
|
2079
|
+
output.textContent += msg + '\\n';
|
|
2080
|
+
});
|
|
2081
|
+
lastProgressLength = statusResult.progress.length;
|
|
2082
|
+
log.scrollTop = log.scrollHeight;
|
|
2083
|
+
}
|
|
2084
|
+
} catch (e) {}
|
|
2085
|
+
}, 500);
|
|
2086
|
+
|
|
2087
|
+
try {
|
|
2088
|
+
const deleteResult = await api('/environments/' + selectedEnvForDelete.env + '/delete', {
|
|
2089
|
+
method: 'POST',
|
|
2090
|
+
body: deleteOptions,
|
|
2091
|
+
});
|
|
2092
|
+
|
|
2093
|
+
clearInterval(pollInterval);
|
|
2094
|
+
|
|
2095
|
+
// Show final progress
|
|
2096
|
+
if (deleteResult.progress) {
|
|
2097
|
+
output.textContent = deleteResult.progress.join('\\n');
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
result.classList.remove('hidden');
|
|
2101
|
+
|
|
2102
|
+
if (deleteResult.success) {
|
|
2103
|
+
result.innerHTML = '<div class="alert alert-success">✅ Environment deleted successfully!</div>';
|
|
2104
|
+
|
|
2105
|
+
// Refresh environment list after a short delay
|
|
2106
|
+
setTimeout(() => {
|
|
2107
|
+
loadEnvironments();
|
|
2108
|
+
showSection('envList');
|
|
2109
|
+
}, 2000);
|
|
2110
|
+
} else {
|
|
2111
|
+
result.innerHTML = '<div class="alert alert-error">❌ Some errors occurred: ' + (deleteResult.errors || []).join(', ') + '</div>';
|
|
2112
|
+
btn.disabled = false;
|
|
2113
|
+
}
|
|
2114
|
+
} catch (error) {
|
|
2115
|
+
clearInterval(pollInterval);
|
|
2116
|
+
result.classList.remove('hidden');
|
|
2117
|
+
result.innerHTML = '<div class="alert alert-error">❌ Error: ' + error.message + '</div>';
|
|
2118
|
+
btn.disabled = false;
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
|
|
759
2122
|
// Initialize
|
|
760
|
-
|
|
2123
|
+
if (MANAGE_ONLY) {
|
|
2124
|
+
// Skip prerequisites UI and go directly to environment management
|
|
2125
|
+
// Prerequisites were already checked by CLI
|
|
2126
|
+
loadEnvironments();
|
|
2127
|
+
showSection('envList');
|
|
2128
|
+
} else {
|
|
2129
|
+
checkPrerequisites();
|
|
2130
|
+
}
|
|
761
2131
|
</script>
|
|
762
2132
|
</body>
|
|
763
2133
|
</html>`;
|