@beastmode-develeap/beastmode 0.1.179 → 0.1.181
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/board.html +279 -31
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/web/board.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
17
|
<!--BOARD_DATA-->
|
|
18
|
-
<script>window.__BUILD_STAMP__ = "20260507-
|
|
18
|
+
<script>window.__BUILD_STAMP__ = "20260507-091132-6af0b67";</script>
|
|
19
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
21
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -1540,6 +1540,89 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
1540
1540
|
opacity: 0.4;
|
|
1541
1541
|
cursor: not-allowed;
|
|
1542
1542
|
}
|
|
1543
|
+
|
|
1544
|
+
/* Deploy modal styles */
|
|
1545
|
+
.deploy-modal-overlay {
|
|
1546
|
+
position: fixed;
|
|
1547
|
+
inset: 0;
|
|
1548
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1549
|
+
display: flex;
|
|
1550
|
+
align-items: center;
|
|
1551
|
+
justify-content: center;
|
|
1552
|
+
z-index: 1000;
|
|
1553
|
+
}
|
|
1554
|
+
.deploy-modal {
|
|
1555
|
+
background: var(--surface);
|
|
1556
|
+
border: 1px solid var(--border);
|
|
1557
|
+
border-radius: 12px;
|
|
1558
|
+
padding: 24px;
|
|
1559
|
+
min-width: 360px;
|
|
1560
|
+
max-width: 480px;
|
|
1561
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
1562
|
+
}
|
|
1563
|
+
.deploy-modal h3 {
|
|
1564
|
+
margin: 0 0 16px;
|
|
1565
|
+
}
|
|
1566
|
+
.deploy-modal label {
|
|
1567
|
+
display: block;
|
|
1568
|
+
font-size: 13px;
|
|
1569
|
+
color: var(--text-muted);
|
|
1570
|
+
margin: 12px 0 4px;
|
|
1571
|
+
}
|
|
1572
|
+
.deploy-modal select,
|
|
1573
|
+
.deploy-modal textarea {
|
|
1574
|
+
width: 100%;
|
|
1575
|
+
padding: 8px;
|
|
1576
|
+
border: 1px solid var(--border);
|
|
1577
|
+
border-radius: 6px;
|
|
1578
|
+
background: var(--bg);
|
|
1579
|
+
color: var(--text);
|
|
1580
|
+
font-size: 13px;
|
|
1581
|
+
box-sizing: border-box;
|
|
1582
|
+
}
|
|
1583
|
+
.deploy-modal textarea {
|
|
1584
|
+
resize: vertical;
|
|
1585
|
+
min-height: 60px;
|
|
1586
|
+
font-family: monospace;
|
|
1587
|
+
}
|
|
1588
|
+
.deploy-modal-actions {
|
|
1589
|
+
display: flex;
|
|
1590
|
+
justify-content: flex-end;
|
|
1591
|
+
gap: 8px;
|
|
1592
|
+
margin-top: 16px;
|
|
1593
|
+
}
|
|
1594
|
+
.deploy-modal-actions button {
|
|
1595
|
+
padding: 8px 16px;
|
|
1596
|
+
border: 1px solid var(--border);
|
|
1597
|
+
border-radius: 6px;
|
|
1598
|
+
background: var(--bg);
|
|
1599
|
+
color: var(--text);
|
|
1600
|
+
font-size: 13px;
|
|
1601
|
+
cursor: pointer;
|
|
1602
|
+
transition: background 0.15s;
|
|
1603
|
+
}
|
|
1604
|
+
.deploy-modal-actions button:hover {
|
|
1605
|
+
background: var(--bg-hover);
|
|
1606
|
+
}
|
|
1607
|
+
.deploy-modal-actions .btn-primary {
|
|
1608
|
+
background: var(--primary);
|
|
1609
|
+
color: white;
|
|
1610
|
+
border-color: var(--primary);
|
|
1611
|
+
}
|
|
1612
|
+
.deploy-modal-actions .btn-primary:hover {
|
|
1613
|
+
opacity: 0.9;
|
|
1614
|
+
}
|
|
1615
|
+
.deploy-modal-check {
|
|
1616
|
+
display: flex;
|
|
1617
|
+
align-items: center;
|
|
1618
|
+
gap: 6px;
|
|
1619
|
+
margin: 12px 0 0;
|
|
1620
|
+
font-size: 13px;
|
|
1621
|
+
}
|
|
1622
|
+
.deploy-modal-check input {
|
|
1623
|
+
cursor: pointer;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1543
1626
|
@keyframes pulse-dot {
|
|
1544
1627
|
0%, 100% { opacity: 1; }
|
|
1545
1628
|
50% { opacity: 0.5; }
|
|
@@ -2509,6 +2592,21 @@ input[type="range"]::-webkit-slider-thumb {
|
|
|
2509
2592
|
.badge-info { background: var(--info-subtle); color: var(--info); }
|
|
2510
2593
|
.badge-muted { background: var(--surface-elevated); color: var(--text-muted); }
|
|
2511
2594
|
.badge-accent { background: var(--accent-subtle); color: var(--accent); }
|
|
2595
|
+
.badge-restart {
|
|
2596
|
+
display: inline-block;
|
|
2597
|
+
padding: 2px 6px;
|
|
2598
|
+
border-radius: 3px;
|
|
2599
|
+
font-size: 10px;
|
|
2600
|
+
font-weight: 600;
|
|
2601
|
+
background: rgba(249, 115, 22, 0.15);
|
|
2602
|
+
color: #fb923c;
|
|
2603
|
+
border: 1px solid rgba(249, 115, 22, 0.3);
|
|
2604
|
+
margin-left: 6px;
|
|
2605
|
+
cursor: help;
|
|
2606
|
+
vertical-align: middle;
|
|
2607
|
+
text-transform: uppercase;
|
|
2608
|
+
letter-spacing: 0.5px;
|
|
2609
|
+
}
|
|
2512
2610
|
|
|
2513
2611
|
/* ================================================================
|
|
2514
2612
|
SKELETON LOADING
|
|
@@ -4711,6 +4809,147 @@ function CostPhaseTable({ itemId, boardParam }) {
|
|
|
4711
4809
|
`;
|
|
4712
4810
|
}
|
|
4713
4811
|
|
|
4812
|
+
// ── Deploy Modal ──
|
|
4813
|
+
|
|
4814
|
+
function DeployModal({ envName, refs, loading, item, selectedProject, onClose }) {
|
|
4815
|
+
const [selectedRef, setSelectedRef] = useState('main');
|
|
4816
|
+
const [dryRun, setDryRun] = useState(false);
|
|
4817
|
+
const [description, setDescription] = useState('');
|
|
4818
|
+
const [submitting, setSubmitting] = useState(false);
|
|
4819
|
+
|
|
4820
|
+
// Default-select main if available, else the first branch.
|
|
4821
|
+
useEffect(() => {
|
|
4822
|
+
const branches = (refs && refs.branches) || [];
|
|
4823
|
+
if (!branches.length) return;
|
|
4824
|
+
if (branches.includes('main')) {
|
|
4825
|
+
setSelectedRef('main');
|
|
4826
|
+
} else {
|
|
4827
|
+
setSelectedRef(branches[0]);
|
|
4828
|
+
}
|
|
4829
|
+
}, [refs && refs.branches && refs.branches.join(',')]);
|
|
4830
|
+
|
|
4831
|
+
// Escape key closes the modal — symmetric with overlay click.
|
|
4832
|
+
useEffect(() => {
|
|
4833
|
+
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
|
|
4834
|
+
window.addEventListener('keydown', onKey);
|
|
4835
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
4836
|
+
}, []);
|
|
4837
|
+
|
|
4838
|
+
const handleDeploy = async () => {
|
|
4839
|
+
if (!envName || submitting) return;
|
|
4840
|
+
setSubmitting(true);
|
|
4841
|
+
try {
|
|
4842
|
+
const proj = (selectedProject && selectedProject !== 'all')
|
|
4843
|
+
? selectedProject
|
|
4844
|
+
: (item && item.project_id) || 'beastmode';
|
|
4845
|
+
const boardParam = proj ? '?board=' + encodeURIComponent(proj) : '';
|
|
4846
|
+
const body = {
|
|
4847
|
+
environment: envName,
|
|
4848
|
+
project: proj,
|
|
4849
|
+
source_ref: selectedRef,
|
|
4850
|
+
dry_run: dryRun,
|
|
4851
|
+
description: description || undefined,
|
|
4852
|
+
};
|
|
4853
|
+
if (item && item.id) body.source_item_id = String(item.id);
|
|
4854
|
+
|
|
4855
|
+
const response = await fetch('/api/deploy/trigger' + boardParam, {
|
|
4856
|
+
method: 'POST',
|
|
4857
|
+
headers: { 'Content-Type': 'application/json' },
|
|
4858
|
+
body: JSON.stringify(body),
|
|
4859
|
+
});
|
|
4860
|
+
const data = await response.json();
|
|
4861
|
+
|
|
4862
|
+
if (response.status === 409) {
|
|
4863
|
+
alert(data.detail || 'A deploy to ' + envName + ' is already in progress.');
|
|
4864
|
+
} else if (!response.ok) {
|
|
4865
|
+
alert('Deploy failed: ' + (data.detail || ('HTTP ' + response.status)));
|
|
4866
|
+
} else if (data && data.id) {
|
|
4867
|
+
alert('Deploy task created: #' + data.id);
|
|
4868
|
+
onClose();
|
|
4869
|
+
}
|
|
4870
|
+
} catch (err) {
|
|
4871
|
+
alert('Deploy error: ' + err.message);
|
|
4872
|
+
} finally {
|
|
4873
|
+
setSubmitting(false);
|
|
4874
|
+
}
|
|
4875
|
+
};
|
|
4876
|
+
|
|
4877
|
+
const branches = (refs && refs.branches) || [];
|
|
4878
|
+
const tags = (refs && refs.tags) || [];
|
|
4879
|
+
|
|
4880
|
+
return html`
|
|
4881
|
+
<div
|
|
4882
|
+
class="deploy-modal-overlay"
|
|
4883
|
+
data-testid="deploy-modal"
|
|
4884
|
+
onClick=${(e) => e.target === e.currentTarget && onClose()}
|
|
4885
|
+
>
|
|
4886
|
+
<div class="deploy-modal">
|
|
4887
|
+
<h3>Deploy to ${envName}</h3>
|
|
4888
|
+
|
|
4889
|
+
<label>Git Ref</label>
|
|
4890
|
+
<select
|
|
4891
|
+
data-testid="deploy-ref-select"
|
|
4892
|
+
value=${selectedRef}
|
|
4893
|
+
onChange=${(e) => setSelectedRef(e.target.value)}
|
|
4894
|
+
disabled=${loading || submitting}
|
|
4895
|
+
>
|
|
4896
|
+
${loading ? html`<option>Loading…</option>` : null}
|
|
4897
|
+
${!loading && branches.length === 0 && tags.length === 0
|
|
4898
|
+
? html`<option>No refs available</option>`
|
|
4899
|
+
: null}
|
|
4900
|
+
${branches.length > 0 ? html`
|
|
4901
|
+
<optgroup label="Branches">
|
|
4902
|
+
${branches.map(b => html`<option value=${b}>${b}</option>`)}
|
|
4903
|
+
</optgroup>
|
|
4904
|
+
` : null}
|
|
4905
|
+
${tags.length > 0 ? html`
|
|
4906
|
+
<optgroup label="Tags">
|
|
4907
|
+
${tags.map(t => html`<option value=${t}>${t}</option>`)}
|
|
4908
|
+
</optgroup>
|
|
4909
|
+
` : null}
|
|
4910
|
+
</select>
|
|
4911
|
+
|
|
4912
|
+
<label>Description (optional)</label>
|
|
4913
|
+
<textarea
|
|
4914
|
+
data-testid="deploy-description"
|
|
4915
|
+
placeholder="Why are you deploying?"
|
|
4916
|
+
value=${description}
|
|
4917
|
+
onChange=${(e) => setDescription(e.target.value)}
|
|
4918
|
+
disabled=${submitting}
|
|
4919
|
+
></textarea>
|
|
4920
|
+
|
|
4921
|
+
<div class="deploy-modal-check">
|
|
4922
|
+
<input
|
|
4923
|
+
type="checkbox"
|
|
4924
|
+
id="dry-run-check"
|
|
4925
|
+
data-testid="deploy-dry-run"
|
|
4926
|
+
checked=${dryRun}
|
|
4927
|
+
onChange=${(e) => setDryRun(e.target.checked)}
|
|
4928
|
+
disabled=${submitting}
|
|
4929
|
+
/>
|
|
4930
|
+
<label for="dry-run-check" style="margin:0;cursor:pointer;">Dry run (no changes)</label>
|
|
4931
|
+
</div>
|
|
4932
|
+
|
|
4933
|
+
<div class="deploy-modal-actions">
|
|
4934
|
+
<button
|
|
4935
|
+
data-testid="deploy-cancel"
|
|
4936
|
+
onClick=${onClose}
|
|
4937
|
+
disabled=${submitting}
|
|
4938
|
+
>Cancel</button>
|
|
4939
|
+
<button
|
|
4940
|
+
class="btn-primary"
|
|
4941
|
+
data-testid="deploy-confirm"
|
|
4942
|
+
onClick=${handleDeploy}
|
|
4943
|
+
disabled=${submitting || loading}
|
|
4944
|
+
>
|
|
4945
|
+
${submitting ? 'Deploying...' : dryRun ? 'Test' : 'Deploy'}
|
|
4946
|
+
</button>
|
|
4947
|
+
</div>
|
|
4948
|
+
</div>
|
|
4949
|
+
</div>
|
|
4950
|
+
`;
|
|
4951
|
+
}
|
|
4952
|
+
|
|
4714
4953
|
// ── Item Detail Sidebar ──
|
|
4715
4954
|
|
|
4716
4955
|
function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
@@ -4726,6 +4965,7 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
4726
4965
|
const [loadingCost, setLoadingCost] = useState(true);
|
|
4727
4966
|
const [phaseData, setPhaseData] = useState([]);
|
|
4728
4967
|
const [envTimeline, setEnvTimeline] = useState(null);
|
|
4968
|
+
const [deployModal, setDeployModal] = useState(null);
|
|
4729
4969
|
const sidebarRef = useRef(null);
|
|
4730
4970
|
const topCommentRef = useRef(null);
|
|
4731
4971
|
|
|
@@ -5050,36 +5290,14 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5050
5290
|
onClick=${(e) => {
|
|
5051
5291
|
e.stopPropagation();
|
|
5052
5292
|
if (isDeploying) return;
|
|
5053
|
-
if (!confirm('Deploy current code to ' + envName + '?')) return;
|
|
5054
5293
|
const proj = (selectedProject && selectedProject !== 'all')
|
|
5055
5294
|
? selectedProject
|
|
5056
5295
|
: (item && item.project_id) || 'beastmode';
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
if (item && item.id) body.source_item_id = String(item.id);
|
|
5063
|
-
fetch('/api/deploy/trigger' + boardParam, {
|
|
5064
|
-
method: 'POST',
|
|
5065
|
-
headers: { 'Content-Type': 'application/json' },
|
|
5066
|
-
body: JSON.stringify(body),
|
|
5067
|
-
})
|
|
5068
|
-
.then(r => r.json().then(data => ({ ok: r.ok, status: r.status, data })))
|
|
5069
|
-
.then(({ ok, status, data }) => {
|
|
5070
|
-
if (status === 409) {
|
|
5071
|
-
alert(data.detail || 'A deploy to ' + envName + ' is already in progress.');
|
|
5072
|
-
return;
|
|
5073
|
-
}
|
|
5074
|
-
if (!ok) {
|
|
5075
|
-
alert('Deploy failed: ' + (data.detail || ('HTTP ' + status)));
|
|
5076
|
-
return;
|
|
5077
|
-
}
|
|
5078
|
-
if (data && data.id) {
|
|
5079
|
-
alert('Deploy task created: #' + data.id);
|
|
5080
|
-
}
|
|
5081
|
-
})
|
|
5082
|
-
.catch(err => alert('Deploy error: ' + err.message));
|
|
5296
|
+
setDeployModal({ envName, proj, refs: null, loading: true });
|
|
5297
|
+
fetch('/api/deploy/refs?project=' + encodeURIComponent(proj))
|
|
5298
|
+
.then(r => r.ok ? r.json() : { branches: [], tags: [] })
|
|
5299
|
+
.then(refs => setDeployModal(prev => prev ? { ...prev, refs, loading: false } : null))
|
|
5300
|
+
.catch(() => setDeployModal(prev => prev ? { ...prev, refs: { branches: [], tags: [] }, loading: false } : null));
|
|
5083
5301
|
}}
|
|
5084
5302
|
>🚀</button>
|
|
5085
5303
|
</div>
|
|
@@ -5136,6 +5354,14 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5136
5354
|
<${CommentBox} itemId=${item.id} onPosted=${refreshUpdates} />
|
|
5137
5355
|
</div>
|
|
5138
5356
|
</aside>
|
|
5357
|
+
${deployModal && html`<${DeployModal}
|
|
5358
|
+
envName=${deployModal.envName}
|
|
5359
|
+
refs=${deployModal.refs}
|
|
5360
|
+
loading=${deployModal.loading}
|
|
5361
|
+
item=${item}
|
|
5362
|
+
selectedProject=${selectedProject}
|
|
5363
|
+
onClose=${() => setDeployModal(null)}
|
|
5364
|
+
/>`}
|
|
5139
5365
|
`;
|
|
5140
5366
|
}
|
|
5141
5367
|
|
|
@@ -8175,11 +8401,17 @@ function HelpPage() {
|
|
|
8175
8401
|
// Settings Page
|
|
8176
8402
|
// ================================================================
|
|
8177
8403
|
|
|
8404
|
+
// Badge component for restart-required fields (shown next to section headers)
|
|
8405
|
+
function RestartBadge({field, restartFields}) {
|
|
8406
|
+
if (!restartFields || !restartFields.has(field)) return null;
|
|
8407
|
+
return html`<span class="badge-restart" title="Changing this field requires a daemon restart to take effect">restart required</span>`;
|
|
8408
|
+
}
|
|
8409
|
+
|
|
8178
8410
|
// Gap 15: opt-in anonymous telemetry status surface. Reads
|
|
8179
8411
|
// /api/telemetry/status (proxied to the board, which reads the daemon's
|
|
8180
8412
|
// last heartbeat). Always renders — shows "Disabled" with a red dot
|
|
8181
8413
|
// when telemetry is off, full metrics when on. See docs/telemetry.md.
|
|
8182
|
-
function TelemetrySettings() {
|
|
8414
|
+
function TelemetrySettings({restartFields}) {
|
|
8183
8415
|
const [status, setStatus] = useState(null);
|
|
8184
8416
|
const [loading, setLoading] = useState(true);
|
|
8185
8417
|
const [loadError, setLoadError] = useState(null);
|
|
@@ -8196,7 +8428,7 @@ function TelemetrySettings() {
|
|
|
8196
8428
|
|
|
8197
8429
|
return html`
|
|
8198
8430
|
<div class="settings-section">
|
|
8199
|
-
<h3>Telemetry
|
|
8431
|
+
<h3 style="display:flex;align-items:center;gap:8px">Telemetry <${RestartBadge} field="telemetry" restartFields=${restartFields} /></h3>
|
|
8200
8432
|
${loading ? html`<p style="font-size:13px;color:var(--text-muted);">Loading telemetry status...</p>` : null}
|
|
8201
8433
|
${loadError ? html`<p style="font-size:13px;color:var(--danger);">Telemetry status unavailable: ${loadError}</p>` : null}
|
|
8202
8434
|
${!loading && !loadError ? html`
|
|
@@ -8255,6 +8487,8 @@ function SettingsPage() {
|
|
|
8255
8487
|
const [success, setSuccess] = useState(null);
|
|
8256
8488
|
const [saving, setSaving] = useState(false);
|
|
8257
8489
|
|
|
8490
|
+
const [restartFields, setRestartFields] = useState(new Set());
|
|
8491
|
+
|
|
8258
8492
|
useEffect(() => {
|
|
8259
8493
|
api('GET', '/api/config')
|
|
8260
8494
|
.then(setConfig)
|
|
@@ -8262,6 +8496,20 @@ function SettingsPage() {
|
|
|
8262
8496
|
.finally(() => setLoading(false));
|
|
8263
8497
|
}, []);
|
|
8264
8498
|
|
|
8499
|
+
useEffect(() => {
|
|
8500
|
+
api('GET', '/api/daemon/reload-categories')
|
|
8501
|
+
.then(data => {
|
|
8502
|
+
const cats = data.categories || {};
|
|
8503
|
+
const fields = new Set(
|
|
8504
|
+
Object.entries(cats)
|
|
8505
|
+
.filter(([_, v]) => v === 'restart')
|
|
8506
|
+
.map(([k]) => k)
|
|
8507
|
+
);
|
|
8508
|
+
setRestartFields(fields);
|
|
8509
|
+
})
|
|
8510
|
+
.catch(() => {});
|
|
8511
|
+
}, []);
|
|
8512
|
+
|
|
8265
8513
|
const saveConfig = async (forceWrite = false) => {
|
|
8266
8514
|
try {
|
|
8267
8515
|
setSaving(true);
|
|
@@ -8696,7 +8944,7 @@ function SettingsPage() {
|
|
|
8696
8944
|
</div>
|
|
8697
8945
|
|
|
8698
8946
|
<!-- Telemetry Section (Gap 15) -->
|
|
8699
|
-
<${TelemetrySettings} />
|
|
8947
|
+
<${TelemetrySettings} restartFields=${restartFields} />
|
|
8700
8948
|
</div>
|
|
8701
8949
|
</div>
|
|
8702
8950
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
6af0b67cf1cb45e506a8f816517181c9ac1e8606
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
20260507-
|
|
1
|
+
20260507-091132-6af0b67
|