gator-command 1.0.0__py3-none-any.whl
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.
- gator_command/__init__.py +2 -0
- gator_command/cli.py +137 -0
- gator_command/scripts/crawler.py +633 -0
- gator_command/scripts/dashboard/dashboard.css +982 -0
- gator_command/scripts/dashboard/dashboard.html +84 -0
- gator_command/scripts/dashboard/dashboard.js +419 -0
- gator_command/scripts/dashboard/views/audit.js +270 -0
- gator_command/scripts/dashboard/views/fleet.js +307 -0
- gator_command/scripts/dashboard/views/repo.js +599 -0
- gator_command/scripts/dashboard/views/settings.js +173 -0
- gator_command/scripts/dashboard/views/updates.js +308 -0
- gator_command/scripts/enforcer-prompt.md +22 -0
- gator_command/scripts/extract-claude-sessions.py +489 -0
- gator_command/scripts/extract-codex-sessions.py +477 -0
- gator_command/scripts/extract-gemini-sessions.py +410 -0
- gator_command/scripts/gator-audit.py +956 -0
- gator_command/scripts/gator-charter-draft.py +919 -0
- gator_command/scripts/gator-charter-lint.py +427 -0
- gator_command/scripts/gator-charter-verify.py +606 -0
- gator_command/scripts/gator-dashboard.py +1271 -0
- gator_command/scripts/gator-deploy.py +916 -0
- gator_command/scripts/gator-drift.py +569 -0
- gator_command/scripts/gator-enforce.py +82 -0
- gator_command/scripts/gator-fleet-intel.py +460 -0
- gator_command/scripts/gator-fleet-report.py +615 -0
- gator_command/scripts/gator-init-command-post.py +315 -0
- gator_command/scripts/gator-init.py +434 -0
- gator_command/scripts/gator-machine-id.py +153 -0
- gator_command/scripts/gator-policy-status.py +631 -0
- gator_command/scripts/gator-pulse.py +459 -0
- gator_command/scripts/gator-repo-status.py +649 -0
- gator_command/scripts/gator-session-common.py +372 -0
- gator_command/scripts/gator-session-sink.py +831 -0
- gator_command/scripts/gator-sessions.py +1244 -0
- gator_command/scripts/gator-update.py +615 -0
- gator_command/scripts/gator-version.py +38 -0
- gator_command/scripts/gator_core.py +489 -0
- gator_command/scripts/gator_remote.py +381 -0
- gator_command/scripts/gator_runtime.py +142 -0
- gator_command/scripts/gatorize-actions.sh +989 -0
- gator_command/scripts/gatorize-lib.sh +166 -0
- gator_command/scripts/gatorize-post.sh +394 -0
- gator_command/scripts/gatorize.py +1163 -0
- gator_command/scripts/gatorize.sh +185 -0
- gator_command/scripts/generate_markdown.py +212 -0
- gator_command/scripts/generate_wiki.py +424 -0
- gator_command/scripts/graph_health.py +780 -0
- gator_command/scripts/memex-lint.py +286 -0
- gator_command/scripts/memex-lint.sh +205 -0
- gator_command/scripts/memex.py +1472 -0
- gator_command/scripts/memex_formatters.py +191 -0
- gator_command/scripts/memex_state.py +236 -0
- gator_command/scripts/spawn.py +650 -0
- gator_command/templates/gator-starter/blueprints/README.md +32 -0
- gator_command/templates/gator-starter/charterignore +53 -0
- gator_command/templates/gator-starter/charters/README.md +178 -0
- gator_command/templates/gator-starter/charters/_template.md +31 -0
- gator_command/templates/gator-starter/commands/commit.md +33 -0
- gator_command/templates/gator-starter/commands/init.md +11 -0
- gator_command/templates/gator-starter/commands/update.md +5 -0
- gator_command/templates/gator-starter/constitution.md +165 -0
- gator_command/templates/gator-starter/field-guides/README.md +25 -0
- gator_command/templates/gator-starter/gator-start-up.md +119 -0
- gator_command/templates/gator-starter/procedures/charter-alignment.md +83 -0
- gator_command/templates/gator-starter/procedures/enforcer-review.md +317 -0
- gator_command/templates/gator-starter/procedures/field-guide-generation.md +176 -0
- gator_command/templates/gator-starter/procedures/knowledge-capture.md +57 -0
- gator_command/templates/gator-starter/procedures/significance-check.md +69 -0
- gator_command/templates/gator-starter/reference-notes/concierge-responses.md +535 -0
- gator_command/templates/gator-starter/reference-notes/dangerous-patterns.md +91 -0
- gator_command/templates/gator-starter/reference-notes/dashboard-operations.md +22 -0
- gator_command/templates/gator-starter/reference-notes/enforcer-configuration.md +232 -0
- gator_command/templates/gator-starter/reference-notes/example-project.md +289 -0
- gator_command/templates/gator-starter/reference-notes/failure-modes-and-self-correction.md +72 -0
- gator_command/templates/gator-starter/reference-notes/git-workflow.md +60 -0
- gator_command/templates/gator-starter/reference-notes/identity-and-ownership.md +37 -0
- gator_command/templates/gator-starter/reference-notes/refactor-approach.md +155 -0
- gator_command/templates/gator-starter/reference-notes/what-gator-requires-from-a-model.md +108 -0
- gator_command/templates/gator-starter/reference-notes/why-navigation-coding-feels-different.md +99 -0
- gator_command/templates/gator-starter/reference-notes/workflow-profiles.md +155 -0
- gator_command/templates/gator-starter/scripts/__pycache__/enforcer-review.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-approve.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-init.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-pre-commit.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/__pycache__/gator-update.cpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/enforcer-prompt.md +55 -0
- gator_command/templates/gator-starter/scripts/enforcer-review.py +1551 -0
- gator_command/templates/gator-starter/scripts/gator-approve.py +139 -0
- gator_command/templates/gator-starter/scripts/gator-enforce.py +82 -0
- gator_command/templates/gator-starter/scripts/gator-init.py +434 -0
- gator_command/templates/gator-starter/scripts/gator-pre-commit.py +2670 -0
- gator_command/templates/gator-starter/scripts/gator-pulse.py +459 -0
- gator_command/templates/gator-starter/scripts/gator-update.py +615 -0
- gator_command/templates/gator-starter/scripts/gator-version.py +38 -0
- gator_command/templates/gator-starter/scripts/gator_core.py +487 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/commit-msgcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/post-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/__pycache__/pre-commitcpython-313.pyc +0 -0
- gator_command/templates/gator-starter/scripts/hooks/commit-msg +5 -0
- gator_command/templates/gator-starter/scripts/hooks/post-commit +7 -0
- gator_command/templates/gator-starter/scripts/hooks/pre-commit +5 -0
- gator_command/templates/gator-starter/sessions/.gitignore +7 -0
- gator_command/templates/gator-starter/vault/.gitkeep +0 -0
- gator_command/templates/gator-starter/whiteboard.md +5 -0
- gator_command-1.0.0.dist-info/METADATA +122 -0
- gator_command-1.0.0.dist-info/RECORD +110 -0
- gator_command-1.0.0.dist-info/WHEEL +5 -0
- gator_command-1.0.0.dist-info/entry_points.txt +2 -0
- gator_command-1.0.0.dist-info/licenses/LICENSE +21 -0
- gator_command-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Gator Dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="dashboard.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app-shell">
|
|
11
|
+
|
|
12
|
+
<aside id="sidebar">
|
|
13
|
+
<div id="sidebar-brand">
|
|
14
|
+
<span id="brand-icon">⚙</span>
|
|
15
|
+
<span id="brand-name">Gator</span>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<nav id="sidebar-nav">
|
|
19
|
+
<div class="nav-group-label">Overview</div>
|
|
20
|
+
<button class="sidebar-item active" data-view="fleet">
|
|
21
|
+
<span class="sidebar-icon">◈</span> Fleet
|
|
22
|
+
</button>
|
|
23
|
+
<button class="sidebar-item dimmed" data-view="repo" id="repo-tab" style="padding-left:28px">
|
|
24
|
+
<span class="sidebar-icon">▸</span> Repo
|
|
25
|
+
</button>
|
|
26
|
+
<button class="sidebar-item" data-view="audit">
|
|
27
|
+
<span class="sidebar-icon">◉</span> Audit
|
|
28
|
+
</button>
|
|
29
|
+
|
|
30
|
+
<div class="nav-group-label">Knowledge</div>
|
|
31
|
+
<button class="sidebar-item placeholder" data-view="docs" disabled>
|
|
32
|
+
<span class="sidebar-icon">▤</span> Docs
|
|
33
|
+
<span class="coming-soon">soon</span>
|
|
34
|
+
</button>
|
|
35
|
+
|
|
36
|
+
<div class="nav-group-label">System</div>
|
|
37
|
+
<button class="sidebar-item" data-view="settings">
|
|
38
|
+
<span class="sidebar-icon">⚙</span> Settings
|
|
39
|
+
</button>
|
|
40
|
+
<button class="sidebar-item" data-view="updates">
|
|
41
|
+
<span class="sidebar-icon">↻</span> Updates
|
|
42
|
+
</button>
|
|
43
|
+
</nav>
|
|
44
|
+
|
|
45
|
+
<div id="sidebar-footer">
|
|
46
|
+
<div id="sidebar-footer-text">Local-first · Git-native<br>governance for AI coding</div>
|
|
47
|
+
</div>
|
|
48
|
+
</aside>
|
|
49
|
+
|
|
50
|
+
<div id="main-shell">
|
|
51
|
+
<button id="sidebar-toggle" title="Toggle sidebar">◫</button>
|
|
52
|
+
<header id="topbar">
|
|
53
|
+
<div id="topbar-left">
|
|
54
|
+
<h1 id="topbar-title">Fleet</h1>
|
|
55
|
+
<span id="topbar-subtitle"></span>
|
|
56
|
+
</div>
|
|
57
|
+
<div id="topbar-right">
|
|
58
|
+
<span id="generated-at"></span>
|
|
59
|
+
<button id="refresh-btn" title="Re-run fleet scan">↺ Refresh</button>
|
|
60
|
+
</div>
|
|
61
|
+
</header>
|
|
62
|
+
|
|
63
|
+
<div id="snapshot-banner" style="display:none" class="snapshot-note">
|
|
64
|
+
Snapshot — data captured at generation time.
|
|
65
|
+
Run <code>gator dashboard</code> for live view with Repo drill-down.
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<main id="view-slot">
|
|
69
|
+
<div id="loading">
|
|
70
|
+
<p>Loading fleet data...</p>
|
|
71
|
+
</div>
|
|
72
|
+
</main>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<script src="views/fleet.js"></script>
|
|
78
|
+
<script src="views/audit.js"></script>
|
|
79
|
+
<script src="views/repo.js"></script>
|
|
80
|
+
<script src="views/updates.js"></script>
|
|
81
|
+
<script src="views/settings.js"></script>
|
|
82
|
+
<script src="dashboard.js"></script>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard.js — Gator Dashboard shell controller.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Load Tier 1 data from /api/data (or window.DASHBOARD_DATA in snapshot mode)
|
|
6
|
+
* - Route sidebar clicks to view modules
|
|
7
|
+
* - Handle ?repo=<name> query param to pre-load Repo view
|
|
8
|
+
* - Manage Refresh button (re-fetches /api/refresh then /api/data)
|
|
9
|
+
* - Update topbar title/subtitle from active view
|
|
10
|
+
*
|
|
11
|
+
* View modules register via window.GatorViews.<name> = function(data, container, ...extras)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
(function () {
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
// ── state ──────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const state = {
|
|
20
|
+
data: null, // fast_data from server
|
|
21
|
+
activeView: "fleet",
|
|
22
|
+
activeRepo: null, // name of currently loaded repo (Repo view)
|
|
23
|
+
settings: null, // dashboard settings (mode, etc.)
|
|
24
|
+
standalone: false, // true when no command post detected
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// ── view metadata ─────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const VIEW_META = {
|
|
30
|
+
fleet: { title: "Fleet", subtitle: "" },
|
|
31
|
+
audit: { title: "Audit", subtitle: "" },
|
|
32
|
+
repo: { title: "Repo", subtitle: "" },
|
|
33
|
+
updates: { title: "Updates", subtitle: "" },
|
|
34
|
+
settings: { title: "Settings", subtitle: "" },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ── DOM refs ───────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
const viewSlot = document.getElementById("view-slot");
|
|
40
|
+
const generatedAt = document.getElementById("generated-at");
|
|
41
|
+
const refreshBtn = document.getElementById("refresh-btn");
|
|
42
|
+
const repoTab = document.getElementById("repo-tab");
|
|
43
|
+
const topbarTitle = document.getElementById("topbar-title");
|
|
44
|
+
|
|
45
|
+
// ── sidebar toggle ────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
const sidebarToggle = document.getElementById("sidebar-toggle");
|
|
48
|
+
const sidebar = document.getElementById("sidebar");
|
|
49
|
+
if (sidebarToggle && sidebar) {
|
|
50
|
+
sidebarToggle.addEventListener("click", function () {
|
|
51
|
+
sidebar.classList.toggle("collapsed");
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const topbarSubtitle = document.getElementById("topbar-subtitle");
|
|
55
|
+
|
|
56
|
+
// ── data loading ───────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
async function loadData() {
|
|
59
|
+
if (window.DASHBOARD_DATA) {
|
|
60
|
+
// Snapshot mode — data is already inlined
|
|
61
|
+
return window.DASHBOARD_DATA;
|
|
62
|
+
}
|
|
63
|
+
const resp = await fetch("/api/data");
|
|
64
|
+
if (!resp.ok) throw new Error(`/api/data returned ${resp.status}`);
|
|
65
|
+
return resp.json();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function updateTimestamp(ts) {
|
|
69
|
+
if (!ts) return;
|
|
70
|
+
try {
|
|
71
|
+
const d = new Date(ts);
|
|
72
|
+
generatedAt.textContent = "Updated " + d.toLocaleTimeString();
|
|
73
|
+
} catch (_) {
|
|
74
|
+
generatedAt.textContent = ts;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── topbar ─────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
function updateTopbar(name, subtitle) {
|
|
81
|
+
const meta = VIEW_META[name] || { title: name, subtitle: "" };
|
|
82
|
+
topbarTitle.textContent = meta.title;
|
|
83
|
+
topbarSubtitle.innerHTML = subtitle || meta.subtitle;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── view routing ───────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function showView(name, extra) {
|
|
89
|
+
state.activeView = name;
|
|
90
|
+
|
|
91
|
+
// Update sidebar active state
|
|
92
|
+
document.querySelectorAll(".sidebar-item").forEach(btn => {
|
|
93
|
+
btn.classList.toggle("active", btn.dataset.view === name);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Clear slot
|
|
97
|
+
viewSlot.innerHTML = "";
|
|
98
|
+
|
|
99
|
+
const views = window.GatorViews || {};
|
|
100
|
+
|
|
101
|
+
if (name === "fleet") {
|
|
102
|
+
const fleet = state.data && state.data.fleet || {};
|
|
103
|
+
const summary = fleet.summary || {};
|
|
104
|
+
const total = summary.total || (fleet.repos || []).length;
|
|
105
|
+
const accessible = summary.accessible || 0;
|
|
106
|
+
updateTopbar("fleet", `${total} repos · ${accessible} accessible`);
|
|
107
|
+
if (views.fleet) {
|
|
108
|
+
views.fleet(state.data, viewSlot);
|
|
109
|
+
} else {
|
|
110
|
+
viewSlot.innerHTML = "<p class='muted'>Fleet view not available.</p>";
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (name === "audit") {
|
|
116
|
+
updateTopbar("audit", "Governance posture");
|
|
117
|
+
if (views.audit) {
|
|
118
|
+
views.audit(state.data, viewSlot);
|
|
119
|
+
} else {
|
|
120
|
+
viewSlot.innerHTML = "<p class='muted'>Audit view not available.</p>";
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (name === "repo") {
|
|
126
|
+
const repoName = extra || state.activeRepo;
|
|
127
|
+
if (!repoName) {
|
|
128
|
+
updateTopbar("repo", "Select a repo from Fleet");
|
|
129
|
+
viewSlot.innerHTML = "<p class='muted' style='padding:40px;text-align:center'>Select a repo from the Fleet view.</p>";
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
state.activeRepo = repoName;
|
|
133
|
+
// Update repo sidebar label
|
|
134
|
+
repoTab.textContent = "";
|
|
135
|
+
const icon = document.createElement("span");
|
|
136
|
+
icon.className = "sidebar-icon";
|
|
137
|
+
icon.textContent = "▸";
|
|
138
|
+
repoTab.appendChild(icon);
|
|
139
|
+
repoTab.appendChild(document.createTextNode(" " + repoName));
|
|
140
|
+
repoTab.classList.remove("dimmed");
|
|
141
|
+
// Find branch from fleet data
|
|
142
|
+
const fleetRepos = (state.data && state.data.fleet && state.data.fleet.repos) || (state.data && state.data.repos) || [];
|
|
143
|
+
const repoInfo = fleetRepos.find(r => r.name === repoName);
|
|
144
|
+
const branch = repoInfo && repoInfo.branch ? repoInfo.branch : "";
|
|
145
|
+
const subtitle = branch
|
|
146
|
+
? repoName + ' <span class="muted" style="font-weight:normal">' + branch + '</span>'
|
|
147
|
+
+ ' <button class="history-toggle-btn" id="branch-history-btn" title="Browse commit history" style="font-size:13px;">▾</button>'
|
|
148
|
+
+ '<span id="branch-version-label"></span>'
|
|
149
|
+
+ '<div class="history-dropdown" id="branch-history-dropdown" style="display:none; left:0; right:auto;"></div>'
|
|
150
|
+
: repoName;
|
|
151
|
+
updateTopbar("repo", subtitle);
|
|
152
|
+
// Make subtitle container position:relative for dropdown
|
|
153
|
+
topbarSubtitle.style.position = "relative";
|
|
154
|
+
if (views.repo) {
|
|
155
|
+
views.repo(state.data, viewSlot, repoName);
|
|
156
|
+
} else {
|
|
157
|
+
viewSlot.innerHTML = "<p class='muted'>Repo view not available.</p>";
|
|
158
|
+
}
|
|
159
|
+
// Wire up branch history dropdown
|
|
160
|
+
const branchBtn = document.getElementById("branch-history-btn");
|
|
161
|
+
const branchDropdown = document.getElementById("branch-history-dropdown");
|
|
162
|
+
if (branchBtn && branchDropdown) {
|
|
163
|
+
let branchHistLoaded = false;
|
|
164
|
+
branchBtn.addEventListener("click", async function (e) {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
if (branchDropdown.style.display !== "none") {
|
|
167
|
+
branchDropdown.style.display = "none";
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
branchDropdown.style.display = "block";
|
|
171
|
+
if (branchHistLoaded) return;
|
|
172
|
+
branchDropdown.innerHTML = '<div style="padding:8px; color:#aaa;">Loading…</div>';
|
|
173
|
+
try {
|
|
174
|
+
const resp = await fetch(`/api/repo/${encodeURIComponent(repoName)}/commits`);
|
|
175
|
+
const data = await resp.json();
|
|
176
|
+
if (!data.commits || !data.commits.length) {
|
|
177
|
+
branchDropdown.innerHTML = '<div style="padding:8px; color:#aaa;">No history</div>';
|
|
178
|
+
branchHistLoaded = true;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Add "Current (HEAD)" as first option
|
|
182
|
+
let html = '<div class="history-item history-active" data-hash="" title="Current working state">'
|
|
183
|
+
+ '<span class="history-hash">HEAD</span>'
|
|
184
|
+
+ '<span class="history-date">current</span>'
|
|
185
|
+
+ '<span class="history-msg">Working state</span></div>';
|
|
186
|
+
for (const c of data.commits) {
|
|
187
|
+
const dateStr = (c.date || "").substring(0, 19);
|
|
188
|
+
html += `<div class="history-item" data-hash="${c.hash}" title="${c.message}">
|
|
189
|
+
<span class="history-hash">${c.short_hash}</span>
|
|
190
|
+
<span class="history-date">${dateStr}</span>
|
|
191
|
+
<span class="history-msg">${(c.message || "").substring(0, 50)}</span>
|
|
192
|
+
</div>`;
|
|
193
|
+
}
|
|
194
|
+
branchDropdown.innerHTML = html;
|
|
195
|
+
branchHistLoaded = true;
|
|
196
|
+
branchDropdown.querySelectorAll(".history-item").forEach(item => {
|
|
197
|
+
item.addEventListener("click", function (e) {
|
|
198
|
+
e.stopPropagation();
|
|
199
|
+
branchDropdown.style.display = "none";
|
|
200
|
+
const hash = this.dataset.hash;
|
|
201
|
+
// Update active highlight
|
|
202
|
+
branchDropdown.querySelectorAll(".history-item").forEach(i => i.classList.remove("history-active"));
|
|
203
|
+
this.classList.add("history-active");
|
|
204
|
+
// Update version label in header
|
|
205
|
+
const vLabel = document.getElementById("branch-version-label");
|
|
206
|
+
if (vLabel) {
|
|
207
|
+
const commitDate = this.querySelector(".history-date");
|
|
208
|
+
const dateStr = commitDate ? commitDate.textContent.trim() : "";
|
|
209
|
+
if (hash) {
|
|
210
|
+
vLabel.innerHTML = ' <span class="muted" style="font-weight:normal; font-size:12px;">Viewing: ' + hash.substring(0, 8) + (dateStr && dateStr !== "current" ? ' (' + dateStr.substring(0, 19) + ')' : '') + '</span>';
|
|
211
|
+
} else {
|
|
212
|
+
vLabel.innerHTML = '';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Set version context and reload file list
|
|
216
|
+
window._gatorRepoVersion = hash || null;
|
|
217
|
+
if (views.repo) {
|
|
218
|
+
views.repo(state.data, viewSlot, repoName, hash || null);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
} catch (err) {
|
|
223
|
+
branchDropdown.innerHTML = '<div style="padding:8px; color:var(--color-critical);">Error</div>';
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
document.addEventListener("click", function () { branchDropdown.style.display = "none"; });
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (name === "updates") {
|
|
232
|
+
updateTopbar("updates", "Gator updates");
|
|
233
|
+
if (views.updates) {
|
|
234
|
+
views.updates(state.data, viewSlot);
|
|
235
|
+
} else {
|
|
236
|
+
viewSlot.innerHTML = "<p class='muted'>Updates view not available.</p>";
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (name === "settings") {
|
|
242
|
+
updateTopbar("settings", "Fleet settings");
|
|
243
|
+
if (views.settings) {
|
|
244
|
+
views.settings(state.data, viewSlot, {
|
|
245
|
+
standalone: state.standalone,
|
|
246
|
+
});
|
|
247
|
+
} else {
|
|
248
|
+
viewSlot.innerHTML = "<p class='muted'>Settings view not available.</p>";
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── sidebar click handler ─────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
document.getElementById("sidebar-nav").addEventListener("click", function (e) {
|
|
257
|
+
const item = e.target.closest(".sidebar-item");
|
|
258
|
+
if (!item || item.classList.contains("dimmed") || item.classList.contains("placeholder")) return;
|
|
259
|
+
const view = item.dataset.view;
|
|
260
|
+
if (view === "repo" && !state.activeRepo) return;
|
|
261
|
+
showView(view);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ── session evidence modal ─────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
let modalOverlay = null;
|
|
267
|
+
|
|
268
|
+
function ensureModal() {
|
|
269
|
+
if (modalOverlay) return;
|
|
270
|
+
modalOverlay = document.createElement("div");
|
|
271
|
+
modalOverlay.className = "session-overlay";
|
|
272
|
+
modalOverlay.style.display = "none";
|
|
273
|
+
modalOverlay.innerHTML = `
|
|
274
|
+
<div class="session-modal">
|
|
275
|
+
<div class="session-modal-header">
|
|
276
|
+
<span class="session-modal-title"></span>
|
|
277
|
+
<button class="session-modal-close" title="Close">×</button>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="session-modal-body"></div>
|
|
280
|
+
</div>
|
|
281
|
+
`;
|
|
282
|
+
document.body.appendChild(modalOverlay);
|
|
283
|
+
|
|
284
|
+
// Close handlers
|
|
285
|
+
modalOverlay.querySelector(".session-modal-close").addEventListener("click", closeModal);
|
|
286
|
+
modalOverlay.addEventListener("click", function (e) {
|
|
287
|
+
if (e.target === modalOverlay) closeModal();
|
|
288
|
+
});
|
|
289
|
+
document.addEventListener("keydown", function (e) {
|
|
290
|
+
if (e.key === "Escape" && modalOverlay.style.display !== "none") closeModal();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function closeModal() {
|
|
295
|
+
if (modalOverlay) modalOverlay.style.display = "none";
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
window.showSessionModal = async function (repo, sourceKind, filename) {
|
|
299
|
+
ensureModal();
|
|
300
|
+
const titleEl = modalOverlay.querySelector(".session-modal-title");
|
|
301
|
+
const bodyEl = modalOverlay.querySelector(".session-modal-body");
|
|
302
|
+
|
|
303
|
+
titleEl.textContent = filename;
|
|
304
|
+
bodyEl.innerHTML = '<span class="muted">Loading…</span>';
|
|
305
|
+
modalOverlay.style.display = "flex";
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const resp = await fetch("/api/session", {
|
|
309
|
+
method: "POST",
|
|
310
|
+
headers: {
|
|
311
|
+
"Content-Type": "application/json",
|
|
312
|
+
"X-Gator-Dashboard": "1",
|
|
313
|
+
},
|
|
314
|
+
body: JSON.stringify({
|
|
315
|
+
repo: repo,
|
|
316
|
+
source_kind: sourceKind,
|
|
317
|
+
filename: filename,
|
|
318
|
+
}),
|
|
319
|
+
});
|
|
320
|
+
const data = await resp.json();
|
|
321
|
+
if (data.error) {
|
|
322
|
+
bodyEl.innerHTML = '<div class="session-modal-error">' + escHtml(data.error) + '</div>';
|
|
323
|
+
} else {
|
|
324
|
+
bodyEl.innerHTML = '<pre class="session-markdown">' + escHtml(data.content || "") + '</pre>';
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
bodyEl.innerHTML = '<div class="session-modal-error">Failed to load: ' + escHtml(err.message) + '</div>';
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
function escHtml(str) {
|
|
332
|
+
return String(str)
|
|
333
|
+
.replace(/&/g, "&")
|
|
334
|
+
.replace(/</g, "<")
|
|
335
|
+
.replace(/>/g, ">")
|
|
336
|
+
.replace(/"/g, """);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ── repo navigation (called by fleet view) ─────────────────────────────────
|
|
340
|
+
|
|
341
|
+
window.gatorNavToRepo = function (repoName) {
|
|
342
|
+
showView("repo", repoName);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// ── refresh ────────────────────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
let refreshPending = false;
|
|
348
|
+
|
|
349
|
+
async function doRefresh() {
|
|
350
|
+
if (refreshPending || window.GATOR_SNAPSHOT) return;
|
|
351
|
+
refreshPending = true;
|
|
352
|
+
refreshBtn.classList.add("spinning");
|
|
353
|
+
refreshBtn.textContent = "↺ Refreshing…";
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
await fetch("/api/refresh");
|
|
357
|
+
const oldTs = state.data && state.data.generated_at;
|
|
358
|
+
let attempts = 0;
|
|
359
|
+
while (attempts < 30) {
|
|
360
|
+
await sleep(1000);
|
|
361
|
+
const fresh = await loadData();
|
|
362
|
+
if (fresh.generated_at !== oldTs) {
|
|
363
|
+
state.data = fresh;
|
|
364
|
+
updateTimestamp(fresh.generated_at);
|
|
365
|
+
showView(state.activeView, state.activeRepo);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
attempts++;
|
|
369
|
+
}
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error("Refresh failed:", err);
|
|
372
|
+
} finally {
|
|
373
|
+
refreshBtn.classList.remove("spinning");
|
|
374
|
+
refreshBtn.textContent = "↺ Refresh";
|
|
375
|
+
refreshPending = false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
refreshBtn.addEventListener("click", doRefresh);
|
|
380
|
+
|
|
381
|
+
// Exposed so view modules (e.g. fleet.js) can trigger a fleet refresh
|
|
382
|
+
window.gatorRefreshFleet = doRefresh;
|
|
383
|
+
|
|
384
|
+
function sleep(ms) {
|
|
385
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── initial load ───────────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
async function init() {
|
|
391
|
+
// Snapshot mode: show persistent banner and hide Refresh button.
|
|
392
|
+
if (window.GATOR_SNAPSHOT) {
|
|
393
|
+
const banner = document.getElementById("snapshot-banner");
|
|
394
|
+
if (banner) banner.style.display = "block";
|
|
395
|
+
refreshBtn.style.display = "none";
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
state.data = await loadData();
|
|
400
|
+
updateTimestamp(state.data.generated_at);
|
|
401
|
+
} catch (err) {
|
|
402
|
+
viewSlot.innerHTML = `<div class="error-block">Failed to load dashboard data: ${err.message}</div>`;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
state.standalone = !!(state.data && state.data.standalone);
|
|
407
|
+
|
|
408
|
+
// Check for ?repo= query param
|
|
409
|
+
const params = new URLSearchParams(window.location.search);
|
|
410
|
+
const preloadRepo = params.get("repo");
|
|
411
|
+
if (preloadRepo) {
|
|
412
|
+
showView("repo", preloadRepo);
|
|
413
|
+
} else {
|
|
414
|
+
showView("fleet");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
init();
|
|
419
|
+
})();
|