@beastmode-develeap/beastmode 0.1.180 → 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 +246 -33
- 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; }
|
|
@@ -4726,6 +4809,147 @@ function CostPhaseTable({ itemId, boardParam }) {
|
|
|
4726
4809
|
`;
|
|
4727
4810
|
}
|
|
4728
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
|
+
|
|
4729
4953
|
// ── Item Detail Sidebar ──
|
|
4730
4954
|
|
|
4731
4955
|
function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
@@ -4741,6 +4965,7 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
4741
4965
|
const [loadingCost, setLoadingCost] = useState(true);
|
|
4742
4966
|
const [phaseData, setPhaseData] = useState([]);
|
|
4743
4967
|
const [envTimeline, setEnvTimeline] = useState(null);
|
|
4968
|
+
const [deployModal, setDeployModal] = useState(null);
|
|
4744
4969
|
const sidebarRef = useRef(null);
|
|
4745
4970
|
const topCommentRef = useRef(null);
|
|
4746
4971
|
|
|
@@ -5065,36 +5290,14 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5065
5290
|
onClick=${(e) => {
|
|
5066
5291
|
e.stopPropagation();
|
|
5067
5292
|
if (isDeploying) return;
|
|
5068
|
-
if (!confirm('Deploy current code to ' + envName + '?')) return;
|
|
5069
5293
|
const proj = (selectedProject && selectedProject !== 'all')
|
|
5070
5294
|
? selectedProject
|
|
5071
5295
|
: (item && item.project_id) || 'beastmode';
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
if (item && item.id) body.source_item_id = String(item.id);
|
|
5078
|
-
fetch('/api/deploy/trigger' + boardParam, {
|
|
5079
|
-
method: 'POST',
|
|
5080
|
-
headers: { 'Content-Type': 'application/json' },
|
|
5081
|
-
body: JSON.stringify(body),
|
|
5082
|
-
})
|
|
5083
|
-
.then(r => r.json().then(data => ({ ok: r.ok, status: r.status, data })))
|
|
5084
|
-
.then(({ ok, status, data }) => {
|
|
5085
|
-
if (status === 409) {
|
|
5086
|
-
alert(data.detail || 'A deploy to ' + envName + ' is already in progress.');
|
|
5087
|
-
return;
|
|
5088
|
-
}
|
|
5089
|
-
if (!ok) {
|
|
5090
|
-
alert('Deploy failed: ' + (data.detail || ('HTTP ' + status)));
|
|
5091
|
-
return;
|
|
5092
|
-
}
|
|
5093
|
-
if (data && data.id) {
|
|
5094
|
-
alert('Deploy task created: #' + data.id);
|
|
5095
|
-
}
|
|
5096
|
-
})
|
|
5097
|
-
.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));
|
|
5098
5301
|
}}
|
|
5099
5302
|
>🚀</button>
|
|
5100
5303
|
</div>
|
|
@@ -5151,6 +5354,14 @@ function ItemDetailSidebar({ item, onClose, onStatusChange, selectedProject }) {
|
|
|
5151
5354
|
<${CommentBox} itemId=${item.id} onPosted=${refreshUpdates} />
|
|
5152
5355
|
</div>
|
|
5153
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
|
+
/>`}
|
|
5154
5365
|
`;
|
|
5155
5366
|
}
|
|
5156
5367
|
|
|
@@ -8190,15 +8401,16 @@ function HelpPage() {
|
|
|
8190
8401
|
// Settings Page
|
|
8191
8402
|
// ================================================================
|
|
8192
8403
|
|
|
8193
|
-
//
|
|
8194
|
-
// /api/telemetry/status (proxied to the board, which reads the daemon's
|
|
8195
|
-
// last heartbeat). Always renders — shows "Disabled" with a red dot
|
|
8196
|
-
// when telemetry is off, full metrics when on. See docs/telemetry.md.
|
|
8404
|
+
// Badge component for restart-required fields (shown next to section headers)
|
|
8197
8405
|
function RestartBadge({field, restartFields}) {
|
|
8198
8406
|
if (!restartFields || !restartFields.has(field)) return null;
|
|
8199
8407
|
return html`<span class="badge-restart" title="Changing this field requires a daemon restart to take effect">restart required</span>`;
|
|
8200
8408
|
}
|
|
8201
8409
|
|
|
8410
|
+
// Gap 15: opt-in anonymous telemetry status surface. Reads
|
|
8411
|
+
// /api/telemetry/status (proxied to the board, which reads the daemon's
|
|
8412
|
+
// last heartbeat). Always renders — shows "Disabled" with a red dot
|
|
8413
|
+
// when telemetry is off, full metrics when on. See docs/telemetry.md.
|
|
8202
8414
|
function TelemetrySettings({restartFields}) {
|
|
8203
8415
|
const [status, setStatus] = useState(null);
|
|
8204
8416
|
const [loading, setLoading] = useState(true);
|
|
@@ -8274,6 +8486,7 @@ function SettingsPage() {
|
|
|
8274
8486
|
const [error, setError] = useState(null);
|
|
8275
8487
|
const [success, setSuccess] = useState(null);
|
|
8276
8488
|
const [saving, setSaving] = useState(false);
|
|
8489
|
+
|
|
8277
8490
|
const [restartFields, setRestartFields] = useState(new Set());
|
|
8278
8491
|
|
|
8279
8492
|
useEffect(() => {
|
|
@@ -8286,7 +8499,7 @@ function SettingsPage() {
|
|
|
8286
8499
|
useEffect(() => {
|
|
8287
8500
|
api('GET', '/api/daemon/reload-categories')
|
|
8288
8501
|
.then(data => {
|
|
8289
|
-
const cats =
|
|
8502
|
+
const cats = data.categories || {};
|
|
8290
8503
|
const fields = new Set(
|
|
8291
8504
|
Object.entries(cats)
|
|
8292
8505
|
.filter(([_, v]) => v === 'restart')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
6af0b67cf1cb45e506a8f816517181c9ac1e8606
|
package/dist/web/build-stamp.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
20260507-
|
|
1
|
+
20260507-091132-6af0b67
|