commit 15bd7abba3f5f45cf305509c988c4db9d1017c4e
parent dd2990ee488d38e5747b9c42bd90f2254385cf4b
Author: Arjoonn Sharma <arjoonn@midpathsoftware.com>
Date: Sat, 28 Feb 2026 13:35:52 +0530
x
Diffstat:
| M | internal/jci/web.go | | | 192 | ++++++++++++++++++++++++++++++++++--------------------------------------------- |
1 file changed, 83 insertions(+), 109 deletions(-)
diff --git a/internal/jci/web.go b/internal/jci/web.go
@@ -119,87 +119,7 @@ func getLocalBranches() ([]string, error) {
// getRemoteJCIRefs returns a set of commits that have CI refs pushed to remote
-// getBranchCommits returns recent commits for a branch
-func getBranchCommits(branch string, limit int) ([]CommitInfo, error) {
- // Get commit hash and message
- out, err := git("log", branch, fmt.Sprintf("--max-count=%d", limit), "--format=%H|%s")
- if err != nil {
- return nil, err
- }
- if out == "" {
- return nil, nil
- }
-
- // Get local JCI refs (single-run)
- jciRefs, _ := ListJCIRefs()
- jciSet := make(map[string]bool)
- for _, ref := range jciRefs {
- commit := strings.TrimPrefix(ref, "jci/")
- jciSet[commit] = true
- }
-
- // Get local JCI run refs (multi-run)
- // Map: commit -> list of run refs
- jciRuns := make(map[string][]string)
- runRefs, _ := ListJCIRunRefs()
- for _, ref := range runRefs {
- // ref is refs/jci-runs/<commit>/<runid>
- parts := strings.Split(strings.TrimPrefix(ref, "refs/jci-runs/"), "/")
- if len(parts) >= 2 {
- commit := parts[0]
- jciRuns[commit] = append(jciRuns[commit], ref)
- }
- }
-
- // Get remote JCI refs for CI push status
- remoteCI := getRemoteJCIRefs("origin")
-
- var commits []CommitInfo
- for _, line := range strings.Split(out, "\n") {
- parts := strings.SplitN(line, "|", 2)
- if len(parts) != 2 {
- continue
- }
- hash := parts[0]
- msg := parts[1]
-
- hasCI := jciSet[hash] || len(jciRuns[hash]) > 0
- commit := CommitInfo{
- Hash: hash,
- ShortHash: hash[:7],
- Message: msg,
- HasCI: hasCI,
- CIPushed: remoteCI["refs/jci/"+hash],
- }
-
- if jciSet[hash] {
- commit.CIStatus = getCIStatus(hash)
- }
- // Add multi-run info
- for _, runRef := range jciRuns[hash] {
- parts := strings.Split(strings.TrimPrefix(runRef, "refs/jci-runs/"), "/")
- if len(parts) >= 2 {
- runID := parts[1]
- status := getCIStatusFromRef(runRef)
- commit.Runs = append(commit.Runs, RunInfo{
- RunID: runID,
- Status: status,
- Ref: runRef,
- })
- }
- }
-
- // If no single-run status but has runs, use latest run status
- if commit.CIStatus == "" && len(commit.Runs) > 0 {
- commit.CIStatus = commit.Runs[len(commit.Runs)-1].Status
- }
-
- commits = append(commits, commit)
- }
-
- return commits, nil
-}
// getCIStatus returns "success", "failed", or "running" based on status.txt
func getCIStatus(commit string) string {
@@ -725,6 +645,7 @@ func showMainPage(w http.ResponseWriter, r *http.Request) {
<script>
let branches = [], currentCommit = null, currentFiles = [], currentFile = null, currentRuns = [], currentRunId = null;
+ let currentBranch = null, currentPage = 0, loadedCommits = [], hasMoreCommits = false;
async function loadBranches() {
const res = await fetch('/api/branches');
@@ -732,8 +653,8 @@ func showMainPage(w http.ResponseWriter, r *http.Request) {
const select = document.getElementById('branchSelect');
select.innerHTML = branches.map(b => '<option value="' + b.name + '">' + b.name + '</option>').join('');
const def = branches.find(b => b.name === 'main') || branches[0];
- if (def) { select.value = def.name; showBranch(def.name); }
-
+ if (def) { select.value = def.name; await showBranch(def.name); }
+
// Check URL for initial commit and file
const m = location.pathname.match(/^\/jci\/([a-f0-9]+)/);
if (m) selectCommitByHash(m[1]);
@@ -748,40 +669,92 @@ func showMainPage(w http.ResponseWriter, r *http.Request) {
}
}
- function showBranch(name) {
- const branch = branches.find(b => b.name === name);
- if (!branch) return;
- const list = document.getElementById('commitList');
- list.innerHTML = (branch.commits || []).map(c => {
- const status = c.hasCI ? (c.ciStatus || 'none') : 'none';
- const noCiClass = c.hasCI ? '' : 'no-ci';
- let pushBadge = '';
- if (c.hasCI) {
- if (c.ciPushed) {
- pushBadge = '<span class="push-badge pushed">pushed</span>';
- } else {
- pushBadge = '<span class="push-badge local">local</span>';
- }
- }
- return '<li class="commit-item ' + noCiClass + '" data-hash="' + c.hash + '" data-hasci="' + c.hasCI + '">' +
- '<span class="status-badge ' + status + '">' + getStatusLabel(status) + '</span>' +
- '<span class="commit-hash">' + c.shortHash + '</span>' +
- '<span class="commit-msg">' + escapeHtml(c.message) + '</span>' +
- pushBadge + '</li>';
- }).join('');
- list.querySelectorAll('.commit-item').forEach(el => {
+ function renderCommitItem(c) {
+ const status = c.hasCI ? (c.ciStatus || 'none') : 'none';
+ const noCiClass = c.hasCI ? '' : 'no-ci';
+ let pushBadge = '';
+ if (c.hasCI) {
+ pushBadge = c.ciPushed
+ ? '<span class="push-badge pushed">pushed</span>'
+ : '<span class="push-badge local">local</span>';
+ }
+ return '<li class="commit-item ' + noCiClass + '" data-hash="' + c.hash + '" data-hasci="' + c.hasCI + '">' +
+ '<span class="status-badge ' + status + '">' + getStatusLabel(status) + '</span>' +
+ '<span class="commit-hash">' + c.shortHash + '</span>' +
+ '<span class="commit-msg">' + escapeHtml(c.message) + '</span>' +
+ pushBadge + '</li>';
+ }
+
+ function attachCommitClickHandlers() {
+ document.querySelectorAll('.commit-item').forEach(el => {
el.onclick = () => selectCommit(el.dataset.hash, el.dataset.hasci === 'true');
});
}
- function selectCommitByHash(hash) {
- // Find full hash from branches
- for (const b of branches) {
- const c = (b.commits || []).find(c => c.hash.startsWith(hash));
- if (c) { selectCommit(c.hash, c.hasCI); return; }
+ async function showBranch(name) {
+ currentBranch = name;
+ currentPage = 0;
+ loadedCommits = [];
+ hasMoreCommits = false;
+ const list = document.getElementById('commitList');
+ list.innerHTML = '<li style="padding:8px 10px;color:#656d76;">Loading…</li>';
+ await loadMoreCommits();
+ }
+
+ async function loadMoreCommits() {
+ const res = await fetch('/api/commits?branch=' + encodeURIComponent(currentBranch) + '&page=' + currentPage);
+ const data = await res.json();
+ loadedCommits = loadedCommits.concat(data.commits || []);
+ hasMoreCommits = data.hasMore || false;
+
+ const list = document.getElementById('commitList');
+ // Remove existing load-more button if present
+ const oldBtn = document.getElementById('loadMoreBtn');
+ if (oldBtn) oldBtn.remove();
+
+ if (currentPage === 0) {
+ list.innerHTML = loadedCommits.map(renderCommitItem).join('');
+ } else {
+ // Append newly loaded commits (remove loading placeholder first)
+ const placeholder = document.getElementById('loadMorePlaceholder');
+ if (placeholder) placeholder.remove();
+ const frag = document.createDocumentFragment();
+ const tmp = document.createElement('ul');
+ tmp.innerHTML = (data.commits || []).map(renderCommitItem).join('');
+ while (tmp.firstChild) frag.appendChild(tmp.firstChild);
+ list.appendChild(frag);
+ }
+
+ attachCommitClickHandlers();
+
+ if (hasMoreCommits) {
+ const btn = document.createElement('li');
+ btn.id = 'loadMoreBtn';
+ btn.style.cssText = 'padding:8px 10px;text-align:center;cursor:pointer;color:#0969da;border-top:1px solid #eaeef2;';
+ btn.textContent = 'Load more commits…';
+ btn.onclick = async () => {
+ btn.textContent = 'Loading…';
+ btn.onclick = null;
+ currentPage++;
+ await loadMoreCommits();
+ };
+ list.appendChild(btn);
+ }
+
+ // Re-highlight selected commit if any
+ if (currentCommit) {
+ document.querySelectorAll('.commit-item').forEach(el =>
+ el.classList.toggle('selected', el.dataset.hash === currentCommit)
+ );
}
}
+ function selectCommitByHash(hash) {
+ // Search already-loaded commits
+ const c = loadedCommits.find(c => c.hash.startsWith(hash));
+ if (c) { selectCommit(c.hash, c.hasCI); return; }
+ }
+
async function selectCommit(hash, hasCI) {
currentCommit = hash;
currentFile = null;
@@ -1044,6 +1017,7 @@ func showMainPage(w http.ResponseWriter, r *http.Request) {
document.getElementById('branchSelect').onchange = e => showBranch(e.target.value);
loadBranches();
+
</script>
</body>
</html>