@openqa/cli 2.1.0 → 2.1.2
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 +26 -0
- package/dist/agent/index-v2.js +277 -92
- package/dist/agent/index-v2.js.map +1 -1
- package/dist/agent/index.js +109 -118
- package/dist/agent/index.js.map +1 -1
- package/dist/cli/daemon.js +1045 -635
- package/dist/cli/dashboard.html.js +53 -16
- package/dist/cli/env.html.js +717 -529
- package/dist/cli/index.js +762 -537
- package/dist/cli/server.js +762 -537
- package/package.json +1 -1
package/dist/cli/server.js
CHANGED
|
@@ -2589,7 +2589,7 @@ function getDashboardHTML() {
|
|
|
2589
2589
|
.logo-mark {
|
|
2590
2590
|
width: 34px;
|
|
2591
2591
|
height: 34px;
|
|
2592
|
-
background:
|
|
2592
|
+
background: transparent;
|
|
2593
2593
|
border-radius: 8px;
|
|
2594
2594
|
display: grid;
|
|
2595
2595
|
place-items: center;
|
|
@@ -3211,7 +3211,9 @@ function getDashboardHTML() {
|
|
|
3211
3211
|
<!-- Sidebar -->
|
|
3212
3212
|
<aside>
|
|
3213
3213
|
<div class="logo">
|
|
3214
|
-
<div class="logo-mark"
|
|
3214
|
+
<div class="logo-mark">
|
|
3215
|
+
<img src="https://openqa.orkajs.com/_next/image?url=https%3A%2F%2Forkajs.com%2Floutre-orka-qa.png&w=256&q=75" alt="OpenQA Logo" style="width: 40px; height: 40px;">
|
|
3216
|
+
</div>
|
|
3215
3217
|
<div>
|
|
3216
3218
|
<div class="logo-name">OpenQA</div>
|
|
3217
3219
|
<div class="logo-version">v2.1.0 \xB7 OSS</div>
|
|
@@ -3221,42 +3223,69 @@ function getDashboardHTML() {
|
|
|
3221
3223
|
<div class="nav-section">
|
|
3222
3224
|
<div class="nav-label">Overview</div>
|
|
3223
3225
|
<a class="nav-item active" href="/">
|
|
3224
|
-
<span class="icon"
|
|
3226
|
+
<span class="icon">
|
|
3227
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gauge-icon lucide-gauge"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>
|
|
3228
|
+
</span> Dashboard
|
|
3225
3229
|
</a>
|
|
3226
3230
|
<a class="nav-item" href="/kanban">
|
|
3227
|
-
<span class="icon"
|
|
3231
|
+
<span class="icon">
|
|
3232
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-dashed-kanban-icon lucide-square-dashed-kanban"><path d="M8 7v7"/><path d="M12 7v4"/><path d="M16 7v9"/><path d="M5 3a2 2 0 0 0-2 2"/><path d="M9 3h1"/><path d="M14 3h1"/><path d="M19 3a2 2 0 0 1 2 2"/><path d="M21 9v1"/><path d="M21 14v1"/><path d="M21 19a2 2 0 0 1-2 2"/><path d="M14 21h1"/><path d="M9 21h1"/><path d="M5 21a2 2 0 0 1-2-2"/><path d="M3 14v1"/><path d="M3 9v1"/></svg>
|
|
3233
|
+
</span> Kanban
|
|
3228
3234
|
<span class="badge" id="kanban-count">0</span>
|
|
3229
3235
|
</a>
|
|
3230
3236
|
|
|
3231
3237
|
<div class="nav-label">Agents</div>
|
|
3232
3238
|
<a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('agents-table')">
|
|
3233
|
-
<span class="icon"
|
|
3239
|
+
<span class="icon">
|
|
3240
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-activity-icon lucide-activity"><path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/></svg>
|
|
3241
|
+
</span> Active Agents
|
|
3234
3242
|
</a>
|
|
3235
3243
|
<a class="nav-item" href="javascript:void(0)" onclick="switchAgentTab('specialists'); scrollToSection('agents-table')">
|
|
3236
|
-
<span class="icon"
|
|
3244
|
+
<span class="icon">
|
|
3245
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hat-glasses-icon lucide-hat-glasses"><path d="M14 18a2 2 0 0 0-4 0"/><path d="m19 11-2.11-6.657a2 2 0 0 0-2.752-1.148l-1.276.61A2 2 0 0 1 12 4H8.5a2 2 0 0 0-1.925 1.456L5 11"/><path d="M2 11h20"/><circle cx="17" cy="18" r="3"/><circle cx="7" cy="18" r="3"/></svg>
|
|
3246
|
+
</span> Specialists
|
|
3237
3247
|
</a>
|
|
3238
3248
|
<a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('interventions-panel')">
|
|
3239
|
-
<span class="icon"
|
|
3249
|
+
<span class="icon">
|
|
3250
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user-cog-icon lucide-user-cog"><path d="M10 15H6a4 4 0 0 0-4 4v2"/><path d="m14.305 16.53.923-.382"/><path d="m15.228 13.852-.923-.383"/><path d="m16.852 12.228-.383-.923"/><path d="m16.852 17.772-.383.924"/><path d="m19.148 12.228.383-.923"/><path d="m19.53 18.696-.382-.924"/><path d="m20.772 13.852.924-.383"/><path d="m20.772 16.148.924.383"/><circle cx="18" cy="15" r="3"/><circle cx="9" cy="7" r="4"/></svg>
|
|
3251
|
+
</span> Interventions
|
|
3240
3252
|
<span class="badge" id="intervention-count" style="background: var(--red);">0</span>
|
|
3241
3253
|
</a>
|
|
3242
3254
|
|
|
3243
3255
|
<div class="nav-label">Analysis</div>
|
|
3244
3256
|
<a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('issues-panel')">
|
|
3245
|
-
<span class="icon"
|
|
3257
|
+
<span class="icon">
|
|
3258
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug-play-icon lucide-bug-play"><path d="M10 19.655A6 6 0 0 1 6 14v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 3.97"/><path d="M14 15.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"/>
|
|
3259
|
+
<path d="M14.12 3.88 16 2"/>
|
|
3260
|
+
<path d="M21 5a4 4 0 0 1-3.55 3.97"/>
|
|
3261
|
+
<path d="M3 21a4 4 0 0 1 3.81-4"/>
|
|
3262
|
+
<path d="M3 5a4 4 0 0 0 3.55 3.97"/>
|
|
3263
|
+
<path d="M6 13H2"/><path d="m8 2 1.88 1.88"/>
|
|
3264
|
+
<path d="M9 7.13V6a3 3 0 1 1 6 0v1.13"/>
|
|
3265
|
+
</svg>
|
|
3266
|
+
</span> Bug Reports
|
|
3246
3267
|
</a>
|
|
3247
3268
|
<a class="nav-item" href="javascript:void(0)" onclick="switchChartTab('performance'); scrollToSection('chart-performance')">
|
|
3248
|
-
<span class="icon"
|
|
3269
|
+
<span class="icon">
|
|
3270
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chart-spline-icon lucide-chart-spline"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16c.5-2 1.5-7 4-7 2 0 2 3 4 3 2.5 0 4.5-5 5-7"/></svg>
|
|
3271
|
+
</span> Performance
|
|
3249
3272
|
</a>
|
|
3250
3273
|
<a class="nav-item" href="javascript:void(0)" onclick="scrollToSection('activity-list')">
|
|
3251
|
-
<span class="icon"
|
|
3274
|
+
<span class="icon">
|
|
3275
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scroll-text-icon lucide-scroll-text"><path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/></svg>
|
|
3276
|
+
</span> Logs
|
|
3252
3277
|
</a>
|
|
3253
3278
|
|
|
3254
3279
|
<div class="nav-label">System</div>
|
|
3255
3280
|
<a class="nav-item" href="/config">
|
|
3256
|
-
<span class="icon"
|
|
3281
|
+
<span class="icon">
|
|
3282
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-columns3-cog-icon lucide-columns-3-cog"><path d="M10.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.5"/><path d="m14.3 19.6 1-.4"/><path d="M15 3v7.5"/><path d="m15.2 16.9-.9-.3"/><path d="m16.6 21.7.3-.9"/><path d="m16.8 15.3-.4-1"/><path d="m19.1 15.2.3-.9"/><path d="m19.6 21.7-.4-1"/><path d="m20.7 16.8 1-.4"/><path d="m21.7 19.4-.9-.3"/><path d="M9 3v18"/><circle cx="18" cy="18" r="3"/></svg>
|
|
3283
|
+
</span> Config
|
|
3257
3284
|
</a>
|
|
3258
3285
|
<a class="nav-item" href="/config/env">
|
|
3259
|
-
<span class="icon"
|
|
3286
|
+
<span class="icon">
|
|
3287
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-columns3-cog-icon lucide-columns-3-cog"><path d="M10.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.5"/><path d="m14.3 19.6 1-.4"/><path d="M15 3v7.5"/><path d="m15.2 16.9-.9-.3"/><path d="m16.6 21.7.3-.9"/><path d="m16.8 15.3-.4-1"/><path d="m19.1 15.2.3-.9"/><path d="m19.6 21.7-.4-1"/><path d="m20.7 16.8 1-.4"/><path d="m21.7 19.4-.9-.3"/><path d="M9 3v18"/><circle cx="18" cy="18" r="3"/></svg>
|
|
3288
|
+
</span> Environment
|
|
3260
3289
|
</a>
|
|
3261
3290
|
</div>
|
|
3262
3291
|
|
|
@@ -3288,7 +3317,9 @@ function getDashboardHTML() {
|
|
|
3288
3317
|
<div class="metric-card">
|
|
3289
3318
|
<div class="metric-header">
|
|
3290
3319
|
<div class="metric-label">Active Agents</div>
|
|
3291
|
-
<div class="metric-icon"
|
|
3320
|
+
<div class="metric-icon">
|
|
3321
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-terminal-icon lucide-square-terminal"><path d="m7 11 2-2-2-2"/><path d="M11 13h4"/><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/></svg>
|
|
3322
|
+
</div>
|
|
3292
3323
|
</div>
|
|
3293
3324
|
<div class="metric-value" id="active-agents">0</div>
|
|
3294
3325
|
<div class="metric-change positive" id="agents-change">\u2191 0 from last hour</div>
|
|
@@ -3296,7 +3327,9 @@ function getDashboardHTML() {
|
|
|
3296
3327
|
<div class="metric-card">
|
|
3297
3328
|
<div class="metric-header">
|
|
3298
3329
|
<div class="metric-label">Total Actions</div>
|
|
3299
|
-
<div class="metric-icon"
|
|
3330
|
+
<div class="metric-icon">
|
|
3331
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-todo-icon lucide-list-todo"><path d="M13 5h8"/><path d="M13 12h8"/><path d="M13 19h8"/><path d="m3 17 2 2 4-4"/><rect x="3" y="4" width="6" height="6" rx="1"/></svg>
|
|
3332
|
+
</div>
|
|
3300
3333
|
</div>
|
|
3301
3334
|
<div class="metric-value" id="total-actions">0</div>
|
|
3302
3335
|
<div class="metric-change positive" id="actions-change">\u2191 0% this session</div>
|
|
@@ -3304,7 +3337,9 @@ function getDashboardHTML() {
|
|
|
3304
3337
|
<div class="metric-card">
|
|
3305
3338
|
<div class="metric-header">
|
|
3306
3339
|
<div class="metric-label">Bugs Found</div>
|
|
3307
|
-
<div class="metric-icon"
|
|
3340
|
+
<div class="metric-icon">
|
|
3341
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-todo-icon lucide-list-todo"><path d="M13 5h8"/><path d="M13 12h8"/><path d="M13 19h8"/><path d="m3 17 2 2 4-4"/><rect x="3" y="4" width="6" height="6" rx="1"/></svg>
|
|
3342
|
+
</div>
|
|
3308
3343
|
</div>
|
|
3309
3344
|
<div class="metric-value" id="bugs-found">0</div>
|
|
3310
3345
|
<div class="metric-change negative" id="bugs-change">\u2193 0 from yesterday</div>
|
|
@@ -3312,7 +3347,9 @@ function getDashboardHTML() {
|
|
|
3312
3347
|
<div class="metric-card">
|
|
3313
3348
|
<div class="metric-header">
|
|
3314
3349
|
<div class="metric-label">Success Rate</div>
|
|
3315
|
-
<div class="metric-icon"
|
|
3350
|
+
<div class="metric-icon">
|
|
3351
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-check-icon lucide-cloud-check"><path d="m17 15-5.5 5.5L9 18"/><path d="M5.516 16.07A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 3.501 7.327"/></svg>
|
|
3352
|
+
</div>
|
|
3316
3353
|
</div>
|
|
3317
3354
|
<div class="metric-value" id="success-rate">\u2014</div>
|
|
3318
3355
|
<div class="metric-change positive" id="rate-change">\u2191 0 pts improvement</div>
|
|
@@ -5551,672 +5588,860 @@ function getEnvHTML() {
|
|
|
5551
5588
|
<head>
|
|
5552
5589
|
<meta charset="UTF-8">
|
|
5553
5590
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5554
|
-
<title>
|
|
5591
|
+
<title>OpenQA \u2014 Environment</title>
|
|
5592
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
5593
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;600;700;800&display=swap" rel="stylesheet">
|
|
5555
5594
|
<style>
|
|
5556
|
-
|
|
5557
|
-
|
|
5595
|
+
:root {
|
|
5596
|
+
--bg: #080b10;
|
|
5597
|
+
--surface: #0d1117;
|
|
5598
|
+
--panel: #111720;
|
|
5599
|
+
--border: rgba(255,255,255,0.06);
|
|
5600
|
+
--border-hi: rgba(255,255,255,0.12);
|
|
5601
|
+
--accent: #f97316;
|
|
5602
|
+
--accent-lo: rgba(249,115,22,0.08);
|
|
5603
|
+
--accent-md: rgba(249,115,22,0.18);
|
|
5604
|
+
--green: #22c55e;
|
|
5605
|
+
--green-lo: rgba(34,197,94,0.08);
|
|
5606
|
+
--red: #ef4444;
|
|
5607
|
+
--red-lo: rgba(239,68,68,0.08);
|
|
5608
|
+
--amber: #f59e0b;
|
|
5609
|
+
--amber-lo: rgba(245,158,11,0.08);
|
|
5610
|
+
--blue: #38bdf8;
|
|
5611
|
+
--blue-lo: rgba(56,189,248,0.08);
|
|
5612
|
+
--text-1: #f1f5f9;
|
|
5613
|
+
--text-2: #8b98a8;
|
|
5614
|
+
--text-3: #4b5563;
|
|
5615
|
+
--mono: 'DM Mono', monospace;
|
|
5616
|
+
--sans: 'Syne', sans-serif;
|
|
5617
|
+
--radius: 10px;
|
|
5618
|
+
--radius-lg: 16px;
|
|
5619
|
+
}
|
|
5620
|
+
|
|
5621
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5622
|
+
|
|
5558
5623
|
body {
|
|
5559
|
-
font-family:
|
|
5560
|
-
background:
|
|
5624
|
+
font-family: var(--sans);
|
|
5625
|
+
background: var(--bg);
|
|
5626
|
+
color: var(--text-1);
|
|
5561
5627
|
min-height: 100vh;
|
|
5562
|
-
|
|
5628
|
+
overflow-x: hidden;
|
|
5563
5629
|
}
|
|
5564
5630
|
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5631
|
+
/* \u2500\u2500 Layout \u2500\u2500 */
|
|
5632
|
+
.shell {
|
|
5633
|
+
display: grid;
|
|
5634
|
+
grid-template-columns: 220px 1fr;
|
|
5635
|
+
min-height: 100vh;
|
|
5568
5636
|
}
|
|
5569
5637
|
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
border-radius: 12px;
|
|
5575
|
-
margin-bottom: 20px;
|
|
5576
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
5638
|
+
/* \u2500\u2500 Sidebar \u2500\u2500 */
|
|
5639
|
+
aside {
|
|
5640
|
+
background: var(--surface);
|
|
5641
|
+
border-right: 1px solid var(--border);
|
|
5577
5642
|
display: flex;
|
|
5578
|
-
|
|
5579
|
-
|
|
5643
|
+
flex-direction: column;
|
|
5644
|
+
padding: 28px 0;
|
|
5645
|
+
position: sticky;
|
|
5646
|
+
top: 0;
|
|
5647
|
+
height: 100vh;
|
|
5580
5648
|
}
|
|
5581
5649
|
|
|
5582
|
-
.
|
|
5583
|
-
font-size: 24px;
|
|
5584
|
-
color: #1a202c;
|
|
5650
|
+
.logo {
|
|
5585
5651
|
display: flex;
|
|
5586
5652
|
align-items: center;
|
|
5587
5653
|
gap: 10px;
|
|
5654
|
+
padding: 0 24px 32px;
|
|
5655
|
+
border-bottom: 1px solid var(--border);
|
|
5656
|
+
margin-bottom: 12px;
|
|
5588
5657
|
}
|
|
5589
5658
|
|
|
5590
|
-
.
|
|
5591
|
-
|
|
5592
|
-
|
|
5659
|
+
.logo-mark {
|
|
5660
|
+
width: 34px; height: 34px;
|
|
5661
|
+
background: var(--accent);
|
|
5662
|
+
border-radius: 8px;
|
|
5663
|
+
display: grid;
|
|
5664
|
+
place-items: center;
|
|
5665
|
+
font-size: 17px;
|
|
5666
|
+
font-weight: 800;
|
|
5667
|
+
color: #fff;
|
|
5593
5668
|
}
|
|
5594
5669
|
|
|
5595
|
-
.
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5670
|
+
.logo-name { font-weight: 800; font-size: 18px; letter-spacing: -0.5px; }
|
|
5671
|
+
.logo-version { font-family: var(--mono); font-size: 10px; color: var(--text-3); }
|
|
5672
|
+
|
|
5673
|
+
.nav-section { padding: 8px 12px; flex: 1; overflow-y: auto; }
|
|
5674
|
+
|
|
5675
|
+
.nav-label {
|
|
5676
|
+
font-family: var(--mono);
|
|
5677
|
+
font-size: 10px;
|
|
5678
|
+
color: var(--text-3);
|
|
5679
|
+
letter-spacing: 1.5px;
|
|
5680
|
+
text-transform: uppercase;
|
|
5681
|
+
padding: 0 12px;
|
|
5682
|
+
margin: 16px 0 6px;
|
|
5683
|
+
}
|
|
5684
|
+
|
|
5685
|
+
.nav-item {
|
|
5686
|
+
display: flex;
|
|
5687
|
+
align-items: center;
|
|
5688
|
+
gap: 10px;
|
|
5689
|
+
padding: 9px 12px;
|
|
5690
|
+
border-radius: var(--radius);
|
|
5691
|
+
color: var(--text-2);
|
|
5692
|
+
text-decoration: none;
|
|
5599
5693
|
font-size: 14px;
|
|
5600
5694
|
font-weight: 600;
|
|
5695
|
+
transition: all 0.15s ease;
|
|
5601
5696
|
cursor: pointer;
|
|
5602
|
-
transition: all 0.2s;
|
|
5603
|
-
text-decoration: none;
|
|
5604
|
-
display: inline-flex;
|
|
5605
|
-
align-items: center;
|
|
5606
|
-
gap: 8px;
|
|
5607
5697
|
}
|
|
5698
|
+
.nav-item:hover { color: var(--text-1); background: var(--panel); }
|
|
5699
|
+
.nav-item.active { color: var(--accent); background: var(--accent-lo); }
|
|
5700
|
+
.nav-item .icon { font-size: 15px; width: 20px; text-align: center; }
|
|
5608
5701
|
|
|
5609
|
-
.
|
|
5610
|
-
|
|
5611
|
-
|
|
5702
|
+
.sidebar-footer {
|
|
5703
|
+
padding: 16px 24px;
|
|
5704
|
+
border-top: 1px solid var(--border);
|
|
5612
5705
|
}
|
|
5613
5706
|
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
transform: translateY(-1px);
|
|
5617
|
-
}
|
|
5707
|
+
/* \u2500\u2500 Main \u2500\u2500 */
|
|
5708
|
+
main { display: flex; flex-direction: column; min-height: 100vh; overflow-y: auto; }
|
|
5618
5709
|
|
|
5619
|
-
.
|
|
5620
|
-
|
|
5621
|
-
|
|
5710
|
+
.topbar {
|
|
5711
|
+
display: flex;
|
|
5712
|
+
align-items: center;
|
|
5713
|
+
justify-content: space-between;
|
|
5714
|
+
padding: 20px 32px;
|
|
5715
|
+
border-bottom: 1px solid var(--border);
|
|
5716
|
+
background: var(--surface);
|
|
5717
|
+
position: sticky;
|
|
5718
|
+
top: 0;
|
|
5719
|
+
z-index: 10;
|
|
5622
5720
|
}
|
|
5623
5721
|
|
|
5624
|
-
.
|
|
5625
|
-
|
|
5626
|
-
}
|
|
5722
|
+
.page-title { font-size: 15px; font-weight: 700; letter-spacing: -0.2px; }
|
|
5723
|
+
.page-sub { font-family: var(--mono); font-size: 11px; color: var(--text-3); margin-top: 2px; }
|
|
5627
5724
|
|
|
5628
|
-
.
|
|
5629
|
-
background: #48bb78;
|
|
5630
|
-
color: white;
|
|
5631
|
-
}
|
|
5725
|
+
.topbar-actions { display: flex; align-items: center; gap: 10px; }
|
|
5632
5726
|
|
|
5633
|
-
.btn
|
|
5634
|
-
|
|
5727
|
+
.btn {
|
|
5728
|
+
font-family: var(--sans);
|
|
5729
|
+
font-weight: 700;
|
|
5730
|
+
font-size: 12px;
|
|
5731
|
+
padding: 8px 16px;
|
|
5732
|
+
border-radius: 8px;
|
|
5733
|
+
border: none;
|
|
5734
|
+
cursor: pointer;
|
|
5735
|
+
transition: all 0.15s ease;
|
|
5736
|
+
display: inline-flex;
|
|
5737
|
+
align-items: center;
|
|
5738
|
+
gap: 6px;
|
|
5739
|
+
text-decoration: none;
|
|
5635
5740
|
}
|
|
5741
|
+
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
5636
5742
|
|
|
5637
|
-
.btn
|
|
5638
|
-
|
|
5639
|
-
|
|
5743
|
+
.btn-ghost {
|
|
5744
|
+
background: var(--panel);
|
|
5745
|
+
color: var(--text-2);
|
|
5746
|
+
border: 1px solid var(--border);
|
|
5640
5747
|
}
|
|
5748
|
+
.btn-ghost:hover { border-color: var(--border-hi); color: var(--text-1); }
|
|
5641
5749
|
|
|
5642
|
-
.
|
|
5643
|
-
background:
|
|
5644
|
-
|
|
5645
|
-
padding: 30px;
|
|
5646
|
-
border-radius: 12px;
|
|
5647
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
5750
|
+
.btn-primary {
|
|
5751
|
+
background: var(--accent);
|
|
5752
|
+
color: #fff;
|
|
5648
5753
|
}
|
|
5754
|
+
.btn-primary:hover:not(:disabled) { background: #ea580c; box-shadow: 0 0 20px rgba(249,115,22,0.35); }
|
|
5649
5755
|
|
|
5650
|
-
|
|
5756
|
+
/* \u2500\u2500 Content \u2500\u2500 */
|
|
5757
|
+
.content { padding: 28px 32px; display: flex; flex-direction: column; gap: 24px; }
|
|
5758
|
+
|
|
5759
|
+
/* \u2500\u2500 Tabs (category selector) \u2500\u2500 */
|
|
5760
|
+
.tab-bar {
|
|
5651
5761
|
display: flex;
|
|
5652
|
-
gap:
|
|
5653
|
-
|
|
5654
|
-
border
|
|
5655
|
-
|
|
5762
|
+
gap: 4px;
|
|
5763
|
+
background: var(--surface);
|
|
5764
|
+
border: 1px solid var(--border);
|
|
5765
|
+
border-radius: 10px;
|
|
5766
|
+
padding: 4px;
|
|
5767
|
+
flex-wrap: wrap;
|
|
5656
5768
|
}
|
|
5657
5769
|
|
|
5658
|
-
.tab {
|
|
5659
|
-
padding:
|
|
5770
|
+
.tab-btn {
|
|
5771
|
+
padding: 7px 14px;
|
|
5772
|
+
background: transparent;
|
|
5660
5773
|
border: none;
|
|
5661
|
-
|
|
5662
|
-
|
|
5774
|
+
border-radius: 7px;
|
|
5775
|
+
color: var(--text-3);
|
|
5776
|
+
font-family: var(--sans);
|
|
5777
|
+
font-size: 12px;
|
|
5663
5778
|
font-weight: 600;
|
|
5664
|
-
color: #718096;
|
|
5665
5779
|
cursor: pointer;
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
color: #667eea;
|
|
5672
|
-
border-bottom-color: #667eea;
|
|
5780
|
+
transition: all 0.15s ease;
|
|
5781
|
+
white-space: nowrap;
|
|
5782
|
+
display: flex;
|
|
5783
|
+
align-items: center;
|
|
5784
|
+
gap: 5px;
|
|
5673
5785
|
}
|
|
5674
|
-
|
|
5675
|
-
.tab
|
|
5676
|
-
|
|
5786
|
+
.tab-btn:hover { color: var(--text-2); }
|
|
5787
|
+
.tab-btn.active {
|
|
5788
|
+
background: var(--panel);
|
|
5789
|
+
color: var(--text-1);
|
|
5790
|
+
border: 1px solid var(--border-hi);
|
|
5677
5791
|
}
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5792
|
+
.tab-btn .tab-dot {
|
|
5793
|
+
width: 6px; height: 6px;
|
|
5794
|
+
border-radius: 50%;
|
|
5795
|
+
background: var(--text-3);
|
|
5681
5796
|
}
|
|
5797
|
+
.tab-btn.has-required .tab-dot { background: var(--amber); }
|
|
5798
|
+
.tab-btn.active .tab-dot { background: var(--accent); }
|
|
5682
5799
|
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
}
|
|
5800
|
+
/* \u2500\u2500 Section \u2500\u2500 */
|
|
5801
|
+
.section { display: none; flex-direction: column; gap: 16px; }
|
|
5802
|
+
.section.active { display: flex; }
|
|
5686
5803
|
|
|
5687
|
-
.
|
|
5804
|
+
.section-header {
|
|
5688
5805
|
display: flex;
|
|
5689
|
-
justify-content: space-between;
|
|
5690
5806
|
align-items: center;
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
.category-title {
|
|
5695
|
-
font-size: 18px;
|
|
5696
|
-
font-weight: 600;
|
|
5697
|
-
color: #2d3748;
|
|
5698
|
-
}
|
|
5699
|
-
|
|
5700
|
-
.env-grid {
|
|
5701
|
-
display: grid;
|
|
5702
|
-
gap: 20px;
|
|
5807
|
+
gap: 12px;
|
|
5808
|
+
margin-bottom: 4px;
|
|
5703
5809
|
}
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5810
|
+
.section-icon {
|
|
5811
|
+
width: 36px; height: 36px;
|
|
5812
|
+
background: var(--accent-lo);
|
|
5813
|
+
border: 1px solid var(--accent-md);
|
|
5707
5814
|
border-radius: 8px;
|
|
5708
|
-
|
|
5709
|
-
|
|
5815
|
+
display: grid;
|
|
5816
|
+
place-items: center;
|
|
5817
|
+
font-size: 16px;
|
|
5710
5818
|
}
|
|
5819
|
+
.section-title { font-size: 15px; font-weight: 700; }
|
|
5820
|
+
.section-desc { font-family: var(--mono); font-size: 11px; color: var(--text-3); margin-top: 2px; }
|
|
5711
5821
|
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5822
|
+
/* \u2500\u2500 Env card \u2500\u2500 */
|
|
5823
|
+
.env-card {
|
|
5824
|
+
background: var(--panel);
|
|
5825
|
+
border: 1px solid var(--border);
|
|
5826
|
+
border-radius: var(--radius-lg);
|
|
5827
|
+
padding: 20px 24px;
|
|
5828
|
+
transition: border-color 0.15s;
|
|
5715
5829
|
}
|
|
5830
|
+
.env-card:hover { border-color: var(--border-hi); }
|
|
5831
|
+
.env-card.has-value { border-color: rgba(249,115,22,0.15); }
|
|
5716
5832
|
|
|
5717
|
-
.env-
|
|
5833
|
+
.env-card-head {
|
|
5718
5834
|
display: flex;
|
|
5719
5835
|
justify-content: space-between;
|
|
5720
5836
|
align-items: flex-start;
|
|
5721
|
-
margin-bottom:
|
|
5837
|
+
margin-bottom: 6px;
|
|
5722
5838
|
}
|
|
5723
5839
|
|
|
5724
|
-
.env-
|
|
5725
|
-
font-
|
|
5726
|
-
|
|
5727
|
-
font-
|
|
5840
|
+
.env-key {
|
|
5841
|
+
font-family: var(--mono);
|
|
5842
|
+
font-size: 13px;
|
|
5843
|
+
font-weight: 500;
|
|
5844
|
+
color: var(--text-1);
|
|
5728
5845
|
display: flex;
|
|
5729
5846
|
align-items: center;
|
|
5730
5847
|
gap: 8px;
|
|
5731
5848
|
}
|
|
5732
5849
|
|
|
5733
|
-
.required
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
font-
|
|
5737
|
-
|
|
5850
|
+
.badge-required {
|
|
5851
|
+
font-family: var(--sans);
|
|
5852
|
+
font-size: 9px;
|
|
5853
|
+
font-weight: 700;
|
|
5854
|
+
letter-spacing: 0.08em;
|
|
5855
|
+
text-transform: uppercase;
|
|
5856
|
+
background: rgba(239,68,68,0.15);
|
|
5857
|
+
color: var(--red);
|
|
5858
|
+
border: 1px solid rgba(239,68,68,0.25);
|
|
5738
5859
|
border-radius: 4px;
|
|
5860
|
+
padding: 2px 6px;
|
|
5861
|
+
}
|
|
5862
|
+
.badge-sensitive {
|
|
5863
|
+
font-size: 9px;
|
|
5739
5864
|
font-weight: 700;
|
|
5865
|
+
font-family: var(--sans);
|
|
5866
|
+
letter-spacing: 0.08em;
|
|
5867
|
+
text-transform: uppercase;
|
|
5868
|
+
background: var(--amber-lo);
|
|
5869
|
+
color: var(--amber);
|
|
5870
|
+
border: 1px solid rgba(245,158,11,0.2);
|
|
5871
|
+
border-radius: 4px;
|
|
5872
|
+
padding: 2px 6px;
|
|
5740
5873
|
}
|
|
5741
5874
|
|
|
5742
|
-
.env-
|
|
5743
|
-
font-
|
|
5744
|
-
|
|
5745
|
-
|
|
5875
|
+
.env-desc {
|
|
5876
|
+
font-family: var(--mono);
|
|
5877
|
+
font-size: 11px;
|
|
5878
|
+
color: var(--text-3);
|
|
5879
|
+
margin-bottom: 14px;
|
|
5880
|
+
line-height: 1.5;
|
|
5746
5881
|
}
|
|
5747
5882
|
|
|
5748
|
-
.env-input-
|
|
5883
|
+
.env-input-row {
|
|
5749
5884
|
display: flex;
|
|
5750
|
-
gap:
|
|
5885
|
+
gap: 8px;
|
|
5751
5886
|
align-items: center;
|
|
5752
5887
|
}
|
|
5753
5888
|
|
|
5754
|
-
.env-input {
|
|
5889
|
+
.env-input, .env-select {
|
|
5755
5890
|
flex: 1;
|
|
5756
|
-
|
|
5757
|
-
border: 1px solid
|
|
5758
|
-
border-radius:
|
|
5759
|
-
|
|
5760
|
-
font-family:
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
.env-input:focus {
|
|
5891
|
+
background: var(--surface);
|
|
5892
|
+
border: 1px solid var(--border-hi);
|
|
5893
|
+
border-radius: 8px;
|
|
5894
|
+
padding: 10px 14px;
|
|
5895
|
+
font-family: var(--mono);
|
|
5896
|
+
font-size: 13px;
|
|
5897
|
+
color: var(--text-1);
|
|
5765
5898
|
outline: none;
|
|
5766
|
-
border-color
|
|
5767
|
-
|
|
5899
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
5900
|
+
appearance: none;
|
|
5768
5901
|
}
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5902
|
+
.env-input:focus, .env-select:focus {
|
|
5903
|
+
border-color: var(--accent);
|
|
5904
|
+
box-shadow: 0 0 0 3px rgba(249,115,22,0.12);
|
|
5772
5905
|
}
|
|
5906
|
+
.env-input.changed { border-color: rgba(249,115,22,0.5); }
|
|
5907
|
+
.env-input.invalid { border-color: var(--red); }
|
|
5773
5908
|
|
|
5774
|
-
.env-
|
|
5775
|
-
display: flex;
|
|
5776
|
-
gap: 5px;
|
|
5777
|
-
}
|
|
5909
|
+
.env-select option { background: var(--panel); }
|
|
5778
5910
|
|
|
5779
|
-
.
|
|
5780
|
-
|
|
5781
|
-
border:
|
|
5782
|
-
|
|
5783
|
-
|
|
5911
|
+
.env-action-btn {
|
|
5912
|
+
width: 36px; height: 36px;
|
|
5913
|
+
border-radius: 8px;
|
|
5914
|
+
border: 1px solid var(--border-hi);
|
|
5915
|
+
background: var(--surface);
|
|
5916
|
+
color: var(--text-2);
|
|
5784
5917
|
cursor: pointer;
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
background: #cbd5e0;
|
|
5791
|
-
}
|
|
5792
|
-
|
|
5793
|
-
.icon-btn.test {
|
|
5794
|
-
background: #bee3f8;
|
|
5795
|
-
color: #2c5282;
|
|
5796
|
-
}
|
|
5797
|
-
|
|
5798
|
-
.icon-btn.test:hover {
|
|
5799
|
-
background: #90cdf4;
|
|
5800
|
-
}
|
|
5801
|
-
|
|
5802
|
-
.icon-btn.generate {
|
|
5803
|
-
background: #c6f6d5;
|
|
5804
|
-
color: #22543d;
|
|
5805
|
-
}
|
|
5806
|
-
|
|
5807
|
-
.icon-btn.generate:hover {
|
|
5808
|
-
background: #9ae6b4;
|
|
5918
|
+
display: grid;
|
|
5919
|
+
place-items: center;
|
|
5920
|
+
font-size: 14px;
|
|
5921
|
+
transition: all 0.15s;
|
|
5922
|
+
flex-shrink: 0;
|
|
5809
5923
|
}
|
|
5924
|
+
.env-action-btn:hover { background: var(--panel); color: var(--text-1); border-color: var(--border-hi); }
|
|
5925
|
+
.env-action-btn.test-btn:hover { background: var(--blue-lo); color: var(--blue); border-color: rgba(56,189,248,0.25); }
|
|
5926
|
+
.env-action-btn.gen-btn:hover { background: var(--green-lo); color: var(--green); border-color: rgba(34,197,94,0.25); }
|
|
5810
5927
|
|
|
5811
|
-
.
|
|
5812
|
-
|
|
5813
|
-
font-size:
|
|
5814
|
-
margin-top:
|
|
5928
|
+
.env-feedback {
|
|
5929
|
+
font-family: var(--mono);
|
|
5930
|
+
font-size: 11px;
|
|
5931
|
+
margin-top: 8px;
|
|
5932
|
+
min-height: 16px;
|
|
5815
5933
|
}
|
|
5934
|
+
.env-feedback.error { color: var(--red); }
|
|
5935
|
+
.env-feedback.success { color: var(--green); }
|
|
5816
5936
|
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5937
|
+
/* \u2500\u2500 Toast \u2500\u2500 */
|
|
5938
|
+
.toast-zone {
|
|
5939
|
+
position: fixed;
|
|
5940
|
+
bottom: 24px;
|
|
5941
|
+
right: 24px;
|
|
5942
|
+
display: flex;
|
|
5943
|
+
flex-direction: column;
|
|
5944
|
+
gap: 8px;
|
|
5945
|
+
z-index: 100;
|
|
5821
5946
|
}
|
|
5822
5947
|
|
|
5823
|
-
.
|
|
5824
|
-
padding:
|
|
5825
|
-
border-radius:
|
|
5826
|
-
|
|
5948
|
+
.toast {
|
|
5949
|
+
padding: 12px 18px;
|
|
5950
|
+
border-radius: 10px;
|
|
5951
|
+
font-size: 13px;
|
|
5952
|
+
font-weight: 600;
|
|
5827
5953
|
display: flex;
|
|
5828
5954
|
align-items: center;
|
|
5829
5955
|
gap: 10px;
|
|
5956
|
+
animation: slideIn 0.2s ease;
|
|
5957
|
+
max-width: 380px;
|
|
5830
5958
|
}
|
|
5959
|
+
.toast.success { background: var(--panel); border: 1px solid rgba(34,197,94,0.3); color: var(--green); }
|
|
5960
|
+
.toast.error { background: var(--panel); border: 1px solid rgba(239,68,68,0.3); color: var(--red); }
|
|
5961
|
+
.toast.warning { background: var(--panel); border: 1px solid rgba(245,158,11,0.3); color: var(--amber); }
|
|
5962
|
+
.toast.info { background: var(--panel); border: 1px solid rgba(56,189,248,0.3); color: var(--blue); }
|
|
5831
5963
|
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
color: #92400e;
|
|
5836
|
-
}
|
|
5837
|
-
|
|
5838
|
-
.alert-info {
|
|
5839
|
-
background: #eff6ff;
|
|
5840
|
-
border-left: 4px solid #3b82f6;
|
|
5841
|
-
color: #1e40af;
|
|
5964
|
+
@keyframes slideIn {
|
|
5965
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
5966
|
+
to { opacity: 1; transform: translateY(0); }
|
|
5842
5967
|
}
|
|
5843
5968
|
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5969
|
+
/* \u2500\u2500 Modal (test result) \u2500\u2500 */
|
|
5970
|
+
.modal-backdrop {
|
|
5971
|
+
display: none;
|
|
5972
|
+
position: fixed; inset: 0;
|
|
5973
|
+
background: rgba(0,0,0,0.6);
|
|
5974
|
+
z-index: 200;
|
|
5975
|
+
align-items: center;
|
|
5976
|
+
justify-content: center;
|
|
5848
5977
|
}
|
|
5978
|
+
.modal-backdrop.open { display: flex; }
|
|
5849
5979
|
|
|
5850
|
-
.
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5980
|
+
.modal {
|
|
5981
|
+
background: var(--surface);
|
|
5982
|
+
border: 1px solid var(--border-hi);
|
|
5983
|
+
border-radius: var(--radius-lg);
|
|
5984
|
+
padding: 28px;
|
|
5985
|
+
width: 420px;
|
|
5986
|
+
max-width: 90vw;
|
|
5987
|
+
box-shadow: 0 24px 64px rgba(0,0,0,0.5);
|
|
5988
|
+
}
|
|
5989
|
+
.modal-title { font-size: 15px; font-weight: 700; margin-bottom: 16px; }
|
|
5990
|
+
.modal-body { margin-bottom: 20px; }
|
|
5991
|
+
.modal-result {
|
|
5992
|
+
padding: 14px;
|
|
5993
|
+
border-radius: 8px;
|
|
5994
|
+
font-family: var(--mono);
|
|
5995
|
+
font-size: 12px;
|
|
5854
5996
|
}
|
|
5997
|
+
.modal-result.ok { background: var(--green-lo); border: 1px solid rgba(34,197,94,0.2); color: var(--green); }
|
|
5998
|
+
.modal-result.fail { background: var(--red-lo); border: 1px solid rgba(239,68,68,0.2); color: var(--red); }
|
|
5999
|
+
.modal-footer { display: flex; justify-content: flex-end; }
|
|
5855
6000
|
|
|
6001
|
+
/* \u2500\u2500 Spinner \u2500\u2500 */
|
|
5856
6002
|
.spinner {
|
|
5857
|
-
|
|
5858
|
-
border
|
|
6003
|
+
width: 36px; height: 36px;
|
|
6004
|
+
border: 3px solid var(--border);
|
|
6005
|
+
border-top-color: var(--accent);
|
|
5859
6006
|
border-radius: 50%;
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
animation: spin 1s linear infinite;
|
|
5863
|
-
margin: 0 auto 20px;
|
|
6007
|
+
animation: spin 0.8s linear infinite;
|
|
6008
|
+
margin: 0 auto 16px;
|
|
5864
6009
|
}
|
|
6010
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
5865
6011
|
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
6012
|
+
.loading-state {
|
|
6013
|
+
text-align: center;
|
|
6014
|
+
padding: 60px 0;
|
|
6015
|
+
color: var(--text-3);
|
|
6016
|
+
font-family: var(--mono);
|
|
6017
|
+
font-size: 12px;
|
|
5869
6018
|
}
|
|
5870
6019
|
|
|
5871
|
-
|
|
6020
|
+
/* \u2500\u2500 Restart banner \u2500\u2500 */
|
|
6021
|
+
.restart-banner {
|
|
5872
6022
|
display: none;
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
6023
|
+
background: var(--amber-lo);
|
|
6024
|
+
border: 1px solid rgba(245,158,11,0.25);
|
|
6025
|
+
border-radius: 10px;
|
|
6026
|
+
padding: 12px 18px;
|
|
6027
|
+
font-size: 13px;
|
|
6028
|
+
color: var(--amber);
|
|
6029
|
+
font-weight: 600;
|
|
5880
6030
|
align-items: center;
|
|
5881
|
-
|
|
6031
|
+
gap: 10px;
|
|
5882
6032
|
}
|
|
6033
|
+
.restart-banner.show { display: flex; }
|
|
6034
|
+
</style>
|
|
6035
|
+
</head>
|
|
6036
|
+
<body>
|
|
6037
|
+
<div class="shell">
|
|
5883
6038
|
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
6039
|
+
<!-- Sidebar -->
|
|
6040
|
+
<aside>
|
|
6041
|
+
<div class="logo">
|
|
6042
|
+
<div class="logo-mark">Q</div>
|
|
6043
|
+
<div>
|
|
6044
|
+
<div class="logo-name">OpenQA</div>
|
|
6045
|
+
<div class="logo-version">v1.3.4</div>
|
|
6046
|
+
</div>
|
|
6047
|
+
</div>
|
|
5887
6048
|
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
6049
|
+
<div class="nav-section">
|
|
6050
|
+
<div class="nav-label">Overview</div>
|
|
6051
|
+
<a class="nav-item" href="/">
|
|
6052
|
+
<span class="icon">\u{1F4CA}</span> Dashboard
|
|
6053
|
+
</a>
|
|
6054
|
+
<a class="nav-item" href="/sessions">
|
|
6055
|
+
<span class="icon">\u{1F9EA}</span> Sessions
|
|
6056
|
+
</a>
|
|
6057
|
+
<a class="nav-item" href="/issues">
|
|
6058
|
+
<span class="icon">\u{1F41B}</span> Issues
|
|
6059
|
+
</a>
|
|
5896
6060
|
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
6061
|
+
<div class="nav-label">Testing</div>
|
|
6062
|
+
<a class="nav-item" href="/tests">
|
|
6063
|
+
<span class="icon">\u26A1</span> Tests
|
|
6064
|
+
</a>
|
|
6065
|
+
<a class="nav-item" href="/coverage">
|
|
6066
|
+
<span class="icon">\u{1F4C8}</span> Coverage
|
|
6067
|
+
</a>
|
|
6068
|
+
<a class="nav-item" href="/kanban">
|
|
6069
|
+
<span class="icon">\u{1F4CB}</span> Kanban
|
|
6070
|
+
</a>
|
|
5903
6071
|
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
6072
|
+
<div class="nav-label">System</div>
|
|
6073
|
+
<a class="nav-item" href="/config">
|
|
6074
|
+
<span class="icon">\u2699\uFE0F</span> Config
|
|
6075
|
+
</a>
|
|
6076
|
+
<a class="nav-item active" href="/config/env">
|
|
6077
|
+
<span class="icon">\u{1F527}</span> Environment
|
|
6078
|
+
</a>
|
|
6079
|
+
<a class="nav-item" href="/logs">
|
|
6080
|
+
<span class="icon">\u{1F4DC}</span> Logs
|
|
6081
|
+
</a>
|
|
6082
|
+
</div>
|
|
5908
6083
|
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
gap: 10px;
|
|
5912
|
-
justify-content: flex-end;
|
|
5913
|
-
}
|
|
5914
|
-
</style>
|
|
5915
|
-
</head>
|
|
5916
|
-
<body>
|
|
5917
|
-
<div class="container">
|
|
5918
|
-
<div class="header">
|
|
5919
|
-
<h1>
|
|
5920
|
-
<span>\u2699\uFE0F</span>
|
|
6084
|
+
<div class="sidebar-footer">
|
|
6085
|
+
<div style="font-family:var(--mono);font-size:11px;color:var(--text-3);">
|
|
5921
6086
|
Environment Variables
|
|
5922
|
-
</
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
6087
|
+
</div>
|
|
6088
|
+
</div>
|
|
6089
|
+
</aside>
|
|
6090
|
+
|
|
6091
|
+
<!-- Main -->
|
|
6092
|
+
<main>
|
|
6093
|
+
<div class="topbar">
|
|
6094
|
+
<div>
|
|
6095
|
+
<div class="page-title">Environment Variables</div>
|
|
6096
|
+
<div class="page-sub">Configure runtime variables for OpenQA</div>
|
|
6097
|
+
</div>
|
|
6098
|
+
<div class="topbar-actions">
|
|
6099
|
+
<a class="btn btn-ghost" href="/config">\u2190 Back to Config</a>
|
|
6100
|
+
<button id="saveBtn" class="btn btn-primary" disabled>
|
|
6101
|
+
\u{1F4BE} Save Changes
|
|
6102
|
+
</button>
|
|
5926
6103
|
</div>
|
|
5927
6104
|
</div>
|
|
5928
6105
|
|
|
5929
6106
|
<div class="content">
|
|
5930
|
-
|
|
6107
|
+
|
|
6108
|
+
<!-- Restart banner -->
|
|
6109
|
+
<div class="restart-banner" id="restartBanner">
|
|
6110
|
+
\u26A0\uFE0F Some changes require a server restart to take effect.
|
|
6111
|
+
</div>
|
|
6112
|
+
|
|
6113
|
+
<!-- Loading -->
|
|
6114
|
+
<div class="loading-state" id="loadingState">
|
|
5931
6115
|
<div class="spinner"></div>
|
|
5932
|
-
|
|
6116
|
+
Loading environment variables\u2026
|
|
5933
6117
|
</div>
|
|
5934
6118
|
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
<button class="tab" data-category="web">\u{1F310} Web Server</button>
|
|
5944
|
-
<button class="tab" data-category="agent">\u{1F916} Agent</button>
|
|
5945
|
-
<button class="tab" data-category="database">\u{1F4BE} Database</button>
|
|
5946
|
-
<button class="tab" data-category="notifications">\u{1F514} Notifications</button>
|
|
5947
|
-
</div>
|
|
6119
|
+
<!-- Main content (hidden while loading) -->
|
|
6120
|
+
<div id="mainContent" style="display:none;flex-direction:column;gap:24px;">
|
|
6121
|
+
|
|
6122
|
+
<!-- Tab bar -->
|
|
6123
|
+
<div class="tab-bar" id="tabBar"></div>
|
|
6124
|
+
|
|
6125
|
+
<!-- Sections -->
|
|
6126
|
+
<div id="sections"></div>
|
|
5948
6127
|
|
|
5949
|
-
<div id="categories"></div>
|
|
5950
6128
|
</div>
|
|
5951
6129
|
</div>
|
|
5952
|
-
</
|
|
6130
|
+
</main>
|
|
6131
|
+
</div>
|
|
5953
6132
|
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
<div class="modal-
|
|
5960
|
-
|
|
5961
|
-
|
|
6133
|
+
<!-- Test result modal -->
|
|
6134
|
+
<div class="modal-backdrop" id="testModal">
|
|
6135
|
+
<div class="modal">
|
|
6136
|
+
<div class="modal-title">Connection Test</div>
|
|
6137
|
+
<div class="modal-body">
|
|
6138
|
+
<div class="modal-result" id="testResultBox">\u2026</div>
|
|
6139
|
+
</div>
|
|
6140
|
+
<div class="modal-footer">
|
|
6141
|
+
<button class="btn btn-ghost" onclick="closeModal()">Close</button>
|
|
5962
6142
|
</div>
|
|
5963
6143
|
</div>
|
|
6144
|
+
</div>
|
|
5964
6145
|
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
let changedVariables = {};
|
|
5968
|
-
let restartRequired = false;
|
|
6146
|
+
<!-- Toast zone -->
|
|
6147
|
+
<div class="toast-zone" id="toastZone"></div>
|
|
5969
6148
|
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
6149
|
+
<script>
|
|
6150
|
+
/* \u2500\u2500 State \u2500\u2500 */
|
|
6151
|
+
let envVars = [];
|
|
6152
|
+
let changed = {};
|
|
6153
|
+
let hasRequiredMissing = false;
|
|
6154
|
+
|
|
6155
|
+
const TABS = [
|
|
6156
|
+
{ id: 'llm', label: '\u{1F916} LLM', desc: 'Language model provider & API keys' },
|
|
6157
|
+
{ id: 'security', label: '\u{1F512} Security', desc: 'Authentication & JWT configuration' },
|
|
6158
|
+
{ id: 'target', label: '\u{1F3AF} Target App', desc: 'Application under test settings' },
|
|
6159
|
+
{ id: 'github', label: '\u{1F419} GitHub', desc: 'Repository & CI/CD integration' },
|
|
6160
|
+
{ id: 'web', label: '\u{1F310} Web Server', desc: 'HTTP host, port & CORS settings' },
|
|
6161
|
+
{ id: 'agent', label: '\u{1F916} Agent', desc: 'Autonomous agent behaviour' },
|
|
6162
|
+
{ id: 'database', label: '\u{1F4BE} Database', desc: 'Persistence & storage' },
|
|
6163
|
+
{ id: 'notifications', label: '\u{1F514} Notifications', desc: 'Slack & Discord webhooks' },
|
|
6164
|
+
];
|
|
5986
6165
|
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
</div>
|
|
6003
|
-
<div class="env-grid">
|
|
6004
|
-
\${vars.map(v => renderEnvItem(v)).join('')}
|
|
6005
|
-
</div>
|
|
6006
|
-
\`;
|
|
6007
|
-
|
|
6008
|
-
container.appendChild(section);
|
|
6009
|
-
});
|
|
6010
|
-
}
|
|
6166
|
+
/* \u2500\u2500 Init \u2500\u2500 */
|
|
6167
|
+
async function init() {
|
|
6168
|
+
try {
|
|
6169
|
+
const res = await fetch('/api/env');
|
|
6170
|
+
if (!res.ok) { toast('error', 'Failed to load environment variables (status ' + res.status + ')'); return; }
|
|
6171
|
+
const data = await res.json();
|
|
6172
|
+
envVars = data.variables || [];
|
|
6173
|
+
renderAll();
|
|
6174
|
+
document.getElementById('loadingState').style.display = 'none';
|
|
6175
|
+
const mc = document.getElementById('mainContent');
|
|
6176
|
+
mc.style.display = 'flex';
|
|
6177
|
+
} catch (e) {
|
|
6178
|
+
toast('error', 'Network error \u2014 ' + e.message);
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6011
6181
|
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
type="\${inputType}"
|
|
6042
|
-
class="env-input"
|
|
6043
|
-
data-key="\${envVar.key}"
|
|
6044
|
-
value="\${value}"
|
|
6045
|
-
placeholder="\${envVar.placeholder || ''}"
|
|
6046
|
-
onchange="handleChange(this)"
|
|
6047
|
-
/>\`
|
|
6048
|
-
}
|
|
6049
|
-
<div class="env-actions">
|
|
6050
|
-
\${envVar.testable ? \`<button class="icon-btn test" onclick="testVariable('\${envVar.key}')" title="Test">\u{1F9EA}</button>\` : ''}
|
|
6051
|
-
\${envVar.key === 'OPENQA_JWT_SECRET' ? \`<button class="icon-btn generate" onclick="generateSecret('\${envVar.key}')" title="Generate">\u{1F511}</button>\` : ''}
|
|
6052
|
-
</div>
|
|
6053
|
-
</div>
|
|
6054
|
-
<div class="error-message" id="error-\${envVar.key}"></div>
|
|
6055
|
-
<div class="success-message" id="success-\${envVar.key}"></div>
|
|
6182
|
+
/* \u2500\u2500 Render \u2500\u2500 */
|
|
6183
|
+
function renderAll() {
|
|
6184
|
+
renderTabBar();
|
|
6185
|
+
renderSections();
|
|
6186
|
+
activateTab(TABS[0].id);
|
|
6187
|
+
}
|
|
6188
|
+
|
|
6189
|
+
function renderTabBar() {
|
|
6190
|
+
const bar = document.getElementById('tabBar');
|
|
6191
|
+
bar.innerHTML = TABS.map(t => {
|
|
6192
|
+
const vars = envVars.filter(v => v.category === t.id);
|
|
6193
|
+
const hasRequired = vars.some(v => v.required);
|
|
6194
|
+
return \`<button class="tab-btn\${hasRequired ? ' has-required' : ''}" data-tab="\${t.id}" onclick="activateTab('\${t.id}')">
|
|
6195
|
+
<span class="tab-dot"></span>
|
|
6196
|
+
\${t.label}
|
|
6197
|
+
</button>\`;
|
|
6198
|
+
}).join('');
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
function renderSections() {
|
|
6202
|
+
const container = document.getElementById('sections');
|
|
6203
|
+
container.innerHTML = TABS.map(t => {
|
|
6204
|
+
const vars = envVars.filter(v => v.category === t.id);
|
|
6205
|
+
return \`<div class="section" id="section-\${t.id}">
|
|
6206
|
+
<div class="section-header">
|
|
6207
|
+
<div class="section-icon">\${t.label.split(' ')[0]}</div>
|
|
6208
|
+
<div>
|
|
6209
|
+
<div class="section-title">\${t.label.slice(t.label.indexOf(' ')+1)}</div>
|
|
6210
|
+
<div class="section-desc">\${t.desc}</div>
|
|
6056
6211
|
</div>
|
|
6057
|
-
|
|
6058
|
-
|
|
6212
|
+
</div>
|
|
6213
|
+
\${vars.map(renderCard).join('')}
|
|
6214
|
+
\${vars.length === 0 ? '<div style="color:var(--text-3);font-family:var(--mono);font-size:12px;padding:20px 0">No variables in this category.</div>' : ''}
|
|
6215
|
+
</div>\`;
|
|
6216
|
+
}).join('');
|
|
6217
|
+
}
|
|
6059
6218
|
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6219
|
+
function renderCard(v) {
|
|
6220
|
+
const displayVal = v.displayValue || '';
|
|
6221
|
+
const isSensitive = v.sensitive;
|
|
6222
|
+
const inputType = (v.type === 'password' && !changed[v.key]) ? 'password' : 'text';
|
|
6223
|
+
|
|
6224
|
+
let inputHTML = '';
|
|
6225
|
+
if (v.type === 'select' || v.type === 'boolean') {
|
|
6226
|
+
const opts = v.type === 'boolean'
|
|
6227
|
+
? [{ val: 'true', lbl: 'true' }, { val: 'false', lbl: 'false' }]
|
|
6228
|
+
: (v.options || []).map(o => ({ val: o, lbl: o }));
|
|
6229
|
+
inputHTML = \`<select class="env-select" data-key="\${v.key}" onchange="handleChange(this)">
|
|
6230
|
+
<option value="">\u2014 Select \u2014</option>
|
|
6231
|
+
\${opts.map(o => \`<option value="\${o.val}" \${displayVal === o.val ? 'selected' : ''}>\${o.lbl}</option>\`).join('')}
|
|
6232
|
+
</select>\`;
|
|
6233
|
+
} else {
|
|
6234
|
+
inputHTML = \`<input
|
|
6235
|
+
type="\${inputType}"
|
|
6236
|
+
class="env-input"
|
|
6237
|
+
data-key="\${v.key}"
|
|
6238
|
+
value="\${escHtml(displayVal)}"
|
|
6239
|
+
placeholder="\${escHtml(v.placeholder || '')}"
|
|
6240
|
+
oninput="handleChange(this)"
|
|
6241
|
+
autocomplete="off"
|
|
6242
|
+
/>\`;
|
|
6243
|
+
}
|
|
6072
6244
|
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6245
|
+
const testBtn = v.testable
|
|
6246
|
+
? \`<button class="env-action-btn test-btn" onclick="testVar('\${v.key}')" title="Test connection">\u{1F9EA}</button>\`
|
|
6247
|
+
: '';
|
|
6248
|
+
|
|
6249
|
+
const genBtn = v.key === 'OPENQA_JWT_SECRET'
|
|
6250
|
+
? \`<button class="env-action-btn gen-btn" onclick="generateSecret('\${v.key}')" title="Generate secret">\u{1F511}</button>\`
|
|
6251
|
+
: '';
|
|
6252
|
+
|
|
6253
|
+
const toggleBtn = (v.type === 'password' || isSensitive)
|
|
6254
|
+
? \`<button class="env-action-btn" onclick="toggleVis('\${v.key}')" title="Toggle visibility" id="vis-\${v.key}">\u{1F441}</button>\`
|
|
6255
|
+
: '';
|
|
6256
|
+
|
|
6257
|
+
return \`<div class="env-card\${displayVal ? ' has-value' : ''}" id="card-\${v.key}">
|
|
6258
|
+
<div class="env-card-head">
|
|
6259
|
+
<div class="env-key">
|
|
6260
|
+
\${v.key}
|
|
6261
|
+
\${v.required ? '<span class="badge-required">Required</span>' : ''}
|
|
6262
|
+
\${isSensitive ? '<span class="badge-sensitive">Sensitive</span>' : ''}
|
|
6263
|
+
</div>
|
|
6264
|
+
</div>
|
|
6265
|
+
<div class="env-desc">\${v.description}</div>
|
|
6266
|
+
<div class="env-input-row">
|
|
6267
|
+
\${inputHTML}
|
|
6268
|
+
\${toggleBtn}
|
|
6269
|
+
\${testBtn}
|
|
6270
|
+
\${genBtn}
|
|
6271
|
+
</div>
|
|
6272
|
+
<div class="env-feedback" id="fb-\${v.key}"></div>
|
|
6273
|
+
</div>\`;
|
|
6274
|
+
}
|
|
6275
|
+
|
|
6276
|
+
/* \u2500\u2500 Tab switching \u2500\u2500 */
|
|
6277
|
+
function activateTab(id) {
|
|
6278
|
+
document.querySelectorAll('.tab-btn').forEach(b => b.classList.toggle('active', b.dataset.tab === id));
|
|
6279
|
+
document.querySelectorAll('.section').forEach(s => s.classList.toggle('active', s.id === 'section-' + id));
|
|
6280
|
+
}
|
|
6281
|
+
|
|
6282
|
+
/* \u2500\u2500 Input handling \u2500\u2500 */
|
|
6283
|
+
function handleChange(el) {
|
|
6284
|
+
const key = el.dataset.key;
|
|
6285
|
+
const val = el.value;
|
|
6286
|
+
changed[key] = val;
|
|
6287
|
+
el.classList.add('changed');
|
|
6288
|
+
el.classList.remove('invalid');
|
|
6289
|
+
clearFeedback(key);
|
|
6290
|
+
document.getElementById('saveBtn').disabled = false;
|
|
6291
|
+
}
|
|
6292
|
+
|
|
6293
|
+
/* \u2500\u2500 Toggle password visibility \u2500\u2500 */
|
|
6294
|
+
function toggleVis(key) {
|
|
6295
|
+
const inp = document.querySelector('[data-key="' + key + '"]');
|
|
6296
|
+
if (!inp || inp.tagName !== 'INPUT') return;
|
|
6297
|
+
inp.type = inp.type === 'password' ? 'text' : 'password';
|
|
6298
|
+
}
|
|
6299
|
+
|
|
6300
|
+
/* \u2500\u2500 Save \u2500\u2500 */
|
|
6301
|
+
async function saveChanges() {
|
|
6302
|
+
if (!Object.keys(changed).length) return;
|
|
6303
|
+
|
|
6304
|
+
const btn = document.getElementById('saveBtn');
|
|
6305
|
+
btn.disabled = true;
|
|
6306
|
+
btn.textContent = '\u23F3 Saving\u2026';
|
|
6307
|
+
|
|
6308
|
+
try {
|
|
6309
|
+
const res = await fetch('/api/env/bulk', {
|
|
6310
|
+
method: 'POST',
|
|
6311
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6312
|
+
body: JSON.stringify({ variables: changed }),
|
|
6313
|
+
credentials: 'include',
|
|
6314
|
+
});
|
|
6315
|
+
|
|
6316
|
+
const body = await res.json().catch(() => ({}));
|
|
6317
|
+
|
|
6318
|
+
if (!res.ok) {
|
|
6319
|
+
const errStr = body.errors
|
|
6320
|
+
? Object.entries(body.errors).map(([k, v]) => k + ': ' + v).join('; ')
|
|
6321
|
+
: body.error || 'Failed to save';
|
|
6322
|
+
// Show per-field errors
|
|
6323
|
+
if (body.errors) {
|
|
6324
|
+
for (const [k, msg] of Object.entries(body.errors)) {
|
|
6325
|
+
setFeedback(k, 'error', msg);
|
|
6326
|
+
const inp = document.querySelector('[data-key="' + k + '"]');
|
|
6327
|
+
if (inp) inp.classList.add('invalid');
|
|
6089
6328
|
}
|
|
6090
|
-
|
|
6091
|
-
const result = await response.json();
|
|
6092
|
-
restartRequired = result.restartRequired;
|
|
6093
|
-
|
|
6094
|
-
showAlert('success', \`\u2705 Saved \${result.updated} variable(s) successfully!\` +
|
|
6095
|
-
(restartRequired ? ' \u26A0\uFE0F Restart required for changes to take effect.' : ''));
|
|
6096
|
-
|
|
6097
|
-
changedVariables = {};
|
|
6098
|
-
saveBtn.textContent = '\u{1F4BE} Save Changes';
|
|
6099
|
-
|
|
6100
|
-
// Reload to show updated values
|
|
6101
|
-
setTimeout(() => location.reload(), 2000);
|
|
6102
|
-
} catch (error) {
|
|
6103
|
-
showAlert('error', 'Failed to save: ' + error.message);
|
|
6104
|
-
saveBtn.disabled = false;
|
|
6105
|
-
saveBtn.textContent = '\u{1F4BE} Save Changes';
|
|
6106
6329
|
}
|
|
6330
|
+
toast('error', errStr);
|
|
6331
|
+
btn.disabled = false;
|
|
6332
|
+
btn.innerHTML = '\u{1F4BE} Save Changes';
|
|
6333
|
+
return;
|
|
6107
6334
|
}
|
|
6108
6335
|
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
const value = input.value;
|
|
6113
|
-
|
|
6114
|
-
if (!value) {
|
|
6115
|
-
showAlert('warning', 'Please enter a value first');
|
|
6116
|
-
return;
|
|
6117
|
-
}
|
|
6118
|
-
|
|
6119
|
-
try {
|
|
6120
|
-
const response = await fetch(\`/api/env/test/\${key}\`, {
|
|
6121
|
-
method: 'POST',
|
|
6122
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6123
|
-
body: JSON.stringify({ value }),
|
|
6124
|
-
});
|
|
6125
|
-
|
|
6126
|
-
const result = await response.json();
|
|
6127
|
-
showTestResult(result);
|
|
6128
|
-
} catch (error) {
|
|
6129
|
-
showTestResult({ success: false, message: 'Test failed: ' + error.message });
|
|
6130
|
-
}
|
|
6336
|
+
toast('success', '\u2705 Saved ' + body.updated + ' variable(s)');
|
|
6337
|
+
if (body.restartRequired) {
|
|
6338
|
+
document.getElementById('restartBanner').classList.add('show');
|
|
6131
6339
|
}
|
|
6132
6340
|
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
const input = document.querySelector(\`[data-key="\${key}"]\`);
|
|
6144
|
-
input.value = result.value;
|
|
6145
|
-
handleChange(input);
|
|
6146
|
-
|
|
6147
|
-
document.getElementById(\`success-\${key}\`).textContent = '\u2705 Secret generated!';
|
|
6148
|
-
} catch (error) {
|
|
6149
|
-
document.getElementById(\`error-\${key}\`).textContent = 'Failed to generate: ' + error.message;
|
|
6150
|
-
}
|
|
6151
|
-
}
|
|
6341
|
+
changed = {};
|
|
6342
|
+
btn.innerHTML = '\u{1F4BE} Save Changes';
|
|
6343
|
+
// Reload to reflect masked values
|
|
6344
|
+
setTimeout(() => location.reload(), 1200);
|
|
6345
|
+
} catch (e) {
|
|
6346
|
+
toast('error', 'Network error \u2014 ' + e.message);
|
|
6347
|
+
btn.disabled = false;
|
|
6348
|
+
btn.innerHTML = '\u{1F4BE} Save Changes';
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6152
6351
|
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
resultDiv.innerHTML = \`
|
|
6159
|
-
<div class="alert \${result.success ? 'alert-success' : 'alert-warning'}">
|
|
6160
|
-
\${result.success ? '\u2705' : '\u274C'} \${result.message}
|
|
6161
|
-
</div>
|
|
6162
|
-
\`;
|
|
6163
|
-
|
|
6164
|
-
modal.classList.add('show');
|
|
6165
|
-
}
|
|
6352
|
+
/* \u2500\u2500 Test variable \u2500\u2500 */
|
|
6353
|
+
async function testVar(key) {
|
|
6354
|
+
const inp = document.querySelector('[data-key="' + key + '"]');
|
|
6355
|
+
const val = inp ? inp.value : '';
|
|
6356
|
+
if (!val) { toast('warning', 'Enter a value first'); return; }
|
|
6166
6357
|
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6358
|
+
setFeedback(key, '', '');
|
|
6359
|
+
const btn = document.querySelector('[onclick="testVar(\\''+key+'\\')"]');
|
|
6360
|
+
if (btn) { btn.textContent = '\u23F3'; btn.disabled = true; }
|
|
6170
6361
|
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
alerts.innerHTML = \`
|
|
6178
|
-
<div class="alert \${alertClass}">
|
|
6179
|
-
\${message}
|
|
6180
|
-
</div>
|
|
6181
|
-
\`;
|
|
6182
|
-
|
|
6183
|
-
setTimeout(() => alerts.innerHTML = '', 5000);
|
|
6184
|
-
}
|
|
6185
|
-
|
|
6186
|
-
// Get category title
|
|
6187
|
-
function getCategoryTitle(category) {
|
|
6188
|
-
const titles = {
|
|
6189
|
-
llm: '\u{1F916} LLM Configuration',
|
|
6190
|
-
security: '\u{1F512} Security Settings',
|
|
6191
|
-
target: '\u{1F3AF} Target Application',
|
|
6192
|
-
github: '\u{1F419} GitHub Integration',
|
|
6193
|
-
web: '\u{1F310} Web Server',
|
|
6194
|
-
agent: '\u{1F916} Agent Configuration',
|
|
6195
|
-
database: '\u{1F4BE} Database',
|
|
6196
|
-
notifications: '\u{1F514} Notifications',
|
|
6197
|
-
};
|
|
6198
|
-
return titles[category] || category;
|
|
6199
|
-
}
|
|
6200
|
-
|
|
6201
|
-
// Tab switching
|
|
6202
|
-
document.addEventListener('click', (e) => {
|
|
6203
|
-
if (e.target.classList.contains('tab')) {
|
|
6204
|
-
const category = e.target.dataset.category;
|
|
6205
|
-
|
|
6206
|
-
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
6207
|
-
e.target.classList.add('active');
|
|
6208
|
-
|
|
6209
|
-
document.querySelectorAll('.category-section').forEach(s => s.classList.remove('active'));
|
|
6210
|
-
document.querySelector(\`[data-category="\${category}"]\`).classList.add('active');
|
|
6211
|
-
}
|
|
6362
|
+
try {
|
|
6363
|
+
const res = await fetch('/api/env/test/' + key, {
|
|
6364
|
+
method: 'POST',
|
|
6365
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6366
|
+
body: JSON.stringify({ value: val }),
|
|
6367
|
+
credentials: 'include',
|
|
6212
6368
|
});
|
|
6369
|
+
const result = await res.json();
|
|
6370
|
+
openModal(result.success, result.message);
|
|
6371
|
+
setFeedback(key, result.success ? 'success' : 'error', result.success ? '\u2713 Connected' : '\u2717 ' + result.message);
|
|
6372
|
+
} catch (e) {
|
|
6373
|
+
openModal(false, 'Network error: ' + e.message);
|
|
6374
|
+
} finally {
|
|
6375
|
+
if (btn) { btn.textContent = '\u{1F9EA}'; btn.disabled = false; }
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
/* \u2500\u2500 Generate secret \u2500\u2500 */
|
|
6380
|
+
async function generateSecret(key) {
|
|
6381
|
+
try {
|
|
6382
|
+
const res = await fetch('/api/env/generate/' + key, {
|
|
6383
|
+
method: 'POST', credentials: 'include'
|
|
6384
|
+
});
|
|
6385
|
+
if (!res.ok) throw new Error('Failed to generate');
|
|
6386
|
+
const { value } = await res.json();
|
|
6387
|
+
const inp = document.querySelector('[data-key="' + key + '"]');
|
|
6388
|
+
if (inp) {
|
|
6389
|
+
inp.type = 'text';
|
|
6390
|
+
inp.value = value;
|
|
6391
|
+
handleChange(inp);
|
|
6392
|
+
}
|
|
6393
|
+
setFeedback(key, 'success', '\u2713 Secret generated \u2014 save to persist');
|
|
6394
|
+
} catch (e) {
|
|
6395
|
+
setFeedback(key, 'error', e.message);
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
|
|
6399
|
+
/* \u2500\u2500 Modal \u2500\u2500 */
|
|
6400
|
+
function openModal(ok, msg) {
|
|
6401
|
+
const box = document.getElementById('testResultBox');
|
|
6402
|
+
box.className = 'modal-result ' + (ok ? 'ok' : 'fail');
|
|
6403
|
+
box.textContent = (ok ? '\u2713 ' : '\u2717 ') + msg;
|
|
6404
|
+
document.getElementById('testModal').classList.add('open');
|
|
6405
|
+
}
|
|
6406
|
+
function closeModal() {
|
|
6407
|
+
document.getElementById('testModal').classList.remove('open');
|
|
6408
|
+
}
|
|
6213
6409
|
|
|
6214
|
-
|
|
6215
|
-
|
|
6410
|
+
/* \u2500\u2500 Toast \u2500\u2500 */
|
|
6411
|
+
function toast(type, msg) {
|
|
6412
|
+
const zone = document.getElementById('toastZone');
|
|
6413
|
+
const el = document.createElement('div');
|
|
6414
|
+
el.className = 'toast ' + type;
|
|
6415
|
+
el.textContent = msg;
|
|
6416
|
+
zone.appendChild(el);
|
|
6417
|
+
setTimeout(() => el.remove(), 4500);
|
|
6418
|
+
}
|
|
6216
6419
|
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6420
|
+
/* \u2500\u2500 Feedback \u2500\u2500 */
|
|
6421
|
+
function setFeedback(key, type, msg) {
|
|
6422
|
+
const el = document.getElementById('fb-' + key);
|
|
6423
|
+
if (!el) return;
|
|
6424
|
+
el.className = 'env-feedback' + (type ? ' ' + type : '');
|
|
6425
|
+
el.textContent = msg;
|
|
6426
|
+
}
|
|
6427
|
+
function clearFeedback(key) { setFeedback(key, '', ''); }
|
|
6428
|
+
|
|
6429
|
+
/* \u2500\u2500 Helpers \u2500\u2500 */
|
|
6430
|
+
function escHtml(s) {
|
|
6431
|
+
return String(s).replace(/[&<>"']/g, c => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[c]));
|
|
6432
|
+
}
|
|
6433
|
+
|
|
6434
|
+
/* \u2500\u2500 Wire save button \u2500\u2500 */
|
|
6435
|
+
document.getElementById('saveBtn').addEventListener('click', saveChanges);
|
|
6436
|
+
|
|
6437
|
+
/* \u2500\u2500 Close modal on backdrop click \u2500\u2500 */
|
|
6438
|
+
document.getElementById('testModal').addEventListener('click', function(e) {
|
|
6439
|
+
if (e.target === this) closeModal();
|
|
6440
|
+
});
|
|
6441
|
+
|
|
6442
|
+
/* \u2500\u2500 Boot \u2500\u2500 */
|
|
6443
|
+
init();
|
|
6444
|
+
</script>
|
|
6220
6445
|
</body>
|
|
6221
6446
|
</html>`;
|
|
6222
6447
|
}
|