@grwnd/pi-governance 1.5.1 → 1.7.0
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 +28 -256
- package/dist/extensions/index.cjs +1713 -38
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.js +1707 -35
- package/dist/extensions/index.js.map +1 -1
- package/dist/index.cjs +1613 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +1611 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3242,7 +3242,20 @@ var DEFAULTS = {
|
|
|
3242
3242
|
sinks: [{ type: "jsonl", path: "~/.pi/agent/audit.jsonl" }]
|
|
3243
3243
|
},
|
|
3244
3244
|
dlp: {
|
|
3245
|
-
enabled:
|
|
3245
|
+
enabled: true,
|
|
3246
|
+
mode: "audit",
|
|
3247
|
+
on_input: "block",
|
|
3248
|
+
on_output: "mask",
|
|
3249
|
+
masking: {
|
|
3250
|
+
strategy: "partial",
|
|
3251
|
+
show_chars: 4,
|
|
3252
|
+
placeholder: "***"
|
|
3253
|
+
},
|
|
3254
|
+
severity_threshold: "low",
|
|
3255
|
+
built_in: {
|
|
3256
|
+
secrets: true,
|
|
3257
|
+
pii: true
|
|
3258
|
+
}
|
|
3246
3259
|
}
|
|
3247
3260
|
};
|
|
3248
3261
|
|
|
@@ -4344,6 +4357,1601 @@ function createApprovalFlow(config, ui) {
|
|
|
4344
4357
|
}
|
|
4345
4358
|
return new CliApprover(ui, config.timeout_seconds);
|
|
4346
4359
|
}
|
|
4360
|
+
|
|
4361
|
+
// src/lib/wizard/server.ts
|
|
4362
|
+
import { createServer } from "http";
|
|
4363
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
4364
|
+
import { join as join2 } from "path";
|
|
4365
|
+
import { stringify } from "yaml";
|
|
4366
|
+
|
|
4367
|
+
// src/lib/wizard/html.ts
|
|
4368
|
+
var WIZARD_HTML = `<!DOCTYPE html>
|
|
4369
|
+
<html lang="en">
|
|
4370
|
+
<head>
|
|
4371
|
+
<meta charset="UTF-8">
|
|
4372
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4373
|
+
<title>Pi Governance \u2014 Setup Wizard</title>
|
|
4374
|
+
<style>
|
|
4375
|
+
:root {
|
|
4376
|
+
--bg: #f8f9fa;
|
|
4377
|
+
--bg-surface: #ffffff;
|
|
4378
|
+
--bg-surface-alt: #f1f3f5;
|
|
4379
|
+
--bg-code: #e9ecef;
|
|
4380
|
+
--text: #212529;
|
|
4381
|
+
--text-muted: #6c757d;
|
|
4382
|
+
--text-inverse: #ffffff;
|
|
4383
|
+
--border: #dee2e6;
|
|
4384
|
+
--border-focus: #4263eb;
|
|
4385
|
+
--primary: #4263eb;
|
|
4386
|
+
--primary-hover: #3b5bdb;
|
|
4387
|
+
--primary-subtle: #dbe4ff;
|
|
4388
|
+
--success: #2f9e44;
|
|
4389
|
+
--success-subtle: #d3f9d8;
|
|
4390
|
+
--danger: #e03131;
|
|
4391
|
+
--danger-subtle: #ffe3e3;
|
|
4392
|
+
--warning: #f08c00;
|
|
4393
|
+
--warning-subtle: #fff3bf;
|
|
4394
|
+
--radius: 8px;
|
|
4395
|
+
--radius-sm: 4px;
|
|
4396
|
+
--shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
4397
|
+
--shadow-lg: 0 4px 12px rgba(0,0,0,0.1);
|
|
4398
|
+
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
4399
|
+
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
4400
|
+
--transition: 150ms ease;
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
@media (prefers-color-scheme: dark) {
|
|
4404
|
+
:root {
|
|
4405
|
+
--bg: #1a1b1e;
|
|
4406
|
+
--bg-surface: #25262b;
|
|
4407
|
+
--bg-surface-alt: #2c2e33;
|
|
4408
|
+
--bg-code: #2c2e33;
|
|
4409
|
+
--text: #c1c2c5;
|
|
4410
|
+
--text-muted: #909296;
|
|
4411
|
+
--text-inverse: #1a1b1e;
|
|
4412
|
+
--border: #373a40;
|
|
4413
|
+
--border-focus: #5c7cfa;
|
|
4414
|
+
--primary: #5c7cfa;
|
|
4415
|
+
--primary-hover: #748ffc;
|
|
4416
|
+
--primary-subtle: #1b2559;
|
|
4417
|
+
--success: #51cf66;
|
|
4418
|
+
--success-subtle: #0b3d1a;
|
|
4419
|
+
--danger: #ff6b6b;
|
|
4420
|
+
--danger-subtle: #3d0b0b;
|
|
4421
|
+
--warning: #fcc419;
|
|
4422
|
+
--warning-subtle: #3d2e00;
|
|
4423
|
+
--shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
4424
|
+
--shadow-lg: 0 4px 12px rgba(0,0,0,0.4);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
4429
|
+
|
|
4430
|
+
body {
|
|
4431
|
+
font-family: var(--font-sans);
|
|
4432
|
+
background: var(--bg);
|
|
4433
|
+
color: var(--text);
|
|
4434
|
+
line-height: 1.6;
|
|
4435
|
+
min-height: 100vh;
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
.layout {
|
|
4439
|
+
display: grid;
|
|
4440
|
+
grid-template-columns: 1fr 420px;
|
|
4441
|
+
gap: 0;
|
|
4442
|
+
min-height: 100vh;
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
@media (max-width: 1024px) {
|
|
4446
|
+
.layout { grid-template-columns: 1fr; }
|
|
4447
|
+
.preview-panel { display: none; }
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
/* --- Left column: Form --- */
|
|
4451
|
+
.form-column {
|
|
4452
|
+
padding: 32px 40px 80px;
|
|
4453
|
+
overflow-y: auto;
|
|
4454
|
+
max-height: 100vh;
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4457
|
+
.logo {
|
|
4458
|
+
display: flex;
|
|
4459
|
+
align-items: center;
|
|
4460
|
+
gap: 10px;
|
|
4461
|
+
margin-bottom: 8px;
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4464
|
+
.logo-icon {
|
|
4465
|
+
width: 36px; height: 36px;
|
|
4466
|
+
background: var(--primary);
|
|
4467
|
+
border-radius: var(--radius);
|
|
4468
|
+
display: flex; align-items: center; justify-content: center;
|
|
4469
|
+
color: var(--text-inverse);
|
|
4470
|
+
font-weight: 700; font-size: 18px;
|
|
4471
|
+
}
|
|
4472
|
+
|
|
4473
|
+
.logo-text {
|
|
4474
|
+
font-size: 20px;
|
|
4475
|
+
font-weight: 700;
|
|
4476
|
+
color: var(--text);
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
.logo-text span { color: var(--text-muted); font-weight: 400; }
|
|
4480
|
+
|
|
4481
|
+
h1 {
|
|
4482
|
+
font-size: 28px;
|
|
4483
|
+
font-weight: 700;
|
|
4484
|
+
margin: 24px 0 8px;
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
.subtitle {
|
|
4488
|
+
color: var(--text-muted);
|
|
4489
|
+
font-size: 15px;
|
|
4490
|
+
margin-bottom: 32px;
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
/* Sections */
|
|
4494
|
+
.section {
|
|
4495
|
+
background: var(--bg-surface);
|
|
4496
|
+
border: 1px solid var(--border);
|
|
4497
|
+
border-radius: var(--radius);
|
|
4498
|
+
padding: 24px;
|
|
4499
|
+
margin-bottom: 20px;
|
|
4500
|
+
box-shadow: var(--shadow);
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4503
|
+
.section-header {
|
|
4504
|
+
display: flex;
|
|
4505
|
+
align-items: center;
|
|
4506
|
+
gap: 10px;
|
|
4507
|
+
margin-bottom: 16px;
|
|
4508
|
+
cursor: pointer;
|
|
4509
|
+
user-select: none;
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
.section-header h2 {
|
|
4513
|
+
font-size: 16px;
|
|
4514
|
+
font-weight: 600;
|
|
4515
|
+
flex: 1;
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
.section-badge {
|
|
4519
|
+
font-size: 11px;
|
|
4520
|
+
padding: 2px 8px;
|
|
4521
|
+
border-radius: 10px;
|
|
4522
|
+
font-weight: 600;
|
|
4523
|
+
text-transform: uppercase;
|
|
4524
|
+
letter-spacing: 0.5px;
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
.badge-required { background: var(--danger-subtle); color: var(--danger); }
|
|
4528
|
+
.badge-optional { background: var(--primary-subtle); color: var(--primary); }
|
|
4529
|
+
|
|
4530
|
+
.section-icon {
|
|
4531
|
+
width: 32px; height: 32px;
|
|
4532
|
+
border-radius: var(--radius-sm);
|
|
4533
|
+
display: flex; align-items: center; justify-content: center;
|
|
4534
|
+
font-size: 16px;
|
|
4535
|
+
flex-shrink: 0;
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
.section-body { display: block; }
|
|
4539
|
+
.section.collapsed .section-body { display: none; }
|
|
4540
|
+
.section-chevron {
|
|
4541
|
+
transition: transform var(--transition);
|
|
4542
|
+
color: var(--text-muted);
|
|
4543
|
+
font-size: 12px;
|
|
4544
|
+
}
|
|
4545
|
+
.section.collapsed .section-chevron { transform: rotate(-90deg); }
|
|
4546
|
+
|
|
4547
|
+
/* Form elements */
|
|
4548
|
+
label {
|
|
4549
|
+
display: block;
|
|
4550
|
+
font-size: 13px;
|
|
4551
|
+
font-weight: 600;
|
|
4552
|
+
color: var(--text);
|
|
4553
|
+
margin-bottom: 4px;
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
.label-hint {
|
|
4557
|
+
font-weight: 400;
|
|
4558
|
+
color: var(--text-muted);
|
|
4559
|
+
font-size: 12px;
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4562
|
+
input[type="text"],
|
|
4563
|
+
input[type="number"],
|
|
4564
|
+
select {
|
|
4565
|
+
width: 100%;
|
|
4566
|
+
padding: 8px 12px;
|
|
4567
|
+
border: 1px solid var(--border);
|
|
4568
|
+
border-radius: var(--radius-sm);
|
|
4569
|
+
background: var(--bg-surface);
|
|
4570
|
+
color: var(--text);
|
|
4571
|
+
font-size: 14px;
|
|
4572
|
+
font-family: var(--font-sans);
|
|
4573
|
+
transition: border-color var(--transition);
|
|
4574
|
+
outline: none;
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
input:focus, select:focus {
|
|
4578
|
+
border-color: var(--border-focus);
|
|
4579
|
+
box-shadow: 0 0 0 2px var(--primary-subtle);
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
.field { margin-bottom: 16px; }
|
|
4583
|
+
|
|
4584
|
+
.field-row {
|
|
4585
|
+
display: grid;
|
|
4586
|
+
grid-template-columns: 1fr 1fr;
|
|
4587
|
+
gap: 12px;
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
.field-row-3 {
|
|
4591
|
+
display: grid;
|
|
4592
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
4593
|
+
gap: 12px;
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
/* Toggle switch */
|
|
4597
|
+
.toggle-row {
|
|
4598
|
+
display: flex;
|
|
4599
|
+
align-items: center;
|
|
4600
|
+
gap: 10px;
|
|
4601
|
+
margin-bottom: 12px;
|
|
4602
|
+
}
|
|
4603
|
+
|
|
4604
|
+
.toggle {
|
|
4605
|
+
position: relative;
|
|
4606
|
+
width: 40px; height: 22px;
|
|
4607
|
+
flex-shrink: 0;
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4610
|
+
.toggle input { display: none; }
|
|
4611
|
+
|
|
4612
|
+
.toggle-slider {
|
|
4613
|
+
position: absolute;
|
|
4614
|
+
inset: 0;
|
|
4615
|
+
background: var(--border);
|
|
4616
|
+
border-radius: 11px;
|
|
4617
|
+
cursor: pointer;
|
|
4618
|
+
transition: background var(--transition);
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
.toggle-slider::before {
|
|
4622
|
+
content: '';
|
|
4623
|
+
position: absolute;
|
|
4624
|
+
width: 16px; height: 16px;
|
|
4625
|
+
left: 3px; top: 3px;
|
|
4626
|
+
background: white;
|
|
4627
|
+
border-radius: 50%;
|
|
4628
|
+
transition: transform var(--transition);
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
.toggle input:checked + .toggle-slider {
|
|
4632
|
+
background: var(--primary);
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
.toggle input:checked + .toggle-slider::before {
|
|
4636
|
+
transform: translateX(18px);
|
|
4637
|
+
}
|
|
4638
|
+
|
|
4639
|
+
.toggle-label {
|
|
4640
|
+
font-size: 14px;
|
|
4641
|
+
font-weight: 500;
|
|
4642
|
+
}
|
|
4643
|
+
|
|
4644
|
+
/* Role cards */
|
|
4645
|
+
.role-grid {
|
|
4646
|
+
display: grid;
|
|
4647
|
+
grid-template-columns: 1fr 1fr;
|
|
4648
|
+
gap: 12px;
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
.role-card {
|
|
4652
|
+
border: 2px solid var(--border);
|
|
4653
|
+
border-radius: var(--radius);
|
|
4654
|
+
padding: 16px;
|
|
4655
|
+
cursor: pointer;
|
|
4656
|
+
transition: all var(--transition);
|
|
4657
|
+
position: relative;
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
.role-card:hover { border-color: var(--primary); }
|
|
4661
|
+
|
|
4662
|
+
.role-card.selected {
|
|
4663
|
+
border-color: var(--primary);
|
|
4664
|
+
background: var(--primary-subtle);
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
.role-card-check {
|
|
4668
|
+
position: absolute;
|
|
4669
|
+
top: 12px; right: 12px;
|
|
4670
|
+
width: 20px; height: 20px;
|
|
4671
|
+
border: 2px solid var(--border);
|
|
4672
|
+
border-radius: 4px;
|
|
4673
|
+
display: flex; align-items: center; justify-content: center;
|
|
4674
|
+
font-size: 12px;
|
|
4675
|
+
color: transparent;
|
|
4676
|
+
transition: all var(--transition);
|
|
4677
|
+
}
|
|
4678
|
+
|
|
4679
|
+
.role-card.selected .role-card-check {
|
|
4680
|
+
background: var(--primary);
|
|
4681
|
+
border-color: var(--primary);
|
|
4682
|
+
color: white;
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
.role-name {
|
|
4686
|
+
font-weight: 600;
|
|
4687
|
+
font-size: 14px;
|
|
4688
|
+
margin-bottom: 4px;
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
.role-desc {
|
|
4692
|
+
font-size: 12px;
|
|
4693
|
+
color: var(--text-muted);
|
|
4694
|
+
margin-bottom: 8px;
|
|
4695
|
+
}
|
|
4696
|
+
|
|
4697
|
+
.role-tags {
|
|
4698
|
+
display: flex;
|
|
4699
|
+
flex-wrap: wrap;
|
|
4700
|
+
gap: 4px;
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4703
|
+
.role-tag {
|
|
4704
|
+
font-size: 11px;
|
|
4705
|
+
padding: 1px 6px;
|
|
4706
|
+
border-radius: 3px;
|
|
4707
|
+
background: var(--bg-surface-alt);
|
|
4708
|
+
color: var(--text-muted);
|
|
4709
|
+
font-family: var(--font-mono);
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
.role-details {
|
|
4713
|
+
display: none;
|
|
4714
|
+
margin-top: 12px;
|
|
4715
|
+
padding-top: 12px;
|
|
4716
|
+
border-top: 1px solid var(--border);
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
.role-card.selected .role-details { display: block; }
|
|
4720
|
+
|
|
4721
|
+
/* Chip selector */
|
|
4722
|
+
.chip-group {
|
|
4723
|
+
display: flex;
|
|
4724
|
+
gap: 6px;
|
|
4725
|
+
flex-wrap: wrap;
|
|
4726
|
+
margin-bottom: 12px;
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
.chip {
|
|
4730
|
+
padding: 5px 14px;
|
|
4731
|
+
border: 1px solid var(--border);
|
|
4732
|
+
border-radius: 16px;
|
|
4733
|
+
font-size: 13px;
|
|
4734
|
+
cursor: pointer;
|
|
4735
|
+
transition: all var(--transition);
|
|
4736
|
+
background: var(--bg-surface);
|
|
4737
|
+
color: var(--text);
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
.chip:hover { border-color: var(--primary); }
|
|
4741
|
+
|
|
4742
|
+
.chip.active {
|
|
4743
|
+
background: var(--primary);
|
|
4744
|
+
border-color: var(--primary);
|
|
4745
|
+
color: var(--text-inverse);
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
/* Pattern list */
|
|
4749
|
+
.pattern-list { margin-top: 12px; }
|
|
4750
|
+
|
|
4751
|
+
.pattern-item {
|
|
4752
|
+
display: grid;
|
|
4753
|
+
grid-template-columns: 1fr 1.5fr auto auto;
|
|
4754
|
+
gap: 8px;
|
|
4755
|
+
align-items: center;
|
|
4756
|
+
margin-bottom: 8px;
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
.pattern-item input, .pattern-item select {
|
|
4760
|
+
font-size: 13px;
|
|
4761
|
+
padding: 6px 8px;
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
.btn-remove {
|
|
4765
|
+
background: none;
|
|
4766
|
+
border: none;
|
|
4767
|
+
color: var(--danger);
|
|
4768
|
+
cursor: pointer;
|
|
4769
|
+
font-size: 18px;
|
|
4770
|
+
padding: 4px;
|
|
4771
|
+
line-height: 1;
|
|
4772
|
+
}
|
|
4773
|
+
|
|
4774
|
+
.btn-add {
|
|
4775
|
+
background: none;
|
|
4776
|
+
border: 1px dashed var(--border);
|
|
4777
|
+
border-radius: var(--radius-sm);
|
|
4778
|
+
padding: 6px 14px;
|
|
4779
|
+
color: var(--primary);
|
|
4780
|
+
cursor: pointer;
|
|
4781
|
+
font-size: 13px;
|
|
4782
|
+
font-weight: 500;
|
|
4783
|
+
transition: all var(--transition);
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4786
|
+
.btn-add:hover {
|
|
4787
|
+
border-color: var(--primary);
|
|
4788
|
+
background: var(--primary-subtle);
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4791
|
+
/* Allowlist */
|
|
4792
|
+
.allowlist-item {
|
|
4793
|
+
display: flex;
|
|
4794
|
+
gap: 8px;
|
|
4795
|
+
margin-bottom: 8px;
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4798
|
+
.allowlist-item input { flex: 1; font-size: 13px; padding: 6px 8px; }
|
|
4799
|
+
|
|
4800
|
+
/* Sink list */
|
|
4801
|
+
.sink-item {
|
|
4802
|
+
background: var(--bg-surface-alt);
|
|
4803
|
+
border-radius: var(--radius-sm);
|
|
4804
|
+
padding: 12px;
|
|
4805
|
+
margin-bottom: 8px;
|
|
4806
|
+
position: relative;
|
|
4807
|
+
}
|
|
4808
|
+
|
|
4809
|
+
.sink-item .btn-remove {
|
|
4810
|
+
position: absolute;
|
|
4811
|
+
top: 8px; right: 8px;
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
/* --- Right column: Preview --- */
|
|
4815
|
+
.preview-panel {
|
|
4816
|
+
background: var(--bg-surface-alt);
|
|
4817
|
+
border-left: 1px solid var(--border);
|
|
4818
|
+
padding: 24px;
|
|
4819
|
+
display: flex;
|
|
4820
|
+
flex-direction: column;
|
|
4821
|
+
position: sticky;
|
|
4822
|
+
top: 0;
|
|
4823
|
+
height: 100vh;
|
|
4824
|
+
overflow: hidden;
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4827
|
+
.preview-header {
|
|
4828
|
+
display: flex;
|
|
4829
|
+
align-items: center;
|
|
4830
|
+
justify-content: space-between;
|
|
4831
|
+
margin-bottom: 16px;
|
|
4832
|
+
flex-shrink: 0;
|
|
4833
|
+
}
|
|
4834
|
+
|
|
4835
|
+
.preview-header h3 {
|
|
4836
|
+
font-size: 14px;
|
|
4837
|
+
font-weight: 600;
|
|
4838
|
+
}
|
|
4839
|
+
|
|
4840
|
+
.preview-tabs {
|
|
4841
|
+
display: flex;
|
|
4842
|
+
gap: 4px;
|
|
4843
|
+
margin-bottom: 12px;
|
|
4844
|
+
flex-shrink: 0;
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4847
|
+
.preview-tab {
|
|
4848
|
+
padding: 4px 12px;
|
|
4849
|
+
font-size: 12px;
|
|
4850
|
+
border: 1px solid var(--border);
|
|
4851
|
+
border-radius: var(--radius-sm);
|
|
4852
|
+
background: var(--bg-surface);
|
|
4853
|
+
cursor: pointer;
|
|
4854
|
+
color: var(--text-muted);
|
|
4855
|
+
transition: all var(--transition);
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4858
|
+
.preview-tab.active {
|
|
4859
|
+
background: var(--primary);
|
|
4860
|
+
border-color: var(--primary);
|
|
4861
|
+
color: var(--text-inverse);
|
|
4862
|
+
}
|
|
4863
|
+
|
|
4864
|
+
.preview-content {
|
|
4865
|
+
flex: 1;
|
|
4866
|
+
overflow-y: auto;
|
|
4867
|
+
background: var(--bg-surface);
|
|
4868
|
+
border: 1px solid var(--border);
|
|
4869
|
+
border-radius: var(--radius);
|
|
4870
|
+
padding: 16px;
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
.preview-content pre {
|
|
4874
|
+
font-family: var(--font-mono);
|
|
4875
|
+
font-size: 12px;
|
|
4876
|
+
line-height: 1.5;
|
|
4877
|
+
white-space: pre;
|
|
4878
|
+
color: var(--text);
|
|
4879
|
+
margin: 0;
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
/* Bottom bar */
|
|
4883
|
+
.bottom-bar {
|
|
4884
|
+
position: fixed;
|
|
4885
|
+
bottom: 0;
|
|
4886
|
+
left: 0;
|
|
4887
|
+
right: 420px;
|
|
4888
|
+
padding: 16px 40px;
|
|
4889
|
+
background: var(--bg-surface);
|
|
4890
|
+
border-top: 1px solid var(--border);
|
|
4891
|
+
display: flex;
|
|
4892
|
+
align-items: center;
|
|
4893
|
+
justify-content: space-between;
|
|
4894
|
+
z-index: 100;
|
|
4895
|
+
box-shadow: 0 -2px 8px rgba(0,0,0,0.06);
|
|
4896
|
+
}
|
|
4897
|
+
|
|
4898
|
+
@media (max-width: 1024px) {
|
|
4899
|
+
.bottom-bar { right: 0; }
|
|
4900
|
+
}
|
|
4901
|
+
|
|
4902
|
+
.btn-primary {
|
|
4903
|
+
padding: 10px 28px;
|
|
4904
|
+
background: var(--primary);
|
|
4905
|
+
color: var(--text-inverse);
|
|
4906
|
+
border: none;
|
|
4907
|
+
border-radius: var(--radius);
|
|
4908
|
+
font-size: 14px;
|
|
4909
|
+
font-weight: 600;
|
|
4910
|
+
cursor: pointer;
|
|
4911
|
+
transition: background var(--transition);
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
.btn-primary:hover { background: var(--primary-hover); }
|
|
4915
|
+
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
4916
|
+
|
|
4917
|
+
.btn-secondary {
|
|
4918
|
+
padding: 10px 20px;
|
|
4919
|
+
background: var(--bg-surface);
|
|
4920
|
+
color: var(--text);
|
|
4921
|
+
border: 1px solid var(--border);
|
|
4922
|
+
border-radius: var(--radius);
|
|
4923
|
+
font-size: 14px;
|
|
4924
|
+
font-weight: 500;
|
|
4925
|
+
cursor: pointer;
|
|
4926
|
+
transition: all var(--transition);
|
|
4927
|
+
}
|
|
4928
|
+
|
|
4929
|
+
.btn-secondary:hover { border-color: var(--primary); color: var(--primary); }
|
|
4930
|
+
|
|
4931
|
+
.save-status {
|
|
4932
|
+
font-size: 13px;
|
|
4933
|
+
color: var(--text-muted);
|
|
4934
|
+
}
|
|
4935
|
+
|
|
4936
|
+
.save-status.success { color: var(--success); }
|
|
4937
|
+
.save-status.error { color: var(--danger); }
|
|
4938
|
+
|
|
4939
|
+
/* Toast */
|
|
4940
|
+
.toast {
|
|
4941
|
+
position: fixed;
|
|
4942
|
+
top: 20px;
|
|
4943
|
+
right: 20px;
|
|
4944
|
+
padding: 12px 20px;
|
|
4945
|
+
border-radius: var(--radius);
|
|
4946
|
+
font-size: 14px;
|
|
4947
|
+
font-weight: 500;
|
|
4948
|
+
z-index: 1000;
|
|
4949
|
+
box-shadow: var(--shadow-lg);
|
|
4950
|
+
transform: translateY(-10px);
|
|
4951
|
+
opacity: 0;
|
|
4952
|
+
transition: all 300ms ease;
|
|
4953
|
+
pointer-events: none;
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4956
|
+
.toast.visible { transform: translateY(0); opacity: 1; pointer-events: auto; }
|
|
4957
|
+
.toast.success { background: var(--success); color: white; }
|
|
4958
|
+
.toast.error { background: var(--danger); color: white; }
|
|
4959
|
+
|
|
4960
|
+
/* Helpers */
|
|
4961
|
+
.help-text {
|
|
4962
|
+
font-size: 12px;
|
|
4963
|
+
color: var(--text-muted);
|
|
4964
|
+
margin-top: 4px;
|
|
4965
|
+
}
|
|
4966
|
+
|
|
4967
|
+
.divider {
|
|
4968
|
+
border: none;
|
|
4969
|
+
border-top: 1px solid var(--border);
|
|
4970
|
+
margin: 16px 0;
|
|
4971
|
+
}
|
|
4972
|
+
|
|
4973
|
+
.hidden { display: none !important; }
|
|
4974
|
+
</style>
|
|
4975
|
+
</head>
|
|
4976
|
+
<body>
|
|
4977
|
+
<div class="layout">
|
|
4978
|
+
<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 LEFT: FORM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->
|
|
4979
|
+
<div class="form-column">
|
|
4980
|
+
<div class="logo">
|
|
4981
|
+
<div class="logo-icon">G</div>
|
|
4982
|
+
<div class="logo-text">Pi Governance <span>Setup Wizard</span></div>
|
|
4983
|
+
</div>
|
|
4984
|
+
|
|
4985
|
+
<h1>Configure your governance policy</h1>
|
|
4986
|
+
<p class="subtitle">
|
|
4987
|
+
AI coding agents are powerful but need guardrails. This wizard generates a
|
|
4988
|
+
<code>governance.yaml</code> and <code>governance-rules.yaml</code> to control
|
|
4989
|
+
tool access, bash safety, data-loss prevention, human approvals, and audit logging.
|
|
4990
|
+
</p>
|
|
4991
|
+
|
|
4992
|
+
<!-- \u2500\u2500 1. Roles \u2500\u2500 -->
|
|
4993
|
+
<div class="section" id="sec-roles">
|
|
4994
|
+
<div class="section-header" onclick="toggleSection('sec-roles')">
|
|
4995
|
+
<div class="section-icon" style="background:var(--primary-subtle);color:var(--primary)">👥</div>
|
|
4996
|
+
<h2>Roles</h2>
|
|
4997
|
+
<span class="section-badge badge-required">Required</span>
|
|
4998
|
+
<span class="section-chevron">▼</span>
|
|
4999
|
+
</div>
|
|
5000
|
+
<div class="section-body">
|
|
5001
|
+
<p class="help-text" style="margin-bottom:12px">Select the roles your team needs. Each role defines tool access, execution mode, and approval rules.</p>
|
|
5002
|
+
<div class="role-grid" id="role-grid"></div>
|
|
5003
|
+
</div>
|
|
5004
|
+
</div>
|
|
5005
|
+
|
|
5006
|
+
<!-- \u2500\u2500 2. DLP \u2500\u2500 -->
|
|
5007
|
+
<div class="section" id="sec-dlp">
|
|
5008
|
+
<div class="section-header" onclick="toggleSection('sec-dlp')">
|
|
5009
|
+
<div class="section-icon" style="background:var(--warning-subtle);color:var(--warning)">🛡</div>
|
|
5010
|
+
<h2>Data Loss Prevention</h2>
|
|
5011
|
+
<span class="section-badge badge-optional">Optional</span>
|
|
5012
|
+
<span class="section-chevron">▼</span>
|
|
5013
|
+
</div>
|
|
5014
|
+
<div class="section-body">
|
|
5015
|
+
<div class="toggle-row">
|
|
5016
|
+
<label class="toggle"><input type="checkbox" id="dlp-enabled" checked onchange="updatePreview()"><span class="toggle-slider"></span></label>
|
|
5017
|
+
<span class="toggle-label">Enable DLP scanning</span>
|
|
5018
|
+
</div>
|
|
5019
|
+
|
|
5020
|
+
<div id="dlp-options">
|
|
5021
|
+
<div class="field">
|
|
5022
|
+
<label>Default Mode</label>
|
|
5023
|
+
<div class="chip-group" id="dlp-mode">
|
|
5024
|
+
<span class="chip active" data-value="audit" onclick="selectChip(this)">Audit</span>
|
|
5025
|
+
<span class="chip" data-value="mask" onclick="selectChip(this)">Mask</span>
|
|
5026
|
+
<span class="chip" data-value="block" onclick="selectChip(this)">Block</span>
|
|
5027
|
+
</div>
|
|
5028
|
+
</div>
|
|
5029
|
+
|
|
5030
|
+
<div class="field-row">
|
|
5031
|
+
<div class="field">
|
|
5032
|
+
<label>On Input <span class="label-hint">(agent receives)</span></label>
|
|
5033
|
+
<select id="dlp-on-input" onchange="updatePreview()">
|
|
5034
|
+
<option value="">Use default mode</option>
|
|
5035
|
+
<option value="audit">Audit</option>
|
|
5036
|
+
<option value="mask">Mask</option>
|
|
5037
|
+
<option value="block" selected>Block</option>
|
|
5038
|
+
</select>
|
|
5039
|
+
</div>
|
|
5040
|
+
<div class="field">
|
|
5041
|
+
<label>On Output <span class="label-hint">(agent produces)</span></label>
|
|
5042
|
+
<select id="dlp-on-output" onchange="updatePreview()">
|
|
5043
|
+
<option value="">Use default mode</option>
|
|
5044
|
+
<option value="audit">Audit</option>
|
|
5045
|
+
<option value="mask" selected>Mask</option>
|
|
5046
|
+
<option value="block">Block</option>
|
|
5047
|
+
</select>
|
|
5048
|
+
</div>
|
|
5049
|
+
</div>
|
|
5050
|
+
|
|
5051
|
+
<hr class="divider">
|
|
5052
|
+
<label>Built-in Patterns</label>
|
|
5053
|
+
<div class="toggle-row" style="margin-top:6px">
|
|
5054
|
+
<label class="toggle"><input type="checkbox" id="dlp-secrets" checked onchange="updatePreview()"><span class="toggle-slider"></span></label>
|
|
5055
|
+
<span class="toggle-label">Secrets <span class="label-hint">(API keys, tokens, passwords)</span></span>
|
|
5056
|
+
</div>
|
|
5057
|
+
<div class="toggle-row">
|
|
5058
|
+
<label class="toggle"><input type="checkbox" id="dlp-pii" checked onchange="updatePreview()"><span class="toggle-slider"></span></label>
|
|
5059
|
+
<span class="toggle-label">PII <span class="label-hint">(emails, phone numbers, SSNs)</span></span>
|
|
5060
|
+
</div>
|
|
5061
|
+
|
|
5062
|
+
<hr class="divider">
|
|
5063
|
+
<label>Masking Options</label>
|
|
5064
|
+
<div class="field-row-3" style="margin-top:8px">
|
|
5065
|
+
<div class="field">
|
|
5066
|
+
<label>Strategy</label>
|
|
5067
|
+
<select id="dlp-mask-strategy" onchange="updatePreview()">
|
|
5068
|
+
<option value="partial" selected>Partial</option>
|
|
5069
|
+
<option value="full">Full</option>
|
|
5070
|
+
<option value="hash">Hash</option>
|
|
5071
|
+
</select>
|
|
5072
|
+
</div>
|
|
5073
|
+
<div class="field">
|
|
5074
|
+
<label>Show Chars</label>
|
|
5075
|
+
<input type="number" id="dlp-mask-show" value="4" min="0" onchange="updatePreview()">
|
|
5076
|
+
</div>
|
|
5077
|
+
<div class="field">
|
|
5078
|
+
<label>Placeholder</label>
|
|
5079
|
+
<input type="text" id="dlp-mask-placeholder" value="***" onchange="updatePreview()">
|
|
5080
|
+
</div>
|
|
5081
|
+
</div>
|
|
5082
|
+
|
|
5083
|
+
<hr class="divider">
|
|
5084
|
+
<label>Severity Threshold <span class="label-hint">(minimum severity to trigger DLP)</span></label>
|
|
5085
|
+
<div class="chip-group" id="dlp-severity" style="margin-top:6px">
|
|
5086
|
+
<span class="chip active" data-value="low" onclick="selectChip(this)">Low</span>
|
|
5087
|
+
<span class="chip" data-value="medium" onclick="selectChip(this)">Medium</span>
|
|
5088
|
+
<span class="chip" data-value="high" onclick="selectChip(this)">High</span>
|
|
5089
|
+
<span class="chip" data-value="critical" onclick="selectChip(this)">Critical</span>
|
|
5090
|
+
</div>
|
|
5091
|
+
|
|
5092
|
+
<hr class="divider">
|
|
5093
|
+
<label>Custom Patterns</label>
|
|
5094
|
+
<div class="pattern-list" id="dlp-custom-patterns"></div>
|
|
5095
|
+
<button class="btn-add" onclick="addCustomPattern()">+ Add pattern</button>
|
|
5096
|
+
|
|
5097
|
+
<hr class="divider">
|
|
5098
|
+
<label>Allowlist <span class="label-hint">(patterns to ignore)</span></label>
|
|
5099
|
+
<div id="dlp-allowlist"></div>
|
|
5100
|
+
<button class="btn-add" onclick="addAllowlistEntry()" style="margin-top:8px">+ Add entry</button>
|
|
5101
|
+
</div>
|
|
5102
|
+
</div>
|
|
5103
|
+
</div>
|
|
5104
|
+
|
|
5105
|
+
<!-- \u2500\u2500 3. Bash Classification \u2500\u2500 -->
|
|
5106
|
+
<div class="section" id="sec-bash">
|
|
5107
|
+
<div class="section-header" onclick="toggleSection('sec-bash')">
|
|
5108
|
+
<div class="section-icon" style="background:var(--danger-subtle);color:var(--danger)">💻</div>
|
|
5109
|
+
<h2>Bash Classification</h2>
|
|
5110
|
+
<span class="section-badge badge-optional">Optional</span>
|
|
5111
|
+
<span class="section-chevron">▼</span>
|
|
5112
|
+
</div>
|
|
5113
|
+
<div class="section-body">
|
|
5114
|
+
<p class="help-text" style="margin-bottom:12px">The built-in bash classifier categorizes commands by danger level. Adjust the threshold for auto-blocking.</p>
|
|
5115
|
+
<div class="field">
|
|
5116
|
+
<label>Auto-block Severity</label>
|
|
5117
|
+
<p class="help-text">Commands at or above this severity are blocked without HITL.</p>
|
|
5118
|
+
<div class="chip-group" id="bash-severity" style="margin-top:6px">
|
|
5119
|
+
<span class="chip" data-value="low" onclick="selectChip(this)">Low</span>
|
|
5120
|
+
<span class="chip" data-value="medium" onclick="selectChip(this)">Medium</span>
|
|
5121
|
+
<span class="chip active" data-value="high" onclick="selectChip(this)">High</span>
|
|
5122
|
+
<span class="chip" data-value="critical" onclick="selectChip(this)">Critical</span>
|
|
5123
|
+
</div>
|
|
5124
|
+
</div>
|
|
5125
|
+
</div>
|
|
5126
|
+
</div>
|
|
5127
|
+
|
|
5128
|
+
<!-- \u2500\u2500 4. HITL \u2500\u2500 -->
|
|
5129
|
+
<div class="section" id="sec-hitl">
|
|
5130
|
+
<div class="section-header" onclick="toggleSection('sec-hitl')">
|
|
5131
|
+
<div class="section-icon" style="background:var(--success-subtle);color:var(--success)">🧑</div>
|
|
5132
|
+
<h2>Human-in-the-Loop</h2>
|
|
5133
|
+
<span class="section-badge badge-required">Required</span>
|
|
5134
|
+
<span class="section-chevron">▼</span>
|
|
5135
|
+
</div>
|
|
5136
|
+
<div class="section-body">
|
|
5137
|
+
<div class="field">
|
|
5138
|
+
<label>Default Execution Mode</label>
|
|
5139
|
+
<div class="chip-group" id="hitl-mode">
|
|
5140
|
+
<span class="chip" data-value="autonomous" onclick="selectChip(this)">Autonomous</span>
|
|
5141
|
+
<span class="chip active" data-value="supervised" onclick="selectChip(this)">Supervised</span>
|
|
5142
|
+
<span class="chip" data-value="dry_run" onclick="selectChip(this)">Dry Run</span>
|
|
5143
|
+
</div>
|
|
5144
|
+
</div>
|
|
5145
|
+
<div class="field-row">
|
|
5146
|
+
<div class="field">
|
|
5147
|
+
<label>Approval Channel</label>
|
|
5148
|
+
<select id="hitl-channel" onchange="updatePreview()">
|
|
5149
|
+
<option value="cli" selected>CLI (terminal prompt)</option>
|
|
5150
|
+
<option value="webhook">Webhook</option>
|
|
5151
|
+
</select>
|
|
5152
|
+
</div>
|
|
5153
|
+
<div class="field">
|
|
5154
|
+
<label>Timeout <span class="label-hint">(seconds)</span></label>
|
|
5155
|
+
<input type="number" id="hitl-timeout" value="300" min="10" max="3600" onchange="updatePreview()">
|
|
5156
|
+
</div>
|
|
5157
|
+
</div>
|
|
5158
|
+
<div class="field hidden" id="hitl-webhook-field">
|
|
5159
|
+
<label>Webhook URL</label>
|
|
5160
|
+
<input type="text" id="hitl-webhook-url" placeholder="https://..." onchange="updatePreview()">
|
|
5161
|
+
</div>
|
|
5162
|
+
</div>
|
|
5163
|
+
</div>
|
|
5164
|
+
|
|
5165
|
+
<!-- \u2500\u2500 5. Audit \u2500\u2500 -->
|
|
5166
|
+
<div class="section" id="sec-audit">
|
|
5167
|
+
<div class="section-header" onclick="toggleSection('sec-audit')">
|
|
5168
|
+
<div class="section-icon" style="background:var(--primary-subtle);color:var(--primary)">📝</div>
|
|
5169
|
+
<h2>Audit Logging</h2>
|
|
5170
|
+
<span class="section-badge badge-required">Required</span>
|
|
5171
|
+
<span class="section-chevron">▼</span>
|
|
5172
|
+
</div>
|
|
5173
|
+
<div class="section-body">
|
|
5174
|
+
<p class="help-text" style="margin-bottom:12px">All governance events are logged to one or more sinks.</p>
|
|
5175
|
+
<div id="audit-sinks"></div>
|
|
5176
|
+
<button class="btn-add" onclick="addAuditSink()" style="margin-top:8px">+ Add sink</button>
|
|
5177
|
+
</div>
|
|
5178
|
+
</div>
|
|
5179
|
+
|
|
5180
|
+
<!-- \u2500\u2500 6. Auth \u2500\u2500 -->
|
|
5181
|
+
<div class="section collapsed" id="sec-auth">
|
|
5182
|
+
<div class="section-header" onclick="toggleSection('sec-auth')">
|
|
5183
|
+
<div class="section-icon" style="background:var(--bg-surface-alt);color:var(--text-muted)">🔑</div>
|
|
5184
|
+
<h2>Authentication</h2>
|
|
5185
|
+
<span class="section-badge badge-optional">Optional</span>
|
|
5186
|
+
<span class="section-chevron">▼</span>
|
|
5187
|
+
</div>
|
|
5188
|
+
<div class="section-body">
|
|
5189
|
+
<div class="field">
|
|
5190
|
+
<label>Provider</label>
|
|
5191
|
+
<div class="chip-group" id="auth-provider">
|
|
5192
|
+
<span class="chip active" data-value="env" onclick="selectChip(this)">Environment Vars</span>
|
|
5193
|
+
<span class="chip" data-value="local" onclick="selectChip(this)">Local File</span>
|
|
5194
|
+
<span class="chip" data-value="oidc" onclick="selectChip(this)">OIDC</span>
|
|
5195
|
+
</div>
|
|
5196
|
+
</div>
|
|
5197
|
+
<div id="auth-env-fields">
|
|
5198
|
+
<div class="field-row-3">
|
|
5199
|
+
<div class="field">
|
|
5200
|
+
<label>User Var</label>
|
|
5201
|
+
<input type="text" id="auth-user-var" value="GRWND_USER" onchange="updatePreview()">
|
|
5202
|
+
</div>
|
|
5203
|
+
<div class="field">
|
|
5204
|
+
<label>Role Var</label>
|
|
5205
|
+
<input type="text" id="auth-role-var" value="GRWND_ROLE" onchange="updatePreview()">
|
|
5206
|
+
</div>
|
|
5207
|
+
<div class="field">
|
|
5208
|
+
<label>Org Unit Var</label>
|
|
5209
|
+
<input type="text" id="auth-org-unit-var" value="GRWND_ORG_UNIT" onchange="updatePreview()">
|
|
5210
|
+
</div>
|
|
5211
|
+
</div>
|
|
5212
|
+
</div>
|
|
5213
|
+
<div id="auth-local-fields" class="hidden">
|
|
5214
|
+
<div class="field">
|
|
5215
|
+
<label>Users File</label>
|
|
5216
|
+
<input type="text" id="auth-users-file" value="./users.yaml" onchange="updatePreview()">
|
|
5217
|
+
</div>
|
|
5218
|
+
</div>
|
|
5219
|
+
</div>
|
|
5220
|
+
</div>
|
|
5221
|
+
|
|
5222
|
+
<div style="height:80px"></div>
|
|
5223
|
+
</div>
|
|
5224
|
+
|
|
5225
|
+
<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 RIGHT: PREVIEW \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->
|
|
5226
|
+
<div class="preview-panel">
|
|
5227
|
+
<div class="preview-header">
|
|
5228
|
+
<h3>Live Preview</h3>
|
|
5229
|
+
</div>
|
|
5230
|
+
<div class="preview-tabs">
|
|
5231
|
+
<span class="preview-tab active" data-tab="governance" onclick="switchTab(this)">governance.yaml</span>
|
|
5232
|
+
<span class="preview-tab" data-tab="rules" onclick="switchTab(this)">governance-rules.yaml</span>
|
|
5233
|
+
</div>
|
|
5234
|
+
<div class="preview-content">
|
|
5235
|
+
<pre id="preview-yaml"></pre>
|
|
5236
|
+
</div>
|
|
5237
|
+
</div>
|
|
5238
|
+
</div>
|
|
5239
|
+
|
|
5240
|
+
<!-- \u2500\u2500 Bottom bar \u2500\u2500 -->
|
|
5241
|
+
<div class="bottom-bar">
|
|
5242
|
+
<span class="save-status" id="save-status"></span>
|
|
5243
|
+
<div style="display:flex;gap:10px">
|
|
5244
|
+
<button class="btn-secondary" onclick="handleClose()">Cancel</button>
|
|
5245
|
+
<button class="btn-primary" id="btn-save" onclick="handleSave()">Save Configuration</button>
|
|
5246
|
+
</div>
|
|
5247
|
+
</div>
|
|
5248
|
+
|
|
5249
|
+
<!-- Toast -->
|
|
5250
|
+
<div class="toast" id="toast"></div>
|
|
5251
|
+
|
|
5252
|
+
<script>
|
|
5253
|
+
// \u2500\u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5254
|
+
const PRESET_ROLES = {
|
|
5255
|
+
analyst: {
|
|
5256
|
+
label: 'Analyst',
|
|
5257
|
+
desc: 'Read-only access, every action requires approval.',
|
|
5258
|
+
allowed_tools: ['read','grep','find','ls'],
|
|
5259
|
+
blocked_tools: ['write','edit','bash'],
|
|
5260
|
+
prompt_template: 'analyst',
|
|
5261
|
+
execution_mode: 'supervised',
|
|
5262
|
+
human_approval: { required_for: ['all'] },
|
|
5263
|
+
token_budget_daily: 100000,
|
|
5264
|
+
allowed_paths: ['{{project_path}}/**'],
|
|
5265
|
+
blocked_paths: ['**/secrets/**', '**/.env*']
|
|
5266
|
+
},
|
|
5267
|
+
project_lead: {
|
|
5268
|
+
label: 'Project Lead',
|
|
5269
|
+
desc: 'Full tools, bash & write need human approval.',
|
|
5270
|
+
allowed_tools: ['read','write','edit','bash','grep','find','ls'],
|
|
5271
|
+
blocked_tools: [],
|
|
5272
|
+
prompt_template: 'project-lead',
|
|
5273
|
+
execution_mode: 'supervised',
|
|
5274
|
+
human_approval: { required_for: ['bash','write'], auto_approve: ['read','edit','grep','find','ls'] },
|
|
5275
|
+
token_budget_daily: 500000,
|
|
5276
|
+
allowed_paths: ['{{project_path}}/**'],
|
|
5277
|
+
blocked_paths: ['**/secrets/**', '**/.env*'],
|
|
5278
|
+
bash_overrides: { additional_blocked: ['sudo','ssh','curl.*\\\\|.*sh'] }
|
|
5279
|
+
},
|
|
5280
|
+
admin: {
|
|
5281
|
+
label: 'Admin',
|
|
5282
|
+
desc: 'Full autonomous access, no approvals, unlimited budget.',
|
|
5283
|
+
allowed_tools: ['all'],
|
|
5284
|
+
blocked_tools: [],
|
|
5285
|
+
prompt_template: 'admin',
|
|
5286
|
+
execution_mode: 'autonomous',
|
|
5287
|
+
human_approval: { required_for: [] },
|
|
5288
|
+
token_budget_daily: -1,
|
|
5289
|
+
allowed_paths: ['**'],
|
|
5290
|
+
blocked_paths: []
|
|
5291
|
+
},
|
|
5292
|
+
auditor: {
|
|
5293
|
+
label: 'Auditor',
|
|
5294
|
+
desc: 'Dry-run: all calls logged, nothing executed.',
|
|
5295
|
+
allowed_tools: ['read','grep','find','ls'],
|
|
5296
|
+
blocked_tools: ['write','edit','bash'],
|
|
5297
|
+
prompt_template: 'analyst',
|
|
5298
|
+
execution_mode: 'dry_run',
|
|
5299
|
+
human_approval: { required_for: ['all'] },
|
|
5300
|
+
token_budget_daily: 50000,
|
|
5301
|
+
allowed_paths: ['**'],
|
|
5302
|
+
blocked_paths: ['**/secrets/**']
|
|
5303
|
+
}
|
|
5304
|
+
};
|
|
5305
|
+
|
|
5306
|
+
let selectedRoles = { analyst: false, project_lead: true, admin: false, auditor: false };
|
|
5307
|
+
let customPatterns = [];
|
|
5308
|
+
let allowlistEntries = [];
|
|
5309
|
+
let auditSinks = [{ type: 'jsonl', path: '~/.pi/agent/audit.jsonl' }];
|
|
5310
|
+
let activePreviewTab = 'governance';
|
|
5311
|
+
|
|
5312
|
+
// \u2500\u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5313
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
5314
|
+
renderRoles();
|
|
5315
|
+
renderAuditSinks();
|
|
5316
|
+
updatePreview();
|
|
5317
|
+
|
|
5318
|
+
try {
|
|
5319
|
+
const [configRes, defaultsRes] = await Promise.all([
|
|
5320
|
+
fetch('/api/config').catch(() => null),
|
|
5321
|
+
fetch('/api/defaults').catch(() => null)
|
|
5322
|
+
]);
|
|
5323
|
+
if (configRes && configRes.ok) {
|
|
5324
|
+
const cfg = await configRes.json();
|
|
5325
|
+
applyConfig(cfg);
|
|
5326
|
+
}
|
|
5327
|
+
if (defaultsRes && defaultsRes.ok) {
|
|
5328
|
+
const defs = await defaultsRes.json();
|
|
5329
|
+
if (!configRes || !configRes.ok) applyConfig(defs);
|
|
5330
|
+
}
|
|
5331
|
+
} catch (e) { /* use built-in defaults */ }
|
|
5332
|
+
|
|
5333
|
+
// watch for webhook channel
|
|
5334
|
+
document.getElementById('hitl-channel').addEventListener('change', (e) => {
|
|
5335
|
+
document.getElementById('hitl-webhook-field').classList.toggle('hidden', e.target.value !== 'webhook');
|
|
5336
|
+
updatePreview();
|
|
5337
|
+
});
|
|
5338
|
+
|
|
5339
|
+
// watch dlp toggle
|
|
5340
|
+
document.getElementById('dlp-enabled').addEventListener('change', (e) => {
|
|
5341
|
+
document.getElementById('dlp-options').classList.toggle('hidden', !e.target.checked);
|
|
5342
|
+
updatePreview();
|
|
5343
|
+
});
|
|
5344
|
+
});
|
|
5345
|
+
|
|
5346
|
+
// \u2500\u2500\u2500 Apply Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5347
|
+
function applyConfig(cfg) {
|
|
5348
|
+
if (!cfg) return;
|
|
5349
|
+
|
|
5350
|
+
// Auth
|
|
5351
|
+
if (cfg.auth) {
|
|
5352
|
+
setChipValue('auth-provider', cfg.auth.provider || 'env');
|
|
5353
|
+
if (cfg.auth.env) {
|
|
5354
|
+
if (cfg.auth.env.user_var) document.getElementById('auth-user-var').value = cfg.auth.env.user_var;
|
|
5355
|
+
if (cfg.auth.env.role_var) document.getElementById('auth-role-var').value = cfg.auth.env.role_var;
|
|
5356
|
+
if (cfg.auth.env.org_unit_var) document.getElementById('auth-org-unit-var').value = cfg.auth.env.org_unit_var;
|
|
5357
|
+
}
|
|
5358
|
+
if (cfg.auth.local && cfg.auth.local.users_file) {
|
|
5359
|
+
document.getElementById('auth-users-file').value = cfg.auth.local.users_file;
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
|
|
5363
|
+
// HITL
|
|
5364
|
+
if (cfg.hitl) {
|
|
5365
|
+
if (cfg.hitl.default_mode) setChipValue('hitl-mode', cfg.hitl.default_mode);
|
|
5366
|
+
if (cfg.hitl.approval_channel) {
|
|
5367
|
+
document.getElementById('hitl-channel').value = cfg.hitl.approval_channel;
|
|
5368
|
+
document.getElementById('hitl-webhook-field').classList.toggle('hidden', cfg.hitl.approval_channel !== 'webhook');
|
|
5369
|
+
}
|
|
5370
|
+
if (cfg.hitl.timeout_seconds) document.getElementById('hitl-timeout').value = cfg.hitl.timeout_seconds;
|
|
5371
|
+
if (cfg.hitl.webhook && cfg.hitl.webhook.url) document.getElementById('hitl-webhook-url').value = cfg.hitl.webhook.url;
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
// DLP
|
|
5375
|
+
if (cfg.dlp) {
|
|
5376
|
+
document.getElementById('dlp-enabled').checked = cfg.dlp.enabled !== false;
|
|
5377
|
+
document.getElementById('dlp-options').classList.toggle('hidden', !cfg.dlp.enabled);
|
|
5378
|
+
if (cfg.dlp.mode) setChipValue('dlp-mode', cfg.dlp.mode);
|
|
5379
|
+
if (cfg.dlp.on_input) document.getElementById('dlp-on-input').value = cfg.dlp.on_input;
|
|
5380
|
+
if (cfg.dlp.on_output) document.getElementById('dlp-on-output').value = cfg.dlp.on_output;
|
|
5381
|
+
if (cfg.dlp.masking) {
|
|
5382
|
+
if (cfg.dlp.masking.strategy) document.getElementById('dlp-mask-strategy').value = cfg.dlp.masking.strategy;
|
|
5383
|
+
if (cfg.dlp.masking.show_chars != null) document.getElementById('dlp-mask-show').value = cfg.dlp.masking.show_chars;
|
|
5384
|
+
if (cfg.dlp.masking.placeholder) document.getElementById('dlp-mask-placeholder').value = cfg.dlp.masking.placeholder;
|
|
5385
|
+
}
|
|
5386
|
+
if (cfg.dlp.severity_threshold) setChipValue('dlp-severity', cfg.dlp.severity_threshold);
|
|
5387
|
+
if (cfg.dlp.built_in) {
|
|
5388
|
+
if (cfg.dlp.built_in.secrets != null) document.getElementById('dlp-secrets').checked = cfg.dlp.built_in.secrets;
|
|
5389
|
+
if (cfg.dlp.built_in.pii != null) document.getElementById('dlp-pii').checked = cfg.dlp.built_in.pii;
|
|
5390
|
+
}
|
|
5391
|
+
if (cfg.dlp.custom_patterns) {
|
|
5392
|
+
customPatterns = cfg.dlp.custom_patterns;
|
|
5393
|
+
renderCustomPatterns();
|
|
5394
|
+
}
|
|
5395
|
+
if (cfg.dlp.allowlist) {
|
|
5396
|
+
allowlistEntries = cfg.dlp.allowlist.map(e => e.pattern || e);
|
|
5397
|
+
renderAllowlist();
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5401
|
+
// Audit
|
|
5402
|
+
if (cfg.audit && cfg.audit.sinks) {
|
|
5403
|
+
auditSinks = cfg.audit.sinks;
|
|
5404
|
+
renderAuditSinks();
|
|
5405
|
+
}
|
|
5406
|
+
|
|
5407
|
+
updatePreview();
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
// \u2500\u2500\u2500 Roles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5411
|
+
function renderRoles() {
|
|
5412
|
+
const grid = document.getElementById('role-grid');
|
|
5413
|
+
grid.innerHTML = '';
|
|
5414
|
+
for (const [key, role] of Object.entries(PRESET_ROLES)) {
|
|
5415
|
+
const sel = selectedRoles[key];
|
|
5416
|
+
const card = document.createElement('div');
|
|
5417
|
+
card.className = 'role-card' + (sel ? ' selected' : '');
|
|
5418
|
+
card.dataset.role = key;
|
|
5419
|
+
card.onclick = () => { toggleRole(key); };
|
|
5420
|
+
card.innerHTML =
|
|
5421
|
+
'<div class="role-card-check">✓</div>' +
|
|
5422
|
+
'<div class="role-name">' + role.label + '</div>' +
|
|
5423
|
+
'<div class="role-desc">' + role.desc + '</div>' +
|
|
5424
|
+
'<div class="role-tags">' +
|
|
5425
|
+
'<span class="role-tag">' + role.execution_mode + '</span>' +
|
|
5426
|
+
'<span class="role-tag">' + (role.token_budget_daily === -1 ? 'unlimited' : role.token_budget_daily.toLocaleString()) + ' budget</span>' +
|
|
5427
|
+
'</div>' +
|
|
5428
|
+
'<div class="role-details">' +
|
|
5429
|
+
'<div class="field"><label>Allowed Tools</label>' +
|
|
5430
|
+
'<input type="text" value="' + role.allowed_tools.join(', ') + '" onchange="updateRoleField(\\'' + key + '\\', \\'allowed_tools\\', this.value)" onclick="event.stopPropagation()">' +
|
|
5431
|
+
'</div>' +
|
|
5432
|
+
'<div class="field"><label>Blocked Tools</label>' +
|
|
5433
|
+
'<input type="text" value="' + role.blocked_tools.join(', ') + '" onchange="updateRoleField(\\'' + key + '\\', \\'blocked_tools\\', this.value)" onclick="event.stopPropagation()">' +
|
|
5434
|
+
'</div>' +
|
|
5435
|
+
'<div class="field"><label>Execution Mode</label>' +
|
|
5436
|
+
'<select onchange="updateRoleField(\\'' + key + '\\', \\'execution_mode\\', this.value)" onclick="event.stopPropagation()">' +
|
|
5437
|
+
'<option value="supervised"' + (role.execution_mode === 'supervised' ? ' selected' : '') + '>Supervised</option>' +
|
|
5438
|
+
'<option value="autonomous"' + (role.execution_mode === 'autonomous' ? ' selected' : '') + '>Autonomous</option>' +
|
|
5439
|
+
'<option value="dry_run"' + (role.execution_mode === 'dry_run' ? ' selected' : '') + '>Dry Run</option>' +
|
|
5440
|
+
'</select>' +
|
|
5441
|
+
'</div>' +
|
|
5442
|
+
'<div class="field"><label>Token Budget Daily</label>' +
|
|
5443
|
+
'<input type="number" value="' + role.token_budget_daily + '" onchange="updateRoleField(\\'' + key + '\\', \\'token_budget_daily\\', parseInt(this.value))" onclick="event.stopPropagation()">' +
|
|
5444
|
+
'</div>' +
|
|
5445
|
+
'</div>';
|
|
5446
|
+
grid.appendChild(card);
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
|
|
5450
|
+
function toggleRole(key) {
|
|
5451
|
+
selectedRoles[key] = !selectedRoles[key];
|
|
5452
|
+
renderRoles();
|
|
5453
|
+
updatePreview();
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
function updateRoleField(key, field, value) {
|
|
5457
|
+
if (field === 'allowed_tools' || field === 'blocked_tools') {
|
|
5458
|
+
PRESET_ROLES[key][field] = value.split(',').map(s => s.trim()).filter(Boolean);
|
|
5459
|
+
} else {
|
|
5460
|
+
PRESET_ROLES[key][field] = value;
|
|
5461
|
+
}
|
|
5462
|
+
updatePreview();
|
|
5463
|
+
}
|
|
5464
|
+
|
|
5465
|
+
// \u2500\u2500\u2500 Section Toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5466
|
+
function toggleSection(id) {
|
|
5467
|
+
document.getElementById(id).classList.toggle('collapsed');
|
|
5468
|
+
}
|
|
5469
|
+
|
|
5470
|
+
// \u2500\u2500\u2500 Chip Groups \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5471
|
+
function selectChip(el) {
|
|
5472
|
+
const group = el.parentElement;
|
|
5473
|
+
group.querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
|
|
5474
|
+
el.classList.add('active');
|
|
5475
|
+
|
|
5476
|
+
// Auth provider visibility
|
|
5477
|
+
if (group.id === 'auth-provider') {
|
|
5478
|
+
const val = el.dataset.value;
|
|
5479
|
+
document.getElementById('auth-env-fields').classList.toggle('hidden', val !== 'env');
|
|
5480
|
+
document.getElementById('auth-local-fields').classList.toggle('hidden', val !== 'local');
|
|
5481
|
+
}
|
|
5482
|
+
|
|
5483
|
+
updatePreview();
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5486
|
+
function getChipValue(groupId) {
|
|
5487
|
+
const active = document.querySelector('#' + groupId + ' .chip.active');
|
|
5488
|
+
return active ? active.dataset.value : '';
|
|
5489
|
+
}
|
|
5490
|
+
|
|
5491
|
+
function setChipValue(groupId, value) {
|
|
5492
|
+
const group = document.getElementById(groupId);
|
|
5493
|
+
if (!group) return;
|
|
5494
|
+
group.querySelectorAll('.chip').forEach(c => {
|
|
5495
|
+
c.classList.toggle('active', c.dataset.value === value);
|
|
5496
|
+
});
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5499
|
+
// \u2500\u2500\u2500 Custom Patterns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5500
|
+
function addCustomPattern() {
|
|
5501
|
+
customPatterns.push({ name: '', pattern: '', severity: 'medium', action: 'audit' });
|
|
5502
|
+
renderCustomPatterns();
|
|
5503
|
+
}
|
|
5504
|
+
|
|
5505
|
+
function renderCustomPatterns() {
|
|
5506
|
+
const container = document.getElementById('dlp-custom-patterns');
|
|
5507
|
+
container.innerHTML = '';
|
|
5508
|
+
customPatterns.forEach((p, i) => {
|
|
5509
|
+
const div = document.createElement('div');
|
|
5510
|
+
div.className = 'pattern-item';
|
|
5511
|
+
div.innerHTML =
|
|
5512
|
+
'<input type="text" placeholder="Name" value="' + esc(p.name) + '" onchange="customPatterns[' + i + '].name=this.value;updatePreview()">' +
|
|
5513
|
+
'<input type="text" placeholder="Regex pattern" value="' + esc(p.pattern) + '" onchange="customPatterns[' + i + '].pattern=this.value;updatePreview()">' +
|
|
5514
|
+
'<select onchange="customPatterns[' + i + '].severity=this.value;updatePreview()">' +
|
|
5515
|
+
'<option value="low"' + (p.severity==='low'?' selected':'') + '>Low</option>' +
|
|
5516
|
+
'<option value="medium"' + (p.severity==='medium'?' selected':'') + '>Medium</option>' +
|
|
5517
|
+
'<option value="high"' + (p.severity==='high'?' selected':'') + '>High</option>' +
|
|
5518
|
+
'<option value="critical"' + (p.severity==='critical'?' selected':'') + '>Critical</option>' +
|
|
5519
|
+
'</select>' +
|
|
5520
|
+
'<button class="btn-remove" onclick="customPatterns.splice(' + i + ',1);renderCustomPatterns();updatePreview()">×</button>';
|
|
5521
|
+
container.appendChild(div);
|
|
5522
|
+
});
|
|
5523
|
+
}
|
|
5524
|
+
|
|
5525
|
+
// \u2500\u2500\u2500 Allowlist \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5526
|
+
function addAllowlistEntry() {
|
|
5527
|
+
allowlistEntries.push('');
|
|
5528
|
+
renderAllowlist();
|
|
5529
|
+
}
|
|
5530
|
+
|
|
5531
|
+
function renderAllowlist() {
|
|
5532
|
+
const container = document.getElementById('dlp-allowlist');
|
|
5533
|
+
container.innerHTML = '';
|
|
5534
|
+
allowlistEntries.forEach((entry, i) => {
|
|
5535
|
+
const div = document.createElement('div');
|
|
5536
|
+
div.className = 'allowlist-item';
|
|
5537
|
+
div.innerHTML =
|
|
5538
|
+
'<input type="text" placeholder="Pattern to allow" value="' + esc(entry) + '" onchange="allowlistEntries[' + i + ']=this.value;updatePreview()">' +
|
|
5539
|
+
'<button class="btn-remove" onclick="allowlistEntries.splice(' + i + ',1);renderAllowlist();updatePreview()">×</button>';
|
|
5540
|
+
container.appendChild(div);
|
|
5541
|
+
});
|
|
5542
|
+
}
|
|
5543
|
+
|
|
5544
|
+
// \u2500\u2500\u2500 Audit Sinks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5545
|
+
function addAuditSink() {
|
|
5546
|
+
auditSinks.push({ type: 'jsonl', path: '' });
|
|
5547
|
+
renderAuditSinks();
|
|
5548
|
+
}
|
|
5549
|
+
|
|
5550
|
+
function renderAuditSinks() {
|
|
5551
|
+
const container = document.getElementById('audit-sinks');
|
|
5552
|
+
container.innerHTML = '';
|
|
5553
|
+
auditSinks.forEach((sink, i) => {
|
|
5554
|
+
const div = document.createElement('div');
|
|
5555
|
+
div.className = 'sink-item';
|
|
5556
|
+
let inner = '<button class="btn-remove" onclick="auditSinks.splice(' + i + ',1);renderAuditSinks();updatePreview()">×</button>';
|
|
5557
|
+
inner += '<div class="field"><label>Sink Type</label>' +
|
|
5558
|
+
'<select onchange="auditSinks[' + i + '].type=this.value;renderAuditSinks();updatePreview()">' +
|
|
5559
|
+
'<option value="jsonl"' + (sink.type==='jsonl'?' selected':'') + '>JSONL File</option>' +
|
|
5560
|
+
'<option value="webhook"' + (sink.type==='webhook'?' selected':'') + '>Webhook</option>' +
|
|
5561
|
+
'<option value="postgres"' + (sink.type==='postgres'?' selected':'') + '>PostgreSQL</option>' +
|
|
5562
|
+
'</select></div>';
|
|
5563
|
+
if (sink.type === 'jsonl') {
|
|
5564
|
+
inner += '<div class="field"><label>File Path</label>' +
|
|
5565
|
+
'<input type="text" value="' + esc(sink.path || '') + '" placeholder="~/.pi/agent/audit.jsonl" ' +
|
|
5566
|
+
'onchange="auditSinks[' + i + '].path=this.value;updatePreview()"></div>';
|
|
5567
|
+
} else if (sink.type === 'webhook') {
|
|
5568
|
+
inner += '<div class="field"><label>Webhook URL</label>' +
|
|
5569
|
+
'<input type="text" value="' + esc(sink.url || '') + '" placeholder="https://..." ' +
|
|
5570
|
+
'onchange="auditSinks[' + i + '].url=this.value;updatePreview()"></div>';
|
|
5571
|
+
} else if (sink.type === 'postgres') {
|
|
5572
|
+
inner += '<div class="field"><label>Connection String</label>' +
|
|
5573
|
+
'<input type="text" value="' + esc(sink.connection || '') + '" placeholder="postgresql://..." ' +
|
|
5574
|
+
'onchange="auditSinks[' + i + '].connection=this.value;updatePreview()"></div>';
|
|
5575
|
+
}
|
|
5576
|
+
div.innerHTML = inner;
|
|
5577
|
+
container.appendChild(div);
|
|
5578
|
+
});
|
|
5579
|
+
}
|
|
5580
|
+
|
|
5581
|
+
// \u2500\u2500\u2500 Preview Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5582
|
+
function switchTab(el) {
|
|
5583
|
+
document.querySelectorAll('.preview-tab').forEach(t => t.classList.remove('active'));
|
|
5584
|
+
el.classList.add('active');
|
|
5585
|
+
activePreviewTab = el.dataset.tab;
|
|
5586
|
+
updatePreview();
|
|
5587
|
+
}
|
|
5588
|
+
|
|
5589
|
+
// \u2500\u2500\u2500 YAML Generator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5590
|
+
function toYaml(obj, indent) {
|
|
5591
|
+
indent = indent || 0;
|
|
5592
|
+
const pad = ' '.repeat(indent);
|
|
5593
|
+
let out = '';
|
|
5594
|
+
|
|
5595
|
+
if (Array.isArray(obj)) {
|
|
5596
|
+
if (obj.length === 0) return ' []\\n';
|
|
5597
|
+
for (const item of obj) {
|
|
5598
|
+
if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
|
|
5599
|
+
const keys = Object.keys(item);
|
|
5600
|
+
if (keys.length > 0) {
|
|
5601
|
+
out += pad + '- ' + keys[0] + ': ' + formatScalar(item[keys[0]]) + '\\n';
|
|
5602
|
+
for (let k = 1; k < keys.length; k++) {
|
|
5603
|
+
const val = item[keys[k]];
|
|
5604
|
+
if (typeof val === 'object' && val !== null) {
|
|
5605
|
+
out += pad + ' ' + keys[k] + ':\\n' + toYaml(val, indent + 2);
|
|
5606
|
+
} else {
|
|
5607
|
+
out += pad + ' ' + keys[k] + ': ' + formatScalar(val) + '\\n';
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
} else {
|
|
5612
|
+
out += pad + '- ' + formatScalar(item) + '\\n';
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
return out;
|
|
5616
|
+
}
|
|
5617
|
+
|
|
5618
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
5619
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
5620
|
+
if (val === undefined || val === null) continue;
|
|
5621
|
+
if (typeof val === 'object') {
|
|
5622
|
+
const yamlVal = toYaml(val, indent + 1);
|
|
5623
|
+
if (Array.isArray(val) && val.length === 0) {
|
|
5624
|
+
out += pad + key + ': []\\n';
|
|
5625
|
+
} else {
|
|
5626
|
+
out += pad + key + ':\\n' + yamlVal;
|
|
5627
|
+
}
|
|
5628
|
+
} else {
|
|
5629
|
+
out += pad + key + ': ' + formatScalar(val) + '\\n';
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
return out;
|
|
5633
|
+
}
|
|
5634
|
+
|
|
5635
|
+
return pad + formatScalar(obj) + '\\n';
|
|
5636
|
+
}
|
|
5637
|
+
|
|
5638
|
+
function formatScalar(val) {
|
|
5639
|
+
if (typeof val === 'boolean') return val ? 'true' : 'false';
|
|
5640
|
+
if (typeof val === 'number') return String(val);
|
|
5641
|
+
if (typeof val === 'string') {
|
|
5642
|
+
if (val === '') return "''";
|
|
5643
|
+
if (val === 'true' || val === 'false' || !isNaN(val)) return "'" + val + "'";
|
|
5644
|
+
if (/[:#{}\\[\\],&*?|\\->!%@]/.test(val) || val.includes('\\\\')) return "'" + val.replace(/'/g, "''") + "'";
|
|
5645
|
+
return val;
|
|
5646
|
+
}
|
|
5647
|
+
return String(val);
|
|
5648
|
+
}
|
|
5649
|
+
|
|
5650
|
+
// \u2500\u2500\u2500 Build Config Objects \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5651
|
+
function buildGovernanceConfig() {
|
|
5652
|
+
const cfg = {};
|
|
5653
|
+
|
|
5654
|
+
// Auth
|
|
5655
|
+
const authProvider = getChipValue('auth-provider');
|
|
5656
|
+
cfg.auth = { provider: authProvider };
|
|
5657
|
+
if (authProvider === 'env') {
|
|
5658
|
+
cfg.auth.env = {
|
|
5659
|
+
user_var: document.getElementById('auth-user-var').value || 'GRWND_USER',
|
|
5660
|
+
role_var: document.getElementById('auth-role-var').value || 'GRWND_ROLE',
|
|
5661
|
+
org_unit_var: document.getElementById('auth-org-unit-var').value || 'GRWND_ORG_UNIT'
|
|
5662
|
+
};
|
|
5663
|
+
} else if (authProvider === 'local') {
|
|
5664
|
+
cfg.auth.local = {
|
|
5665
|
+
users_file: document.getElementById('auth-users-file').value || './users.yaml'
|
|
5666
|
+
};
|
|
5667
|
+
}
|
|
5668
|
+
|
|
5669
|
+
// Policy
|
|
5670
|
+
cfg.policy = {
|
|
5671
|
+
engine: 'yaml',
|
|
5672
|
+
yaml: { rules_file: './governance-rules.yaml' }
|
|
5673
|
+
};
|
|
5674
|
+
|
|
5675
|
+
// HITL
|
|
5676
|
+
cfg.hitl = {
|
|
5677
|
+
default_mode: getChipValue('hitl-mode') || 'supervised',
|
|
5678
|
+
approval_channel: document.getElementById('hitl-channel').value,
|
|
5679
|
+
timeout_seconds: parseInt(document.getElementById('hitl-timeout').value) || 300
|
|
5680
|
+
};
|
|
5681
|
+
if (cfg.hitl.approval_channel === 'webhook') {
|
|
5682
|
+
const url = document.getElementById('hitl-webhook-url').value;
|
|
5683
|
+
if (url) cfg.hitl.webhook = { url: url };
|
|
5684
|
+
}
|
|
5685
|
+
|
|
5686
|
+
// Audit
|
|
5687
|
+
cfg.audit = { sinks: auditSinks.filter(s => {
|
|
5688
|
+
if (s.type === 'jsonl') return s.path;
|
|
5689
|
+
if (s.type === 'webhook') return s.url;
|
|
5690
|
+
if (s.type === 'postgres') return s.connection;
|
|
5691
|
+
return false;
|
|
5692
|
+
}).map(s => {
|
|
5693
|
+
if (s.type === 'jsonl') return { type: 'jsonl', path: s.path };
|
|
5694
|
+
if (s.type === 'webhook') return { type: 'webhook', url: s.url };
|
|
5695
|
+
if (s.type === 'postgres') return { type: 'postgres', connection: s.connection };
|
|
5696
|
+
return s;
|
|
5697
|
+
})};
|
|
5698
|
+
|
|
5699
|
+
// DLP
|
|
5700
|
+
if (document.getElementById('dlp-enabled').checked) {
|
|
5701
|
+
cfg.dlp = {
|
|
5702
|
+
enabled: true,
|
|
5703
|
+
mode: getChipValue('dlp-mode') || 'audit'
|
|
5704
|
+
};
|
|
5705
|
+
const onInput = document.getElementById('dlp-on-input').value;
|
|
5706
|
+
const onOutput = document.getElementById('dlp-on-output').value;
|
|
5707
|
+
if (onInput) cfg.dlp.on_input = onInput;
|
|
5708
|
+
if (onOutput) cfg.dlp.on_output = onOutput;
|
|
5709
|
+
cfg.dlp.masking = {
|
|
5710
|
+
strategy: document.getElementById('dlp-mask-strategy').value,
|
|
5711
|
+
show_chars: parseInt(document.getElementById('dlp-mask-show').value) || 4,
|
|
5712
|
+
placeholder: document.getElementById('dlp-mask-placeholder').value || '***'
|
|
5713
|
+
};
|
|
5714
|
+
cfg.dlp.severity_threshold = getChipValue('dlp-severity') || 'low';
|
|
5715
|
+
cfg.dlp.built_in = {
|
|
5716
|
+
secrets: document.getElementById('dlp-secrets').checked,
|
|
5717
|
+
pii: document.getElementById('dlp-pii').checked
|
|
5718
|
+
};
|
|
5719
|
+
const patterns = customPatterns.filter(p => p.name && p.pattern);
|
|
5720
|
+
if (patterns.length > 0) cfg.dlp.custom_patterns = patterns;
|
|
5721
|
+
const al = allowlistEntries.filter(Boolean);
|
|
5722
|
+
if (al.length > 0) cfg.dlp.allowlist = al.map(p => ({ pattern: p }));
|
|
5723
|
+
} else {
|
|
5724
|
+
cfg.dlp = { enabled: false };
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5727
|
+
return cfg;
|
|
5728
|
+
}
|
|
5729
|
+
|
|
5730
|
+
function buildRulesConfig() {
|
|
5731
|
+
const roles = {};
|
|
5732
|
+
for (const [key, sel] of Object.entries(selectedRoles)) {
|
|
5733
|
+
if (!sel) continue;
|
|
5734
|
+
const r = PRESET_ROLES[key];
|
|
5735
|
+
const role = {
|
|
5736
|
+
allowed_tools: r.allowed_tools,
|
|
5737
|
+
blocked_tools: r.blocked_tools,
|
|
5738
|
+
prompt_template: r.prompt_template,
|
|
5739
|
+
execution_mode: r.execution_mode,
|
|
5740
|
+
human_approval: r.human_approval,
|
|
5741
|
+
token_budget_daily: r.token_budget_daily,
|
|
5742
|
+
allowed_paths: r.allowed_paths,
|
|
5743
|
+
blocked_paths: r.blocked_paths
|
|
5744
|
+
};
|
|
5745
|
+
if (r.bash_overrides) role.bash_overrides = r.bash_overrides;
|
|
5746
|
+
roles[key] = role;
|
|
5747
|
+
}
|
|
5748
|
+
return { roles: roles };
|
|
5749
|
+
}
|
|
5750
|
+
|
|
5751
|
+
// \u2500\u2500\u2500 Update Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5752
|
+
function updatePreview() {
|
|
5753
|
+
const el = document.getElementById('preview-yaml');
|
|
5754
|
+
if (activePreviewTab === 'governance') {
|
|
5755
|
+
const cfg = buildGovernanceConfig();
|
|
5756
|
+
el.textContent = '# governance.yaml\\n# Generated by Pi Governance Setup Wizard\\n\\n' + toYaml(cfg);
|
|
5757
|
+
} else {
|
|
5758
|
+
const rules = buildRulesConfig();
|
|
5759
|
+
el.textContent = '# governance-rules.yaml\\n# Generated by Pi Governance Setup Wizard\\n\\n' + toYaml(rules);
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
|
|
5763
|
+
// \u2500\u2500\u2500 Save \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5764
|
+
async function handleSave() {
|
|
5765
|
+
const btn = document.getElementById('btn-save');
|
|
5766
|
+
const status = document.getElementById('save-status');
|
|
5767
|
+
btn.disabled = true;
|
|
5768
|
+
status.textContent = 'Saving...';
|
|
5769
|
+
status.className = 'save-status';
|
|
5770
|
+
|
|
5771
|
+
try {
|
|
5772
|
+
const payload = {
|
|
5773
|
+
governance: buildGovernanceConfig(),
|
|
5774
|
+
rules: buildRulesConfig()
|
|
5775
|
+
};
|
|
5776
|
+
const res = await fetch('/api/save', {
|
|
5777
|
+
method: 'POST',
|
|
5778
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5779
|
+
body: JSON.stringify(payload)
|
|
5780
|
+
});
|
|
5781
|
+
if (!res.ok) {
|
|
5782
|
+
const err = await res.text();
|
|
5783
|
+
throw new Error(err || 'Server error');
|
|
5784
|
+
}
|
|
5785
|
+
const result = await res.json();
|
|
5786
|
+
showToast('Configuration saved!', 'success');
|
|
5787
|
+
status.textContent = 'Saved: ' + (result.files || []).join(', ');
|
|
5788
|
+
status.className = 'save-status success';
|
|
5789
|
+
} catch (e) {
|
|
5790
|
+
showToast('Failed to save: ' + e.message, 'error');
|
|
5791
|
+
status.textContent = 'Error: ' + e.message;
|
|
5792
|
+
status.className = 'save-status error';
|
|
5793
|
+
} finally {
|
|
5794
|
+
btn.disabled = false;
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
|
|
5798
|
+
async function handleClose() {
|
|
5799
|
+
try { await fetch('/api/close', { method: 'POST' }); } catch (e) { /* ignore */ }
|
|
5800
|
+
window.close();
|
|
5801
|
+
}
|
|
5802
|
+
|
|
5803
|
+
// \u2500\u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5804
|
+
function showToast(msg, type) {
|
|
5805
|
+
const toast = document.getElementById('toast');
|
|
5806
|
+
toast.textContent = msg;
|
|
5807
|
+
toast.className = 'toast ' + type + ' visible';
|
|
5808
|
+
setTimeout(() => { toast.classList.remove('visible'); }, 3000);
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
// \u2500\u2500\u2500 Util \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
5812
|
+
function esc(s) {
|
|
5813
|
+
return String(s).replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>');
|
|
5814
|
+
}
|
|
5815
|
+
</script>
|
|
5816
|
+
</body>
|
|
5817
|
+
</html>`;
|
|
5818
|
+
|
|
5819
|
+
// src/lib/wizard/server.ts
|
|
5820
|
+
var AUTO_SHUTDOWN_MS = 10 * 60 * 1e3;
|
|
5821
|
+
var BUILTIN_ROLES = {
|
|
5822
|
+
analyst: {
|
|
5823
|
+
allowed_tools: ["read", "grep", "find", "ls"],
|
|
5824
|
+
blocked_tools: ["bash", "write", "edit"],
|
|
5825
|
+
hitl_mode: "dry_run"
|
|
5826
|
+
},
|
|
5827
|
+
project_lead: {
|
|
5828
|
+
allowed_tools: ["read", "write", "edit", "grep", "find", "ls", "bash"],
|
|
5829
|
+
blocked_tools: [],
|
|
5830
|
+
hitl_mode: "supervised"
|
|
5831
|
+
},
|
|
5832
|
+
admin: {
|
|
5833
|
+
allowed_tools: ["read", "write", "edit", "grep", "find", "ls", "bash"],
|
|
5834
|
+
blocked_tools: [],
|
|
5835
|
+
hitl_mode: "autonomous"
|
|
5836
|
+
},
|
|
5837
|
+
auditor: {
|
|
5838
|
+
allowed_tools: ["read", "grep", "find", "ls"],
|
|
5839
|
+
blocked_tools: ["bash", "write", "edit"],
|
|
5840
|
+
hitl_mode: "dry_run"
|
|
5841
|
+
}
|
|
5842
|
+
};
|
|
5843
|
+
function setCorsHeaders(res) {
|
|
5844
|
+
res.setHeader("Access-Control-Allow-Origin", "http://localhost");
|
|
5845
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
5846
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
5847
|
+
}
|
|
5848
|
+
function sendJson(res, status, data) {
|
|
5849
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
5850
|
+
res.end(JSON.stringify(data));
|
|
5851
|
+
}
|
|
5852
|
+
function readBody(req) {
|
|
5853
|
+
return new Promise((resolve2, reject) => {
|
|
5854
|
+
const chunks = [];
|
|
5855
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
5856
|
+
req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
|
|
5857
|
+
req.on("error", reject);
|
|
5858
|
+
});
|
|
5859
|
+
}
|
|
5860
|
+
function startWizardServer(options) {
|
|
5861
|
+
return new Promise((resolve2, reject) => {
|
|
5862
|
+
let shutdownTimer;
|
|
5863
|
+
const server = createServer((req, res) => {
|
|
5864
|
+
setCorsHeaders(res);
|
|
5865
|
+
if (req.method === "OPTIONS") {
|
|
5866
|
+
res.writeHead(204);
|
|
5867
|
+
res.end();
|
|
5868
|
+
return;
|
|
5869
|
+
}
|
|
5870
|
+
const url = req.url ?? "/";
|
|
5871
|
+
try {
|
|
5872
|
+
if (req.method === "GET" && url === "/") {
|
|
5873
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5874
|
+
res.end(WIZARD_HTML);
|
|
5875
|
+
return;
|
|
5876
|
+
}
|
|
5877
|
+
if (req.method === "GET" && url === "/api/config") {
|
|
5878
|
+
sendJson(res, 200, options.existingConfig ?? DEFAULTS);
|
|
5879
|
+
return;
|
|
5880
|
+
}
|
|
5881
|
+
if (req.method === "GET" && url === "/api/defaults") {
|
|
5882
|
+
sendJson(res, 200, { defaults: DEFAULTS, roles: BUILTIN_ROLES });
|
|
5883
|
+
return;
|
|
5884
|
+
}
|
|
5885
|
+
if (req.method === "POST" && url === "/api/save") {
|
|
5886
|
+
readBody(req).then((body) => {
|
|
5887
|
+
const parsed = JSON.parse(body);
|
|
5888
|
+
if (typeof parsed !== "object" || parsed === null || !("governance" in parsed)) {
|
|
5889
|
+
sendJson(res, 400, { error: 'Request body must include "governance" property' });
|
|
5890
|
+
return;
|
|
5891
|
+
}
|
|
5892
|
+
const payload = parsed;
|
|
5893
|
+
const governanceYaml = stringify(payload.governance);
|
|
5894
|
+
const piDir = join2(options.workingDirectory, ".pi");
|
|
5895
|
+
const governancePath = join2(piDir, "governance.yaml");
|
|
5896
|
+
mkdirSync(piDir, { recursive: true });
|
|
5897
|
+
writeFileSync(governancePath, governanceYaml, "utf-8");
|
|
5898
|
+
const files = [
|
|
5899
|
+
{ path: governancePath, content: governanceYaml }
|
|
5900
|
+
];
|
|
5901
|
+
if (payload.rules !== void 0) {
|
|
5902
|
+
const rulesYaml = stringify(payload.rules);
|
|
5903
|
+
const rulesPath = join2(options.workingDirectory, "governance-rules.yaml");
|
|
5904
|
+
writeFileSync(rulesPath, rulesYaml, "utf-8");
|
|
5905
|
+
files.push({ path: rulesPath, content: rulesYaml });
|
|
5906
|
+
}
|
|
5907
|
+
sendJson(res, 200, { ok: true, files: files.map((f) => f.path) });
|
|
5908
|
+
options.onComplete(files);
|
|
5909
|
+
}).catch((err) => {
|
|
5910
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5911
|
+
sendJson(res, 400, { error: `Invalid request body: ${message}` });
|
|
5912
|
+
});
|
|
5913
|
+
return;
|
|
5914
|
+
}
|
|
5915
|
+
if (req.method === "POST" && url === "/api/close") {
|
|
5916
|
+
sendJson(res, 200, { ok: true });
|
|
5917
|
+
setTimeout(() => {
|
|
5918
|
+
closeServer();
|
|
5919
|
+
}, 100);
|
|
5920
|
+
return;
|
|
5921
|
+
}
|
|
5922
|
+
sendJson(res, 404, { error: "Not found" });
|
|
5923
|
+
} catch (err) {
|
|
5924
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5925
|
+
options.onError(error);
|
|
5926
|
+
sendJson(res, 500, { error: "Internal server error" });
|
|
5927
|
+
}
|
|
5928
|
+
});
|
|
5929
|
+
function closeServer() {
|
|
5930
|
+
if (shutdownTimer !== void 0) {
|
|
5931
|
+
clearTimeout(shutdownTimer);
|
|
5932
|
+
shutdownTimer = void 0;
|
|
5933
|
+
}
|
|
5934
|
+
server.close();
|
|
5935
|
+
}
|
|
5936
|
+
server.on("error", (err) => {
|
|
5937
|
+
options.onError(err);
|
|
5938
|
+
reject(err);
|
|
5939
|
+
});
|
|
5940
|
+
server.listen(0, () => {
|
|
5941
|
+
const addr = server.address();
|
|
5942
|
+
if (addr === null || typeof addr === "string") {
|
|
5943
|
+
const err = new Error("Failed to get server address");
|
|
5944
|
+
options.onError(err);
|
|
5945
|
+
reject(err);
|
|
5946
|
+
return;
|
|
5947
|
+
}
|
|
5948
|
+
shutdownTimer = setTimeout(() => {
|
|
5949
|
+
closeServer();
|
|
5950
|
+
}, AUTO_SHUTDOWN_MS);
|
|
5951
|
+
resolve2({ port: addr.port, close: closeServer });
|
|
5952
|
+
});
|
|
5953
|
+
});
|
|
5954
|
+
}
|
|
4347
5955
|
export {
|
|
4348
5956
|
AuditLogger,
|
|
4349
5957
|
BashClassifier,
|
|
@@ -4372,6 +5980,7 @@ export {
|
|
|
4372
5980
|
createIdentityChain,
|
|
4373
5981
|
createPolicyEngine,
|
|
4374
5982
|
loadConfig,
|
|
4375
|
-
render as renderTemplate
|
|
5983
|
+
render as renderTemplate,
|
|
5984
|
+
startWizardServer
|
|
4376
5985
|
};
|
|
4377
5986
|
//# sourceMappingURL=index.js.map
|