-
+
+
- ${repo.issues.totalCount > 0 ? `
-
Issues
-
-
- ${repo.issues.nodes.map(issue => `
-
- |
- ${escapeHtml(issue.title)}
- ${formatAge(issue.updatedAt)}
- ${issue.labels.nodes.length > 0 ? `
-
- ${issue.labels.nodes.map(label => `${escapeHtml(label.name)}`).join('')}
-
- ` : ''}
- |
-
- `).join('')}
-
-
- ` : ''}
-
- ${repo.pullRequests.totalCount > 0 ? `
-
Pull Requests
-
-
- ${repo.pullRequests.nodes.map(pr => `
-
- |
- ${escapeHtml(pr.title)}
- ${pr.author ? `by ${escapeHtml(pr.author.login)}` : ''}
- ${formatAge(pr.updatedAt)}
- ${getReviewStatusBadge(pr)}
- |
-
- `).join('')}
-
-
- ` : ''}
+
+
+ ${repo.issues.totalCount > 0 ? `
+
Issues
+
+
+ ${repo.issues.nodes.map(issue => generateIssueRow(issue, config)).join('')}
+
+
+ ` : '
No open issues
'}
+
+
+ ${repo.pullRequests.totalCount > 0 ? `
+
Pull Requests
+
+
+ ${repo.pullRequests.nodes.map(pr => generatePullRequestRow(pr, config)).join('')}
+
+
+ ` : '
No open pull requests
'}
+
+
`).join('')}
+
+ `;
+}
+
+export function generateRecentActivitySection(orgDataMap, limit = 15) {
+ const items = [];
+ for (const [orgName, data] of Object.entries(orgDataMap)) {
+ const repos = data.data.organization.repositories.nodes;
+ for (const repo of repos) {
+ for (const issue of repo.issues.nodes) {
+ items.push({
+ type: 'issue',
+ title: issue.title,
+ url: issue.url,
+ updatedAt: issue.updatedAt,
+ repo: repo.name,
+ org: orgName,
+ author: null
+ });
+ }
+ for (const pr of repo.pullRequests.nodes) {
+ items.push({
+ type: 'pr',
+ title: pr.title,
+ url: pr.url,
+ updatedAt: pr.updatedAt,
+ repo: repo.name,
+ org: orgName,
+ author: pr.author?.login || null
+ });
+ }
+ }
+ }
+
+ items.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
+ const recent = items.slice(0, limit);
+
+ if (recent.length === 0) {
+ return '';
+ }
+
+ const rows = recent.map(item => {
+ const typeBadge = item.type === 'pr'
+ ? '
PR'
+ : '
Issue';
+ const author = item.author ? `
by ${escapeHtml(item.author)}` : '';
+ return `
+
+ | ${typeBadge} |
+ ${escapeHtml(item.org)}/${escapeHtml(item.repo)} |
+
+ ${escapeHtml(item.title)}
+ ${author}
+ |
+ ${formatAge(item.updatedAt)} |
+
+ `;
+ }).join('');
+
+ return `
+
`;
@@ -231,7 +314,7 @@ export function generateSummarySection(stats, config) {
`;
}
-export function generateHTML(summarySection, orgSections, missingMirrorsSection, workflowSection) {
+export function generateHTML(summarySection, orgSections, missingMirrorsSection, workflowSection, recentActivitySection = '') {
const lastUpdate = formatDateUTC(new Date().toISOString());
return `
@@ -249,10 +332,61 @@ export function generateHTML(summarySection, orgSections, missingMirrorsSection,
padding: 1rem;
}
- .two-columns {
+ .repo-card {
max-width: 1400px;
- columns: 2;
- column-gap: 1.5rem;
+ }
+
+ .repo-split > .repo-col + .repo-col {
+ border-top: 1px solid rgba(0, 0, 0, 0.125);
+ }
+
+ @media (min-width: 992px) {
+ .repo-split > .repo-col + .repo-col {
+ border-top: none;
+ border-left: 1px solid rgba(0, 0, 0, 0.125);
+ }
+ }
+
+ .repo-col .table-title {
+ background-color: rgba(0, 0, 0, 0.02);
+ }
+
+ .empty-col {
+ padding: 1rem;
+ font-style: italic;
+ }
+
+ .activity-type {
+ display: inline-block;
+ padding: 0.1rem 0.4rem;
+ border-radius: 0.25rem;
+ font-size: 0.7em;
+ font-weight: 500;
+ min-width: 2.5rem;
+ text-align: center;
+ }
+ .activity-type-issue { background-color: #d1ecf1; color: #0c5460; }
+ .activity-type-pr { background-color: #e2d9f3; color: #3d2a6c; }
+
+ .recent-activity-table {
+ max-width: 1400px;
+ font-size: 0.9rem;
+ }
+ .recent-activity-table td {
+ vertical-align: middle;
+ }
+ .recent-activity-table .activity-type-cell {
+ width: 3.5rem;
+ }
+ .recent-activity-table .activity-repo {
+ white-space: nowrap;
+ font-family: monospace;
+ font-size: 0.85em;
+ color: #495057;
+ }
+ .recent-activity-table .activity-age {
+ white-space: nowrap;
+ width: 1%;
}
.label {
@@ -407,6 +541,8 @@ export function generateHTML(summarySection, orgSections, missingMirrorsSection,
${summarySection}
+ ${recentActivitySection}
+
@@ -420,9 +556,9 @@ export function generateHTML(summarySection, orgSections, missingMirrorsSection,
const query = e.target.value.toLowerCase().trim();
// Filter repo cards in org sections
- document.querySelectorAll('.two-columns .col').forEach(function(col) {
- const text = col.textContent.toLowerCase();
- col.style.display = !query || text.includes(query) ? '' : 'none';
+ document.querySelectorAll('.repo-card').forEach(function(card) {
+ const text = card.textContent.toLowerCase();
+ card.style.display = !query || text.includes(query) ? '' : 'none';
});
// Filter table rows in sortable tables