diff --git a/docusaurus.config.js b/docusaurus.config.js
index 8c1a62c..670a48b 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -123,10 +123,6 @@ const config = {
},
],
- // Examples:
- // ['@docusaurus/plugin-google-analytics', { trackingID: 'UA-XXXXXX-X' }],
- // ['docusaurus-plugin-sass', {}],
- // Add any other plugins you need
],
markdown: {
@@ -197,6 +193,7 @@ const config = {
},
{ to: "/blog", label: "Blog", position: "left" },
{ to: "/videos", label: "Videos", position: "left" },
+ { to: "/nightly-e2e", label: "Nightly E2E", position: "left" },
{
type: 'html',
position: 'right',
diff --git a/src/components/NightlyE2EStatus.js b/src/components/NightlyE2EStatus.js
new file mode 100644
index 0000000..d0be784
--- /dev/null
+++ b/src/components/NightlyE2EStatus.js
@@ -0,0 +1,229 @@
+import React, { useState, useEffect } from 'react';
+
+const API_URL = 'https://console.kubestellar.io/api/public/nightly-e2e/runs';
+const POLL_INTERVAL = 300000; // 5 minutes
+
+const PLATFORMS = ['OCP', 'GKE', 'CKS'];
+const PLATFORM_COLORS = { OCP: '#f97316', GKE: '#3b82f6', CKS: '#a855f7' };
+const CONCLUSION_COLORS = { success: '#22c55e', failure: '#ef4444', cancelled: '#6b7280', skipped: '#6b7280' };
+
+function timeAgo(ts) {
+ if (!ts) return '';
+ const ms = Date.now() - new Date(ts).getTime();
+ const h = Math.floor(ms / 3600000);
+ if (h < 1) return Math.floor(ms / 60000) + 'm ago';
+ if (h < 24) return h + 'h ago';
+ return Math.floor(h / 24) + 'd ago';
+}
+
+export default function NightlyE2EStatus({ styles }) {
+ const [data, setData] = useState(null);
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ const fetchData = async () => {
+ try {
+ const res = await fetch(API_URL);
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const json = await res.json();
+ if (!cancelled) {
+ setData(json);
+ setError(null);
+ }
+ } catch (e) {
+ if (!cancelled) {
+ setError(e.message);
+ setData(null);
+ }
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ };
+
+ fetchData();
+ const interval = setInterval(fetchData, POLL_INTERVAL);
+ return () => {
+ cancelled = true;
+ clearInterval(interval);
+ };
+ }, []);
+
+ if (loading) {
+ return (
+
+
+
+ Nightly E2E Status
+
+
Loading status data...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+ Nightly E2E Status
+
+
+
Unable to load E2E status
+
+ Could not connect to the status API. Please try again later.
+
+
+
+ );
+ }
+
+ const guides = data?.guides || [];
+ const totalGuides = guides.length;
+ const failing = guides.filter(g => g.latestConclusion === 'failure').length;
+ const allRuns = guides.flatMap(g => g.runs || []);
+ const completedRuns = allRuns.filter(r => r.status === 'completed');
+ const passedRuns = completedRuns.filter(r => r.conclusion === 'success');
+ const passRate = completedRuns.length > 0
+ ? Math.round((passedRuns.length / completedRuns.length) * 100)
+ : 0;
+
+ return (
+
+
+ 0 ? '#ef4444' : '#22c55e' }}
+ />
+ Nightly E2E Status
+ {data?.cachedAt && (
+
+ Updated {timeAgo(data.cachedAt)}
+
+ )}
+
+
+
+
+
{passRate}%
+
Pass Rate
+
+
+
{totalGuides}
+
Guides
+
+
+
0 ? '#ef4444' : '#22c55e' }}
+ >
+ {failing}
+
+
Failing
+
+
+
+ {PLATFORMS.map(platform => {
+ const platGuides = guides.filter(g => g.platform === platform);
+ if (platGuides.length === 0) return null;
+ return (
+
+
+ {platform}
+
+ {platGuides.map(g => {
+ const runs = (g.runs || []).slice(0, 7);
+ const failedAll = runs.filter(r => r.status === 'completed' && r.conclusion === 'failure');
+ const gpuFails = failedAll.filter(r => r.failureReason === 'gpu_unavailable').length;
+ const workflowUrl = `https://github.com/${g.repo}/actions/workflows/${g.workflowFile}`;
+
+ return (
+
+
+ {g.acronym}
+
+
+ {g.guide}
+
+ {g.model}{g.gpuType && g.gpuType !== 'CPU' ? ` · ${g.gpuCount}× ${g.gpuType}` : ''}
+
+
+
+ {runs.map((run, i) => {
+ const isGpu = run.conclusion === 'failure' && run.failureReason === 'gpu_unavailable';
+ const dotColor = run.status !== 'completed'
+ ? '#60a5fa'
+ : isGpu ? '#f59e0b'
+ : (CONCLUSION_COLORS[run.conclusion] || '#6b7280');
+ const label = (run.conclusion || run.status) + (isGpu ? ' (GPU unavailable)' : '');
+
+ return (
+
+
+
+ );
+ })}
+ {runs.length === 0 && (
+
no runs
+ )}
+
+
+ {gpuFails > 0 && (
+
+ GPU: {gpuFails}
+
+ )}
+ = 80 ? '#22c55e' : g.passRate >= 50 ? '#eab308' : '#ef4444',
+ }}>
+ {g.passRate}%
+
+
+
+ );
+ })}
+
+ );
+ })}
+
+
+
+ Pass
+
+
+ Fail
+
+
+ GPU unavailable
+
+
+ In progress
+
+
+ Cancelled
+
+
+
+ );
+}
diff --git a/src/pages/nightly-e2e.js b/src/pages/nightly-e2e.js
new file mode 100644
index 0000000..c82e9b2
--- /dev/null
+++ b/src/pages/nightly-e2e.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import Layout from '@theme/Layout';
+import Head from '@docusaurus/Head';
+import NightlyE2EStatus from '@site/src/components/NightlyE2EStatus';
+import styles from './nightly-e2e.module.css';
+
+export default function NightlyE2E() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ ✓
+ Nightly E2E Status
+
+
+ Live pass/fail status of llm-d nightly end-to-end workflows
+ across OCP, GKE, and CKS platforms.
+
+
+
+
+
+
+
+
+
+
Ready to get started?
+
+ Dive into our documentation or join our community to learn more.
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/nightly-e2e.module.css b/src/pages/nightly-e2e.module.css
new file mode 100644
index 0000000..a38841b
--- /dev/null
+++ b/src/pages/nightly-e2e.module.css
@@ -0,0 +1,590 @@
+/* Nightly E2E Status Page Styles */
+
+.statusPage {
+ min-height: 100vh;
+ background: linear-gradient(180deg, #faf8fc 0%, #fff 100%);
+}
+
+[data-theme="dark"] .statusPage {
+ background: linear-gradient(180deg, #1a1a1a 0%, #262626 100%);
+}
+
+/* Hero Section */
+.heroSection {
+ position: relative;
+ padding: 4rem 2rem 3rem;
+ text-align: center;
+ overflow: hidden;
+ background-image: url("/img/background.png");
+ background-position: center;
+ background-size: cover;
+}
+
+.heroContent {
+ position: relative;
+ z-index: 2;
+ max-width: 800px;
+ margin: 0 auto;
+ background-color: rgba(255, 255, 255, 0.95);
+ border-radius: 24px;
+ padding: 2.5rem 3rem;
+}
+
+[data-theme="dark"] .heroContent {
+ background-color: rgba(38, 38, 38, 0.95);
+}
+
+.heroTitle {
+ font-size: 3rem;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.75rem;
+}
+
+[data-theme="dark"] .heroTitle {
+ color: #fff;
+}
+
+.heroIcon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 56px;
+ height: 56px;
+ background: #7f317f;
+ color: #fff;
+ border-radius: 50%;
+ font-size: 1.5rem;
+}
+
+.heroSubtitle {
+ font-size: 1.25rem;
+ color: #444;
+ line-height: 1.7;
+ max-width: 600px;
+ margin: 0 auto;
+}
+
+[data-theme="dark"] .heroSubtitle {
+ color: #ccc;
+}
+
+/* Container */
+.container {
+ max-width: 900px;
+ margin: 0 auto;
+ padding: 3rem 2rem 4rem;
+}
+
+/* Status Card */
+.statusCard {
+ background: #fff;
+ border-radius: 16px;
+ padding: 2rem;
+ box-shadow:
+ 0 4px 6px rgba(127, 49, 127, 0.06),
+ 0 12px 24px rgba(127, 49, 127, 0.08);
+}
+
+[data-theme="dark"] .statusCard {
+ background: #2d2d2d;
+ box-shadow:
+ 0 4px 6px rgba(0, 0, 0, 0.2),
+ 0 12px 24px rgba(0, 0, 0, 0.25);
+}
+
+/* Status Header */
+.statusHeader {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 1.5rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid rgba(127, 49, 127, 0.1);
+}
+
+[data-theme="dark"] .statusHeader {
+ border-bottom-color: rgba(212, 148, 212, 0.15);
+}
+
+.statusTitle {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: #1a1a1a;
+}
+
+[data-theme="dark"] .statusTitle {
+ color: #f9fafb;
+}
+
+.statusDot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+.statusInfo {
+ background-color: #3b82f6;
+}
+
+.statusError {
+ background-color: #ef4444;
+}
+
+.lastUpdated {
+ margin-left: auto;
+ font-size: 0.75rem;
+ color: #9ca3af;
+}
+
+/* Loading */
+.loadingText {
+ color: #666;
+ font-size: 1rem;
+ margin: 0;
+}
+
+[data-theme="dark"] .loadingText {
+ color: #aaa;
+}
+
+/* Offline message */
+.offlineMessage {
+ text-align: center;
+ padding: 1.5rem 0;
+}
+
+.offlineTitle {
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: #1a1a1a;
+ margin: 0 0 0.75rem 0;
+}
+
+[data-theme="dark"] .offlineTitle {
+ color: #f9fafb;
+}
+
+.offlineDetail {
+ font-size: 0.95rem;
+ color: #666;
+ margin: 0;
+ line-height: 1.6;
+}
+
+[data-theme="dark"] .offlineDetail {
+ color: #aaa;
+}
+
+/* Stats Row */
+.statsRow {
+ display: flex;
+ gap: 2rem;
+ margin-bottom: 1.5rem;
+ padding-bottom: 1.5rem;
+ border-bottom: 1px solid rgba(127, 49, 127, 0.1);
+}
+
+[data-theme="dark"] .statsRow {
+ border-bottom-color: rgba(212, 148, 212, 0.15);
+}
+
+.stat {
+ text-align: center;
+}
+
+.statValue {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #1a1a1a;
+ line-height: 1.2;
+}
+
+[data-theme="dark"] .statValue {
+ color: #f9fafb;
+}
+
+.statLabel {
+ font-size: 0.8rem;
+ color: #9ca3af;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-top: 0.25rem;
+}
+
+/* Platform Section */
+.platformSection {
+ margin-bottom: 1.25rem;
+}
+
+.platformSection:last-child {
+ margin-bottom: 0;
+}
+
+.platformName {
+ font-weight: 700;
+ font-size: 0.85rem;
+ margin-bottom: 0.5rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+/* Guide Row */
+.guideRow {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.4rem 0;
+}
+
+.guideAcronym {
+ width: 40px;
+ font-size: 0.8rem;
+ font-weight: 700;
+ color: #64748b;
+ flex-shrink: 0;
+ text-decoration: none;
+ transition: color 0.15s ease;
+}
+
+.guideAcronym:hover {
+ color: #7f317f;
+ text-decoration: none;
+}
+
+[data-theme="dark"] .guideAcronym {
+ color: #94a3b8;
+}
+
+[data-theme="dark"] .guideAcronym:hover {
+ color: #d494d4;
+}
+
+.guideNameWrap {
+ display: flex;
+ flex-direction: column;
+ width: 160px;
+ flex-shrink: 0;
+ overflow: hidden;
+}
+
+.guideName {
+ font-size: 0.85rem;
+ color: #475569;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+[data-theme="dark"] .guideName {
+ color: #cbd5e1;
+}
+
+.guideDetail {
+ font-size: 0.7rem;
+ color: #9ca3af;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.runDots {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+ flex: 1;
+}
+
+.runDotLink {
+ display: inline-flex;
+ text-decoration: none;
+ transition: transform 0.1s ease;
+}
+
+.runDotLink:hover {
+ transform: scale(1.3);
+}
+
+.runDot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+.runDotPulse {
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+.noRuns {
+ color: #9ca3af;
+ font-size: 0.75rem;
+}
+
+.guideStats {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-left: auto;
+ flex-shrink: 0;
+}
+
+.gpuBadge {
+ font-size: 0.65rem;
+ font-weight: 600;
+ color: #f59e0b;
+ background: rgba(245, 158, 11, 0.1);
+ padding: 0.1em 0.4em;
+ border-radius: 4px;
+ white-space: nowrap;
+}
+
+[data-theme="dark"] .gpuBadge {
+ background: rgba(245, 158, 11, 0.15);
+}
+
+.guidePassRate {
+ font-size: 0.8rem;
+ font-weight: 600;
+ flex-shrink: 0;
+ font-variant-numeric: tabular-nums;
+ min-width: 36px;
+ text-align: right;
+}
+
+/* Legend */
+.legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ margin-top: 1.5rem;
+ padding-top: 1rem;
+ border-top: 1px solid rgba(127, 49, 127, 0.1);
+}
+
+[data-theme="dark"] .legend {
+ border-top-color: rgba(212, 148, 212, 0.15);
+}
+
+.legendItem {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ font-size: 0.75rem;
+ color: #9ca3af;
+}
+
+.legendDot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ display: inline-block;
+ flex-shrink: 0;
+}
+
+/* CTA Section */
+.ctaSection {
+ text-align: center;
+ padding: 4rem 2rem;
+ background: linear-gradient(135deg, #f8f4f8 0%, #fff 100%);
+ border-top: 1px solid rgba(127, 49, 127, 0.1);
+}
+
+[data-theme="dark"] .ctaSection {
+ background: linear-gradient(135deg, #1f1f1f 0%, #262626 100%);
+ border-top-color: rgba(212, 148, 212, 0.1);
+}
+
+.ctaTitle {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin: 0 0 0.75rem 0;
+}
+
+[data-theme="dark"] .ctaTitle {
+ color: #fff;
+}
+
+.ctaText {
+ font-size: 1.1rem;
+ color: #666;
+ margin: 0 0 2rem 0;
+}
+
+[data-theme="dark"] .ctaText {
+ color: #aaa;
+}
+
+.ctaButtons {
+ display: flex;
+ justify-content: center;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.ctaButtonPrimary,
+.ctaButtonSecondary {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.875rem 2rem;
+ border-radius: 12px;
+ font-size: 1rem;
+ font-weight: 600;
+ text-decoration: none;
+ transition: all 0.2s ease;
+}
+
+.ctaButtonPrimary {
+ background: #7f317f;
+ color: #fff;
+}
+
+.ctaButtonPrimary:hover {
+ background: #a540a5;
+ color: #fff;
+ text-decoration: none;
+ transform: translateY(-2px);
+}
+
+.ctaButtonSecondary {
+ background: transparent;
+ color: #7f317f;
+ border: 2px solid #7f317f;
+}
+
+.ctaButtonSecondary:hover {
+ background: rgba(127, 49, 127, 0.08);
+ color: #7f317f;
+ text-decoration: none;
+ transform: translateY(-2px);
+}
+
+[data-theme="dark"] .ctaButtonSecondary {
+ color: #d494d4;
+ border-color: #d494d4;
+}
+
+[data-theme="dark"] .ctaButtonSecondary:hover {
+ background: rgba(212, 148, 212, 0.1);
+ color: #d494d4;
+}
+
+/* Responsive */
+@media screen and (max-width: 900px) {
+ .heroTitle {
+ font-size: 2.25rem;
+ }
+
+ .heroSubtitle {
+ font-size: 1.1rem;
+ }
+
+ .guideNameWrap {
+ width: 130px;
+ }
+}
+
+@media screen and (max-width: 600px) {
+ .heroSection {
+ padding: 3rem 1.5rem 2.5rem;
+ }
+
+ .heroTitle {
+ font-size: 1.75rem;
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+
+ .heroIcon {
+ width: 48px;
+ height: 48px;
+ font-size: 1.25rem;
+ }
+
+ .heroSubtitle {
+ font-size: 1rem;
+ }
+
+ .container {
+ padding: 2rem 1rem 3rem;
+ }
+
+ .statusCard {
+ padding: 1.25rem;
+ }
+
+ .statsRow {
+ gap: 1.25rem;
+ }
+
+ .statValue {
+ font-size: 1.5rem;
+ }
+
+ .guideRow {
+ gap: 0.5rem;
+ }
+
+ .guideAcronym {
+ width: 32px;
+ font-size: 0.75rem;
+ }
+
+ .guideNameWrap {
+ width: 90px;
+ }
+
+ .guideName {
+ font-size: 0.8rem;
+ }
+
+ .guideDetail {
+ display: none;
+ }
+
+ .runDot {
+ width: 8px;
+ height: 8px;
+ }
+
+ .gpuBadge {
+ display: none;
+ }
+
+ .legend {
+ gap: 0.75rem;
+ }
+
+ .ctaSection {
+ padding: 3rem 1.5rem;
+ }
+
+ .ctaTitle {
+ font-size: 1.5rem;
+ }
+
+ .ctaButtons {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .ctaButtonPrimary,
+ .ctaButtonSecondary {
+ width: 100%;
+ max-width: 280px;
+ }
+}