Skip to content

Commit 26eb71b

Browse files
Deprecate old status page, redirect to status.osc.earth/osa
Replace the JS-based sync dashboard with a redirect notice pointing to the new comprehensive dashboard.
1 parent a670fc9 commit 26eb71b

1 file changed

Lines changed: 11 additions & 347 deletions

File tree

docs/osa/status.md

Lines changed: 11 additions & 347 deletions
Original file line numberDiff line numberDiff line change
@@ -1,352 +1,16 @@
1-
# Sync Status
1+
# Service Status
22

3-
<div id="status-container">
4-
<div class="status-loading">Loading status...</div>
5-
</div>
3+
!!! warning "This page has moved"
4+
The OSA status dashboard has moved to a new location with expanded metrics,
5+
usage charts, and per-community views.
66

7-
<style>
8-
.status-loading {
9-
padding: 2rem;
10-
text-align: center;
11-
color: var(--md-default-fg-color--light);
12-
}
7+
**[View the new dashboard at status.osc.earth/osa](https://status.osc.earth/osa/)**
138

14-
.status-error {
15-
padding: 1rem;
16-
background: var(--md-code-bg-color);
17-
border-left: 4px solid #f44336;
18-
margin: 1rem 0;
19-
}
9+
The new dashboard includes:
2010

21-
.status-grid {
22-
display: grid;
23-
gap: 1.5rem;
24-
margin-top: 1rem;
25-
}
11+
- **Aggregate overview** with total requests, error rates, and community breakdown
12+
- **Per-community views** with usage charts and tool statistics
13+
- **Sync health status** for GitHub and papers knowledge sources
14+
- **Admin section** for token usage and cost metrics (requires API key)
2615

27-
.status-card {
28-
background: var(--md-code-bg-color);
29-
border-radius: 8px;
30-
padding: 1.5rem;
31-
border: 1px solid var(--md-default-fg-color--lightest);
32-
}
33-
34-
.status-card h3 {
35-
margin-top: 0;
36-
display: flex;
37-
align-items: center;
38-
gap: 0.5rem;
39-
}
40-
41-
.status-indicator {
42-
display: inline-block;
43-
width: 12px;
44-
height: 12px;
45-
border-radius: 50%;
46-
margin-right: 0.5rem;
47-
}
48-
49-
.status-healthy { background: #4caf50; }
50-
.status-warning { background: #ff9800; }
51-
.status-error-indicator { background: #f44336; }
52-
53-
.status-details {
54-
display: grid;
55-
grid-template-columns: auto 1fr;
56-
gap: 0.5rem 1rem;
57-
margin-top: 1rem;
58-
font-size: 0.9rem;
59-
}
60-
61-
.status-label {
62-
color: var(--md-default-fg-color--light);
63-
}
64-
65-
.status-value {
66-
font-family: var(--md-code-font-family);
67-
}
68-
69-
.scheduler-info {
70-
margin-top: 1.5rem;
71-
padding-top: 1rem;
72-
border-top: 1px solid var(--md-default-fg-color--lightest);
73-
}
74-
75-
.repo-list, .source-list {
76-
margin-top: 0.5rem;
77-
padding-left: 1rem;
78-
font-size: 0.85rem;
79-
}
80-
81-
.repo-item, .source-item {
82-
padding: 0.25rem 0;
83-
color: var(--md-default-fg-color--light);
84-
}
85-
86-
.last-updated {
87-
margin-top: 1.5rem;
88-
font-size: 0.8rem;
89-
color: var(--md-default-fg-color--light);
90-
text-align: center;
91-
}
92-
93-
.refresh-btn {
94-
background: var(--md-primary-fg-color);
95-
color: var(--md-primary-bg-color);
96-
border: none;
97-
padding: 0.5rem 1rem;
98-
border-radius: 4px;
99-
cursor: pointer;
100-
font-size: 0.9rem;
101-
margin-top: 1rem;
102-
}
103-
104-
.refresh-btn:hover {
105-
opacity: 0.9;
106-
}
107-
108-
.refresh-btn:disabled {
109-
opacity: 0.5;
110-
cursor: not-allowed;
111-
}
112-
</style>
113-
114-
<script>
115-
// API endpoint - allow override via query param for development/testing
116-
// Usage: ?api=http://localhost:38528 for local testing
117-
const urlParams = new URLSearchParams(window.location.search);
118-
const API_BASE = urlParams.get('api') || 'https://api.osc.earth/osa-dev';
119-
120-
// HTML escape to prevent XSS from API response data
121-
function escapeHtml(str) {
122-
if (str === null || str === undefined) return '';
123-
return String(str)
124-
.replace(/&/g, '&amp;')
125-
.replace(/</g, '&lt;')
126-
.replace(/>/g, '&gt;')
127-
.replace(/"/g, '&quot;')
128-
.replace(/'/g, '&#039;');
129-
}
130-
131-
async function fetchStatus() {
132-
const container = document.getElementById('status-container');
133-
134-
try {
135-
const response = await fetch(`${API_BASE}/sync/status`);
136-
137-
if (!response.ok) {
138-
throw new Error(`API returned ${response.status}`);
139-
}
140-
141-
const data = await response.json();
142-
renderStatus(container, data);
143-
} catch (error) {
144-
renderError(container, error);
145-
}
146-
}
147-
148-
function formatDate(isoString) {
149-
if (!isoString) return 'Never';
150-
try {
151-
const date = new Date(isoString);
152-
return date.toLocaleString('en-US', {
153-
year: 'numeric',
154-
month: 'short',
155-
day: 'numeric',
156-
hour: '2-digit',
157-
minute: '2-digit',
158-
timeZoneName: 'short'
159-
});
160-
} catch {
161-
return 'Invalid date';
162-
}
163-
}
164-
165-
function formatAge(hours) {
166-
if (hours === null || hours === undefined) return 'Never synced';
167-
if (hours < 1) return 'Less than an hour ago';
168-
if (hours < 24) return `${Math.round(hours)} hours ago`;
169-
const days = Math.round(hours / 24);
170-
return `${days} day${days === 1 ? '' : 's'} ago`;
171-
}
172-
173-
function getHealthClass(healthy) {
174-
return healthy ? 'status-healthy' : 'status-error-indicator';
175-
}
176-
177-
function getHealthText(healthy, ageHours) {
178-
if (healthy) return 'Healthy';
179-
if (ageHours === null) return 'Pending';
180-
return 'Stale';
181-
}
182-
183-
function renderStatus(container, data) {
184-
const github = data?.github || {};
185-
const papers = data?.papers || {};
186-
const scheduler = data?.scheduler || {};
187-
const health = data?.health || {};
188-
189-
container.innerHTML = `
190-
<div class="status-grid">
191-
<div class="status-card">
192-
<h3>
193-
<span class="status-indicator ${getHealthClass(health.github_healthy)}"></span>
194-
GitHub Sync
195-
</h3>
196-
<div class="status-details">
197-
<span class="status-label">Status:</span>
198-
<span class="status-value">${escapeHtml(getHealthText(health.github_healthy, health.github_age_hours))}</span>
199-
200-
<span class="status-label">Last sync:</span>
201-
<span class="status-value">${escapeHtml(formatAge(health.github_age_hours))}</span>
202-
203-
<span class="status-label">Total items:</span>
204-
<span class="status-value">${(github.total_items ?? 0).toLocaleString()}</span>
205-
206-
<span class="status-label">Issues:</span>
207-
<span class="status-value">${(github.issues ?? 0).toLocaleString()}</span>
208-
209-
<span class="status-label">Pull Requests:</span>
210-
<span class="status-value">${(github.prs ?? 0).toLocaleString()}</span>
211-
212-
<span class="status-label">Open items:</span>
213-
<span class="status-value">${(github.open_items ?? 0).toLocaleString()}</span>
214-
</div>
215-
216-
<div class="repo-list">
217-
<strong>Repositories:</strong>
218-
${Object.entries(github.repos || {}).map(([repo, info]) => `
219-
<div class="repo-item">
220-
${escapeHtml(repo)}: ${(info?.items ?? 0)} items
221-
${info?.last_sync ? `(synced ${escapeHtml(formatDate(info.last_sync))})` : ''}
222-
</div>
223-
`).join('')}
224-
</div>
225-
</div>
226-
227-
<div class="status-card">
228-
<h3>
229-
<span class="status-indicator ${getHealthClass(health.papers_healthy)}"></span>
230-
Papers Sync
231-
</h3>
232-
<div class="status-details">
233-
<span class="status-label">Status:</span>
234-
<span class="status-value">${escapeHtml(getHealthText(health.papers_healthy, health.papers_age_hours))}</span>
235-
236-
<span class="status-label">Last sync:</span>
237-
<span class="status-value">${escapeHtml(formatAge(health.papers_age_hours))}</span>
238-
239-
<span class="status-label">Total papers:</span>
240-
<span class="status-value">${(papers.total_items ?? 0).toLocaleString()}</span>
241-
</div>
242-
243-
<div class="source-list">
244-
<strong>Sources:</strong>
245-
${Object.entries(papers.sources || {}).map(([source, info]) => `
246-
<div class="source-item">
247-
${escapeHtml(source)}: ${(info?.items ?? 0)} papers
248-
${info?.last_sync ? `(synced ${escapeHtml(formatDate(info.last_sync))})` : ''}
249-
</div>
250-
`).join('')}
251-
</div>
252-
</div>
253-
254-
<div class="status-card scheduler-info">
255-
<h3>Scheduler</h3>
256-
<div class="status-details">
257-
<span class="status-label">Enabled:</span>
258-
<span class="status-value">${scheduler.enabled ? 'Yes' : 'No'}</span>
259-
260-
<span class="status-label">Running:</span>
261-
<span class="status-value">${scheduler.running ? 'Yes' : 'No'}</span>
262-
263-
<span class="status-label">GitHub schedule:</span>
264-
<span class="status-value">${escapeHtml(scheduler.github_cron ?? 'Not configured')} (UTC)</span>
265-
266-
<span class="status-label">Papers schedule:</span>
267-
<span class="status-value">${escapeHtml(scheduler.papers_cron ?? 'Not configured')} (UTC)</span>
268-
269-
${scheduler.next_github_sync ? `
270-
<span class="status-label">Next GitHub sync:</span>
271-
<span class="status-value">${escapeHtml(formatDate(scheduler.next_github_sync))}</span>
272-
` : ''}
273-
274-
${scheduler.next_papers_sync ? `
275-
<span class="status-label">Next papers sync:</span>
276-
<span class="status-value">${escapeHtml(formatDate(scheduler.next_papers_sync))}</span>
277-
` : ''}
278-
</div>
279-
</div>
280-
</div>
281-
282-
<div class="last-updated">
283-
Last checked: ${escapeHtml(new Date().toLocaleString())}
284-
<br>
285-
<button class="refresh-btn" onclick="refreshStatus()">Refresh</button>
286-
</div>
287-
`;
288-
}
289-
290-
function renderError(container, error) {
291-
// Detect likely CORS or network issues
292-
let hint = 'The API may be temporarily unavailable. Try again later.';
293-
if (error instanceof TypeError && error.message.includes('fetch')) {
294-
hint = 'Unable to reach the API. This may be a network or CORS issue.';
295-
}
296-
297-
container.innerHTML = `
298-
<div class="status-error">
299-
<strong>Unable to fetch status</strong>
300-
<p>${escapeHtml(error.message)}</p>
301-
<p>${escapeHtml(hint)}</p>
302-
<button class="refresh-btn" onclick="refreshStatus()">Retry</button>
303-
</div>
304-
`;
305-
}
306-
307-
async function refreshStatus() {
308-
const btn = document.querySelector('.refresh-btn');
309-
if (btn) {
310-
btn.disabled = true;
311-
btn.textContent = 'Refreshing...';
312-
}
313-
314-
await fetchStatus();
315-
316-
if (btn) {
317-
btn.disabled = false;
318-
btn.textContent = 'Refresh';
319-
}
320-
}
321-
322-
// Fetch status on page load
323-
document.addEventListener('DOMContentLoaded', fetchStatus);
324-
</script>
325-
326-
## About This Page
327-
328-
This page shows the real-time status of OSA's knowledge sync jobs. The knowledge database automatically syncs:
329-
330-
- **GitHub Issues/PRs**: Daily at 2am UTC from HED repositories
331-
- **Academic Papers**: Weekly Sunday at 3am UTC from OpenALEX, Semantic Scholar, and PubMed
332-
333-
### Health Indicators
334-
335-
| Status | Meaning |
336-
|--------|---------|
337-
| :material-circle:{ style="color: #4caf50" } Healthy | Sync completed within expected timeframe |
338-
| :material-circle:{ style="color: #ff9800" } Pending | Never synced (new installation) |
339-
| :material-circle:{ style="color: #f44336" } Stale | Sync is overdue |
340-
341-
### API Endpoint
342-
343-
The status data is fetched from the OSA API:
344-
345-
```
346-
GET /sync/status
347-
```
348-
349-
Returns JSON with `github`, `papers`, `scheduler`, and `health` objects.
350-
351-
!!! tip "Local Testing"
352-
To test with a local API server, add `?api=http://localhost:38528` to this page's URL.
16+
To view a specific community, visit `https://status.osc.earth/osa/{community_id}` (e.g., [status.osc.earth/osa/hed](https://status.osc.earth/osa/hed)).

0 commit comments

Comments
 (0)