Skip to content

Commit 94287fa

Browse files
authored
initial commit
1 parent 888b09f commit 94287fa

File tree

1 file changed

+256
-0
lines changed

1 file changed

+256
-0
lines changed

api/status-js.js

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// --- Configuration ---
2+
const HOUSELEARNING_ORG = 'HouseLearning';
3+
const WEBSITE_URL = 'https://houselearning.github.io';
4+
const GITHUB_API_URL = 'https://api.github.com';
5+
const WEBSITE_STATUS_ELEMENT = document.getElementById('website-status');
6+
const STATUS_TEXT = document.getElementById('status-text');
7+
const STATUS_ICON = document.getElementById('status-icon');
8+
const REPO_LIST = document.getElementById('repo-list');
9+
const LOADING_REPOS = document.getElementById('loading-repos');
10+
const REPO_DETAIL_CARD = document.getElementById('repo-detail-card');
11+
const REPO_DETAIL_TITLE = document.getElementById('repo-detail-title');
12+
const REPO_DETAIL_CONTENT = document.getElementById('repo-detail-content');
13+
14+
// Store repository data globally
15+
let allRepositories = [];
16+
17+
// --- Utility Functions ---
18+
19+
/**
20+
* Converts ISO 8601 date string to a readable format.
21+
* @param {string} dateString
22+
* @returns {string}
23+
*/
24+
function formatCommitDate(dateString) {
25+
if (!dateString) return 'N/A';
26+
const date = new Date(dateString);
27+
return date.toLocaleString('en-US', {
28+
year: 'numeric',
29+
month: 'short',
30+
day: 'numeric',
31+
hour: '2-digit',
32+
minute: '2-digit',
33+
timeZoneName: 'short'
34+
});
35+
}
36+
37+
// Removed generateMockRepositories as requested.
38+
39+
// --- Core Functions ---
40+
41+
/**
42+
* Checks the status of the main HouseLearning website.
43+
* Due to CORS restrictions, we use 'no-cors' mode, which only tells us if the network request completed (opaque response).
44+
* If the promise resolves, we assume the site is up. If it rejects (network error), we assume it's down.
45+
*/
46+
async function checkWebsiteStatus() {
47+
STATUS_TEXT.textContent = 'Checking...';
48+
STATUS_ICON.classList.remove('bg-red-500', 'bg-hl-primary');
49+
STATUS_ICON.classList.add('bg-gray-400', 'animate-pulse');
50+
WEBSITE_STATUS_ELEMENT.classList.remove('bg-red-100', 'bg-green-100');
51+
WEBSITE_STATUS_ELEMENT.classList.add('bg-gray-50');
52+
53+
try {
54+
// Use 'no-cors' mode to prevent the browser from blocking the request.
55+
const response = await fetch(WEBSITE_URL, { mode: 'no-cors', cache: 'no-store' });
56+
57+
// If the fetch call resolves, the site is considered reachable ("UP").
58+
STATUS_TEXT.textContent = 'UP (Reachable)';
59+
STATUS_TEXT.classList.add('text-hl-primary');
60+
STATUS_ICON.classList.remove('bg-gray-400', 'animate-pulse');
61+
STATUS_ICON.classList.add('bg-hl-primary');
62+
WEBSITE_STATUS_ELEMENT.classList.remove('bg-gray-50');
63+
WEBSITE_STATUS_ELEMENT.classList.add('bg-green-100');
64+
} catch (error) {
65+
console.error("Website status check failed (Network/CORS issue):", error);
66+
// If the fetch promise rejects (e.g., DNS failure, no network), the site is considered down.
67+
STATUS_TEXT.textContent = 'DOWN (Unreachable)';
68+
STATUS_TEXT.classList.remove('text-hl-primary');
69+
STATUS_TEXT.classList.add('text-red-500');
70+
STATUS_ICON.classList.remove('bg-gray-400', 'animate-pulse');
71+
STATUS_ICON.classList.add('bg-red-500');
72+
WEBSITE_STATUS_ELEMENT.classList.remove('bg-gray-50');
73+
WEBSITE_STATUS_ELEMENT.classList.add('bg-red-100');
74+
}
75+
}
76+
77+
/**
78+
* Fetches all public repositories for the organization.
79+
* Displays a "Limit Reached" message on 403/429 errors.
80+
* @returns {Promise<Array>}
81+
*/
82+
async function fetchRepositories() {
83+
try {
84+
// Get list of public repositories
85+
const reposResponse = await fetch(`${GITHUB_API_URL}/orgs/${HOUSELEARNING_ORG}/repos?sort=pushed&per_page=100`);
86+
87+
// Check for rate limit errors first
88+
if (reposResponse.status === 403 || reposResponse.status === 429) {
89+
throw new Error(`GitHub API Error: ${reposResponse.status}. Rate limit hit.`);
90+
}
91+
92+
if (!reposResponse.ok) throw new Error(`GitHub API Error: ${reposResponse.status}`);
93+
const repos = await reposResponse.json();
94+
95+
// For each repo, fetch the latest commit
96+
const reposWithCommits = await Promise.all(repos.map(async (repo) => {
97+
// Endpoint to get the very latest commit on the default branch
98+
const commitUrl = `${GITHUB_API_URL}/repos/${HOUSELEARNING_ORG}/${repo.name}/commits?per_page=1`;
99+
const commitResponse = await fetch(commitUrl);
100+
101+
let latestCommit = null;
102+
if (commitResponse.ok) {
103+
const commits = await commitResponse.json();
104+
if (commits.length > 0) {
105+
latestCommit = commits[0];
106+
}
107+
} else {
108+
// Log warning but don't stop the whole process
109+
console.warn(`Could not fetch commit for ${repo.name}. Status: ${commitResponse.status}`);
110+
}
111+
112+
return {
113+
name: repo.name,
114+
description: repo.description,
115+
html_url: repo.html_url,
116+
latestCommit: latestCommit
117+
};
118+
}));
119+
120+
allRepositories = reposWithCommits;
121+
renderRepositories();
122+
123+
} catch (error) {
124+
console.error("Failed to fetch repository data:", error);
125+
126+
// Check if the error indicates a rate limit issue (403 Forbidden is common for unauthenticated limits)
127+
if (error.message.includes("403") || error.message.includes("429") || error.message.includes("Rate limit hit")) {
128+
129+
// Clear the list area
130+
REPO_LIST.innerHTML = '';
131+
132+
// Display the "Limit Reached" message in the list area
133+
REPO_LIST.innerHTML = `
134+
<div class="p-4 bg-red-100 border border-red-300 rounded-lg text-red-800 text-center font-semibold mt-4">
135+
<p>GitHub API Limit Reached.</p>
136+
<p class="text-sm font-normal mt-1">Cannot fetch real-time data due to rate limits. Please try again later.</p>
137+
</div>
138+
`;
139+
// Hide the detail card since there is no data
140+
REPO_DETAIL_CARD.classList.add('hidden');
141+
142+
return; // Stop execution
143+
}
144+
145+
// Generic error handling
146+
REPO_LIST.innerHTML = `
147+
<div class="p-4 bg-red-100 border border-red-300 rounded-lg text-red-800 text-center font-semibold mt-4">
148+
<p>Error loading repositories.</p>
149+
<p class="text-sm font-normal mt-1">Check console for details on the network failure.</p>
150+
</div>
151+
`;
152+
REPO_DETAIL_CARD.classList.add('hidden');
153+
}
154+
}
155+
156+
/**
157+
* Renders the fetched repositories into the sidebar.
158+
*/
159+
function renderRepositories() {
160+
// Remove the initial 'loading-repos' message if we have data
161+
if (document.getElementById('loading-repos')) {
162+
document.getElementById('loading-repos').remove();
163+
}
164+
165+
REPO_LIST.innerHTML = ''; // Clear contents before re-rendering
166+
167+
if (allRepositories.length === 0) {
168+
REPO_LIST.innerHTML = `
169+
<div class="text-sm text-gray-500 italic p-2 text-center">
170+
No repositories found in the organization.
171+
</div>
172+
`;
173+
REPO_DETAIL_CARD.classList.add('hidden');
174+
return;
175+
}
176+
177+
178+
allRepositories.forEach(repo => {
179+
const latestCommitMessage = repo.latestCommit?.commit?.message.split('\n')[0] || 'No commits found.';
180+
const latestCommitDate = repo.latestCommit?.commit?.author?.date || null;
181+
182+
const repoItem = document.createElement('div');
183+
repoItem.className = 'repo-item p-3 rounded-lg border-l-4 border-hl-secondary/0 hover:border-hl-secondary';
184+
repoItem.setAttribute('data-repo-name', repo.name);
185+
repoItem.innerHTML = `
186+
<p class="text-sm font-semibold text-gray-900 truncate">${repo.name}</p>
187+
<p class="text-xs text-gray-500 mt-1 truncate" title="${latestCommitMessage}">
188+
<span class="font-medium text-gray-600">Commit:</span> ${latestCommitMessage}
189+
</p>
190+
<p class="text-xs text-gray-400 mt-1">
191+
${latestCommitDate ? formatCommitDate(latestCommitDate) : ''}
192+
</p>
193+
`;
194+
195+
repoItem.addEventListener('click', () => displayRepoDetails(repo.name));
196+
REPO_LIST.appendChild(repoItem);
197+
});
198+
199+
// Automatically display the first repo's details if available
200+
if (allRepositories.length > 0) {
201+
displayRepoDetails(allRepositories[0].name);
202+
}
203+
}
204+
205+
/**
206+
* Displays detailed information for a selected repository.
207+
* @param {string} repoName
208+
*/
209+
function displayRepoDetails(repoName) {
210+
const repo = allRepositories.find(r => r.name === repoName);
211+
if (!repo) return;
212+
213+
// Update sidebar highlighting
214+
document.querySelectorAll('.repo-item').forEach(item => {
215+
item.classList.remove('bg-hl-secondary/10', 'border-hl-secondary');
216+
});
217+
document.querySelector(`[data-repo-name="${repoName}"]`).classList.add('bg-hl-secondary/10', 'border-hl-secondary');
218+
219+
// Populate the detail card
220+
const commit = repo.latestCommit;
221+
const commitMessage = commit?.commit?.message.split('\n')[0] || 'N/A';
222+
const commitAuthor = commit?.commit?.author?.name || 'N/A';
223+
const commitDate = commit?.commit?.author?.date ? formatCommitDate(commit.commit.author.date) : 'N/A';
224+
const commitSha = commit?.sha ? commit.sha.substring(0, 10) : 'N/A';
225+
226+
REPO_DETAIL_TITLE.textContent = repo.name;
227+
REPO_DETAIL_CONTENT.innerHTML = `
228+
<p class="text-gray-700">${repo.description || 'No description provided.'}</p>
229+
230+
<div class="space-y-2 pt-4 border-t mt-4 border-gray-100">
231+
<p class="text-sm text-gray-500 font-medium">LATEST COMMIT</p>
232+
<p class="text-md font-semibold text-gray-800 break-words">${commitMessage}</p>
233+
<div class="text-sm text-gray-600 space-y-1">
234+
<p><strong>Author:</strong> ${commitAuthor}</p>
235+
<p><strong>Date:</strong> ${commitDate}</p>
236+
<p><strong>SHA:</strong> <code class="bg-gray-200 px-1 py-0.5 rounded text-xs">${commitSha}</code></p>
237+
</div>
238+
</div>
239+
240+
<a href="${repo.html_url}" target="_blank" class="inline-block mt-4 text-sm font-medium text-white bg-hl-secondary hover:bg-hl-secondary/90 px-4 py-2 rounded-lg transition duration-150 shadow-md">
241+
View Repository on GitHub &rarr;
242+
</a>
243+
`;
244+
REPO_DETAIL_CARD.classList.remove('hidden');
245+
}
246+
247+
// --- Initialization ---
248+
249+
window.onload = function() {
250+
// Start both checks immediately
251+
checkWebsiteStatus();
252+
fetchRepositories();
253+
254+
// Optional: Re-check status every 60 seconds
255+
setInterval(checkWebsiteStatus, 60000);
256+
};

0 commit comments

Comments
 (0)