Skip to content

Commit 06434c8

Browse files
Nightly E2E Status local KubeStellar
Signed-off-by: Diego-Castan <diego.castan@ibm.com>
1 parent 1e36088 commit 06434c8

4 files changed

Lines changed: 721 additions & 4 deletions

File tree

docusaurus.config.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,25 @@ const config = {
123123
},
124124
],
125125

126-
// Examples:
127-
// ['@docusaurus/plugin-google-analytics', { trackingID: 'UA-XXXXXX-X' }],
128-
// ['docusaurus-plugin-sass', {}],
129-
// Add any other plugins you need
126+
// Dev server proxy: forward /api requests to KubeStellar Console
127+
function () {
128+
return {
129+
name: 'kubestellar-proxy',
130+
configureWebpack() {
131+
return {
132+
devServer: {
133+
proxy: [
134+
{
135+
context: ['/api'],
136+
target: 'http://localhost:8080',
137+
changeOrigin: true,
138+
},
139+
],
140+
},
141+
};
142+
},
143+
};
144+
},
130145
],
131146

132147
markdown: {
@@ -197,6 +212,7 @@ const config = {
197212
},
198213
{ to: "/blog", label: "Blog", position: "left" },
199214
{ to: "/videos", label: "Videos", position: "left" },
215+
{ to: "/nightly-e2e", label: "Nightly E2E", position: "left" },
200216
{
201217
type: 'html',
202218
position: 'right',

src/components/NightlyE2EStatus.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import React, { useState, useEffect } from 'react';
2+
3+
const API_URL = '/api/public/nightly-e2e/runs';
4+
const POLL_INTERVAL = 300000; // 5 minutes
5+
6+
const PLATFORMS = ['OCP', 'GKE', 'CKS'];
7+
const PLATFORM_COLORS = { OCP: '#f97316', GKE: '#3b82f6', CKS: '#a855f7' };
8+
const CONCLUSION_COLORS = { success: '#22c55e', failure: '#ef4444', cancelled: '#6b7280', skipped: '#6b7280' };
9+
10+
export default function NightlyE2EStatus({ styles }) {
11+
const [data, setData] = useState(null);
12+
const [error, setError] = useState(null);
13+
const [loading, setLoading] = useState(true);
14+
15+
useEffect(() => {
16+
let cancelled = false;
17+
18+
const fetchData = async () => {
19+
try {
20+
const res = await fetch(API_URL);
21+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
22+
const json = await res.json();
23+
if (!cancelled) {
24+
setData(json);
25+
setError(null);
26+
}
27+
} catch (e) {
28+
if (!cancelled) {
29+
setError(e.message);
30+
setData(null);
31+
}
32+
} finally {
33+
if (!cancelled) setLoading(false);
34+
}
35+
};
36+
37+
fetchData();
38+
const interval = setInterval(fetchData, POLL_INTERVAL);
39+
return () => {
40+
cancelled = true;
41+
clearInterval(interval);
42+
};
43+
}, []);
44+
45+
if (loading) {
46+
return (
47+
<div className={styles.statusCard}>
48+
<div className={styles.statusHeader}>
49+
<span className={`${styles.statusDot} ${styles.statusInfo}`} />
50+
<span className={styles.statusTitle}>Nightly E2E Status</span>
51+
</div>
52+
<p className={styles.loadingText}>Loading status data...</p>
53+
</div>
54+
);
55+
}
56+
57+
if (error) {
58+
return (
59+
<div className={styles.statusCard}>
60+
<div className={styles.statusHeader}>
61+
<span className={`${styles.statusDot} ${styles.statusError}`} />
62+
<span className={styles.statusTitle}>Nightly E2E Status</span>
63+
</div>
64+
<div className={styles.offlineMessage}>
65+
<p className={styles.offlineTitle}>Unable to connect to KubeStellar Console</p>
66+
<p className={styles.offlineDetail}>
67+
This page displays live data from a locally running KubeStellar Console
68+
at <code>localhost:8080</code>. Start the console to see nightly E2E results.
69+
</p>
70+
</div>
71+
</div>
72+
);
73+
}
74+
75+
const guides = data?.guides || [];
76+
const totalGuides = guides.length;
77+
const failing = guides.filter(g => g.latestConclusion === 'failure').length;
78+
const allRuns = guides.flatMap(g => g.runs || []);
79+
const completedRuns = allRuns.filter(r => r.status === 'completed');
80+
const passedRuns = completedRuns.filter(r => r.conclusion === 'success');
81+
const passRate = completedRuns.length > 0
82+
? Math.round((passedRuns.length / completedRuns.length) * 100)
83+
: 0;
84+
85+
return (
86+
<div className={styles.statusCard}>
87+
<div className={styles.statusHeader}>
88+
<span
89+
className={styles.statusDot}
90+
style={{ backgroundColor: failing > 0 ? '#ef4444' : '#22c55e' }}
91+
/>
92+
<span className={styles.statusTitle}>Nightly E2E Status</span>
93+
</div>
94+
95+
<div className={styles.statsRow}>
96+
<div className={styles.stat}>
97+
<div className={styles.statValue} style={{ color: '#a855f7' }}>{passRate}%</div>
98+
<div className={styles.statLabel}>Pass Rate</div>
99+
</div>
100+
<div className={styles.stat}>
101+
<div className={styles.statValue}>{totalGuides}</div>
102+
<div className={styles.statLabel}>Guides</div>
103+
</div>
104+
<div className={styles.stat}>
105+
<div
106+
className={styles.statValue}
107+
style={{ color: failing > 0 ? '#ef4444' : '#22c55e' }}
108+
>
109+
{failing}
110+
</div>
111+
<div className={styles.statLabel}>Failing</div>
112+
</div>
113+
</div>
114+
115+
{PLATFORMS.map(platform => {
116+
const platGuides = guides.filter(g => g.platform === platform);
117+
if (platGuides.length === 0) return null;
118+
return (
119+
<div key={platform} className={styles.platformSection}>
120+
<div
121+
className={styles.platformName}
122+
style={{ color: PLATFORM_COLORS[platform] }}
123+
>
124+
{platform}
125+
</div>
126+
{platGuides.map(g => (
127+
<div key={g.guide + g.platform} className={styles.guideRow}>
128+
<span className={styles.guideAcronym}>{g.acronym}</span>
129+
<span className={styles.guideName}>{g.guide}</span>
130+
<div className={styles.runDots}>
131+
{(g.runs || []).slice(0, 7).map((run, i) => (
132+
<span
133+
key={i}
134+
className={`${styles.runDot} ${run.status !== 'completed' ? styles.runDotPulse : ''}`}
135+
style={{
136+
backgroundColor: run.status !== 'completed'
137+
? '#60a5fa'
138+
: (CONCLUSION_COLORS[run.conclusion] || '#6b7280'),
139+
}}
140+
title={`${run.conclusion || run.status}`}
141+
/>
142+
))}
143+
{(!g.runs || g.runs.length === 0) && (
144+
<span className={styles.noRuns}>no runs</span>
145+
)}
146+
</div>
147+
<span className={styles.guidePassRate}>{g.passRate}%</span>
148+
</div>
149+
))}
150+
</div>
151+
);
152+
})}
153+
</div>
154+
);
155+
}

src/pages/nightly-e2e.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import Layout from '@theme/Layout';
3+
import Head from '@docusaurus/Head';
4+
import NightlyE2EStatus from '@site/src/components/NightlyE2EStatus';
5+
import styles from './nightly-e2e.module.css';
6+
7+
export default function NightlyE2E() {
8+
return (
9+
<>
10+
<Head>
11+
<meta name="keywords" content="llm-d, nightly, e2e, end-to-end, testing, status, OCP, GKE, CKS, kubernetes" />
12+
<meta property="og:title" content="llm-d Nightly E2E Status" />
13+
<meta property="og:description" content="Live pass/fail status of llm-d nightly E2E workflows across OCP, GKE, and CKS platforms." />
14+
</Head>
15+
<Layout
16+
title="Nightly E2E"
17+
description="Live nightly E2E test status for llm-d across OCP, GKE, and CKS platforms">
18+
<main className={styles.statusPage}>
19+
<div className={styles.heroSection}>
20+
<div className={styles.heroContent}>
21+
<h1 className={styles.heroTitle}>
22+
<span className={styles.heroIcon}>&#x2713;</span>
23+
Nightly E2E Status
24+
</h1>
25+
<p className={styles.heroSubtitle}>
26+
Live pass/fail status of llm-d nightly end-to-end workflows
27+
across OCP, GKE, and CKS platforms.
28+
</p>
29+
</div>
30+
</div>
31+
32+
<div className={styles.container}>
33+
<NightlyE2EStatus styles={styles} />
34+
</div>
35+
36+
<div className={styles.ctaSection}>
37+
<h2 className={styles.ctaTitle}>Ready to get started?</h2>
38+
<p className={styles.ctaText}>
39+
Dive into our documentation or join our community to learn more.
40+
</p>
41+
<div className={styles.ctaButtons}>
42+
<a href="/docs/guide" className={styles.ctaButtonPrimary}>
43+
Read the Docs
44+
</a>
45+
<a href="/slack" className={styles.ctaButtonSecondary}>
46+
Join Slack
47+
</a>
48+
</div>
49+
</div>
50+
</main>
51+
</Layout>
52+
</>
53+
);
54+
}

0 commit comments

Comments
 (0)