From aad57916bab828106d4e56b6b321d494e15939a0 Mon Sep 17 00:00:00 2001
From: Gaurav Nelson <23069445+gaurav-nelson@users.noreply.github.com>
Date: Thu, 21 May 2026 11:09:43 +1000
Subject: [PATCH] fix: brings back Classic CI page on the status page
---
layouts/partials/menu-ci.html | 7 +-
static/css/custom.css | 79 +++++++
static/js/dashboard.v2.js | 378 +++++++++++++++++++++++++++++-----
3 files changed, 411 insertions(+), 53 deletions(-)
diff --git a/layouts/partials/menu-ci.html b/layouts/partials/menu-ci.html
index 2dbbe936c0..c733e6bf2f 100644
--- a/layouts/partials/menu-ci.html
+++ b/layouts/partials/menu-ci.html
@@ -2,7 +2,7 @@
diff --git a/static/css/custom.css b/static/css/custom.css
index a245bbc5bb..e1812366dc 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -1249,6 +1249,29 @@ h6 .anchor::before {
font-weight: 600;
}
+.ci-dashboard-filters {
+ display: flex;
+ justify-content: flex-end;
+ margin-bottom: 12px;
+}
+
+.ci-dashboard-filters-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 24px;
+}
+
+.ci-dashboard-filters .ci-toolbar {
+ margin-bottom: 0;
+}
+
+.ci-dashboard-filters .ci-toolbar-range,
+.ci-dashboard-filters .ci-toolbar-sort {
+ justify-content: flex-end;
+}
+
.ci-toolbar {
display: flex;
justify-content: flex-end;
@@ -1345,6 +1368,34 @@ h6 .anchor::before {
color: var(--pf-global--danger-color--100, #c9190b);
}
+.ci-status-dot.unavailable {
+ background-color: var(--pf-global--BorderColor--100, #d2d2d2);
+}
+
+.ci-status-text.unavailable {
+ color: var(--pf-global--Color--200, #6a6e73);
+}
+
+a.ci-status-unavailable-link {
+ color: var(--pf-global--primary-color--100, #06c);
+ text-decoration: underline;
+ font-weight: 500;
+}
+
+a.ci-status-unavailable-link:hover {
+ color: var(--pf-global--primary-color--200, #004080);
+}
+
+.ci-label-load-error {
+ font-size: 0.85rem;
+ color: var(--pf-global--primary-color--100, #06c);
+ text-decoration: underline;
+}
+
+.ci-label-load-error:hover {
+ color: var(--pf-global--primary-color--200, #004080);
+}
+
.ci-pattern-name {
font-weight: 600;
color: var(--pf-global--Color--100, #151515);
@@ -1514,6 +1565,26 @@ h6 .anchor::before {
transition: width 0.4s ease;
}
+.ci-overview-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 16px;
+}
+
+.ci-overview-toolbar .ci-overview-legend {
+ flex: 1 1 220px;
+ margin-bottom: 0;
+ min-width: 0;
+}
+
+.ci-overview-toolbar .ci-toolbar-range {
+ flex-shrink: 0;
+ margin-bottom: 0;
+}
+
.ci-overview-legend {
font-size: 0.82rem;
color: var(--pf-global--Color--200, #6a6e73);
@@ -1659,6 +1730,10 @@ h6 .anchor::before {
background-color: var(--pf-global--danger-color--100, #c9190b);
}
+.ci-card-platform-dot.unavailable {
+ background-color: var(--pf-global--BorderColor--100, #d2d2d2);
+}
+
.ci-card-platform-label {
white-space: nowrap;
}
@@ -1693,6 +1768,10 @@ h6 .anchor::before {
background-color: var(--pf-global--danger-color--100, #c9190b);
}
+.ci-card-block.unavailable {
+ background-color: var(--pf-global--BorderColor--100, #d2d2d2);
+}
+
.ci-card-footer {
font-size: 0.8rem;
color: var(--pf-global--Color--200, #6a6e73);
diff --git a/static/js/dashboard.v2.js b/static/js/dashboard.v2.js
index b842be3650..6da5d5c7fd 100644
--- a/static/js/dashboard.v2.js
+++ b/static/js/dashboard.v2.js
@@ -88,32 +88,59 @@ function jira_component (pattern) {
return pattern
}
+// First segment of badge filenames (before the first `-`) → Hugo section under /patterns/, or absolute path.
+// Sync new keys with `ci:` in content/patterns/**/_index.* (hyphenated CI IDs often appear only as a shortened prefix in keys).
+var CI_PATTERN_DOC_SLUG = {
+ aegitops: 'ansible-edge-gitops',
+ agof: 'ansible-gitops-framework',
+ coco: 'coco-pattern',
+ connvehicle: 'connected-vehicle-architecture',
+ devsecops: 'devsecops',
+ emergingdd: 'emerging-disease-detection',
+ federatedobservability: 'federated-edge-observability',
+ hypershift: 'hypershift',
+ imageclass: 'emerging-disease-detection',
+ industrialedge: 'industrial-edge',
+ ingressmeshbgp: 'ingress-mesh-bgp',
+ layeredzerotrust: 'layered-zero-trust',
+ manuela: 'industrial-edge',
+ mcgitops: 'multicloud-gitops',
+ mcgitopshcp: 'multicloud-gitops',
+ mcgitopsstandalone: 'multicloud-gitops',
+ mcgitopsamx: 'multicloud-gitops-amx',
+ mcgitopsqat: 'multicloud-gitops-qat',
+ mcgitopssgx: 'multicloud-gitops-sgx',
+ mcgitopsrhoai: 'multicloud-gitops-amx-rhoai',
+ medicaldiag: 'medical-diagnosis',
+ netapp: 'netapp-dr-starter-kit',
+ openshiftai: 'openshift-ai',
+ omnicloud: 'omnicloud',
+ patternsoperator: '/learn/using-validated-pattern-operator',
+ portworx: 'portworx-dr',
+ ragllm: 'rag-llm-gitops',
+ ramendr: 'ramendr-starter-kit',
+ retail: 'retail',
+ telco: 'telco-hub',
+ telcohub: 'telco-hub',
+ travelops: 'travelops',
+ vsk: 'virtualization-starter-kit'
+}
+
function pattern_url (key) {
- if (key == 'aegitops') {
- return '/patterns/ansible-edge-gitops/'
+ if (key == null || key === '') {
+ return '/patterns/'
}
- if (key == 'devsecops') {
- return '/patterns/devsecops/'
- }
- if (key == 'industrialedge') {
- return '/patterns/industrial-edge/'
- }
- if (key == 'mcgitops') {
- return '/patterns/multicloud-gitops/'
- }
- if (key == 'medicaldiag') {
- return '/patterns/medical-diagnosis/'
- }
- if (key == 'ragllm') {
- return '/patterns/rag-llm-gitops/'
- }
- if (key == 'openshiftai') {
- return '/patterns/openshift-ai/'
+ var slug = CI_PATTERN_DOC_SLUG[key]
+ if (slug != null) {
+ if (slug.charAt(0) === '/') {
+ return slug.endsWith('/') ? slug : (slug + '/')
+ }
+ return '/patterns/' + slug + '/'
}
- if (key == 'agof') {
- return '/patterns/ansible-gitops-framework/'
+ // Already matches section slug (e.g. telco-hub, cockroachdb)
+ if (/^[a-z0-9]+(-[a-z0-9]+)*$/.test(key)) {
+ return '/patterns/' + key + '/'
}
-
return '/patterns/' + key + '/'
}
@@ -242,13 +269,29 @@ function toTitleCase (str) {
// ============================================
function jsonSuccess() {
- this.callback.apply(this, this.arguments);
+ if (this.status < 200 || this.status >= 300) {
+ console.warn('getJSON HTTP', this.status, this.responseURL)
+ }
+ this.callback.apply(this, this.arguments)
}
function jsonError() {
console.error(this.statusText);
}
+function parseBadgeJsonResponseText (text) {
+ if (text == null) return null
+ var t = String(text).replace(/^\uFEFF/, '').trim()
+ if (t.length === 0) return null
+ var first = t.charCodeAt(0)
+ if (first === 0x3C) return null // '<' — XML/HTML/S3 error bodies
+ try {
+ return JSON.parse(t)
+ } catch (e) {
+ return null
+ }
+}
+
function getJSON(url, callback, ...args) {
const jsonRequest = new XMLHttpRequest();
jsonRequest.callback = callback;
@@ -288,6 +331,12 @@ function rowTitle (field, value) {
function renderSetButtons(sets){
var currentURL = new URL(window.location.href)
+ if (currentURL.searchParams.get('view') === 'classic') {
+ ;['pattern', 'platform', 'version', 'date', 'sort'].forEach(function (k) {
+ currentURL.searchParams.delete(k)
+ })
+ currentURL.searchParams.set('view', 'classic')
+ }
const queryString = window.location.search
const urlParams = new URLSearchParams(queryString)
var setList = {'GA': 'GA', 'early': 'Pre-release', 'all': 'All'}
@@ -311,7 +360,22 @@ function renderSetButtons(sets){
}
function renderSingleBadge (key, field, linkType, badge_url) {
- var json_obj = JSON.parse(this.responseText)
+ var json_obj = parseBadgeJsonResponseText(this.responseText)
+ if (json_obj == null) {
+ var failedEl = document.getElementById(key + '-' + field)
+ if (failedEl) {
+ var fallbackLink = document.createElement('a')
+ fallbackLink.href = badge_url
+ fallbackLink.className = 'ci-label ci-label-load-error'
+ fallbackLink.target = '_blank'
+ fallbackLink.rel = 'noopener noreferrer'
+ fallbackLink.textContent = 'Unavailable'
+ fallbackLink.setAttribute('aria-label', 'Open badge URL in a new tab')
+ failedEl.replaceWith(fallbackLink)
+ }
+ console.warn('Badge JSON parse failed:', badge_url)
+ return
+ }
var branchLabel = json_obj.patternBranch
var color = json_obj.color
@@ -370,6 +434,19 @@ function renderBadges (badges, field, value, links) {
return badgeText
}
+function legacyFilteredHref (field, rowValue) {
+ var cur = new URLSearchParams(window.location.search)
+ if (cur.get('view') !== 'classic') {
+ return '?' + field + '=' + encodeURIComponent(rowValue)
+ }
+ var next = new URLSearchParams()
+ next.set('view', 'classic')
+ var setsVal = cur.get('sets')
+ if (setsVal != null && setsVal !== '') next.set('sets', setsVal)
+ next.set(field, rowValue)
+ return '?' + next.toString()
+}
+
function createFilteredHorizontalTable (badges, field, value, titles, links) {
tableText = "
"
if (titles) {
@@ -385,7 +462,7 @@ function createFilteredHorizontalTable (badges, field, value, titles, links) {
if (value == null && field == 'pattern') {
tableText += "
" + rowTitle(field, r) + ''
} else if (value == null) {
- tableText += "
" + rowTitle(field, r) + ''
+ tableText += "
" + rowTitle(field, r) + ''
}
tableText += ''
@@ -404,6 +481,9 @@ function processBadgesLegacy (badges, options) {
const links = options.get("links")
var htmlText = ""
+ if (options.get('show_dashboard_tabs') === true) {
+ htmlText += renderTabs('classic')
+ }
htmlText += renderSetButtons(options.get('sets'))
if (filter_field === 'date') {
@@ -424,9 +504,17 @@ function processBadgesLegacy (badges, options) {
htmlText += createFilteredHorizontalTable(badges, filter_field, null, true, links)
}
} else {
+ // Classic home: same long-scroll layout as pre-redesign CI page — all groupings at once
badges.sort(function (a, b) { return -1 * a.date.localeCompare(b.date) })
badges.sort(patternVertSort)
+ htmlText += createFilteredHorizontalTable(badges, 'date', null, true, links)
htmlText += createFilteredHorizontalTable(badges, 'pattern', null, true, links)
+ htmlText += createFilteredHorizontalTable(badges, 'platform', null, true, links)
+ htmlText += createFilteredHorizontalTable(badges, 'version', null, true, links)
+ var setsVal = options.get('sets') || 'GA'
+ if (String(setsVal).includes('all') || String(setsVal).includes('early')) {
+ htmlText += createFilteredHorizontalTable(badges, 'operator', null, true, links)
+ }
}
document.getElementById(options.get('target')).innerHTML = htmlText
@@ -509,6 +597,7 @@ function getLatestPerPatternPlatform (badges) {
function getCurrentTab () {
var params = new URLSearchParams(window.location.search)
+ if (params.get('view') === 'classic') return 'classic'
if (params.get('date') != null) return 'history'
if (params.get('pattern') != null) {
var val = params.get('pattern')
@@ -521,9 +610,33 @@ function getCurrentTab () {
}
function buildTabUrl (paramKey) {
- var base = window.location.pathname
- if (!paramKey) return base
- return base + '?' + paramKey + '=all'
+ var u = new URL(window.location.href)
+ ;['pattern', 'platform', 'version', 'date', 'view'].forEach(function (k) {
+ u.searchParams.delete(k)
+ })
+ if (paramKey != null) {
+ u.searchParams.set(paramKey, 'all')
+ }
+ var qs = u.searchParams.toString()
+ return u.pathname + (qs ? '?' + qs : '')
+}
+
+function buildPatternDetailHref (pattern) {
+ var u = new URL(window.location.href)
+ u.searchParams.delete('view')
+ u.searchParams.set('pattern', pattern)
+ var qs = u.searchParams.toString()
+ return u.pathname + (qs ? '?' + qs : '')
+}
+
+function buildClassicTabUrl () {
+ var u = new URL(window.location.href)
+ ;['pattern', 'platform', 'version', 'date', 'sort'].forEach(function (k) {
+ u.searchParams.delete(k)
+ })
+ u.searchParams.set('view', 'classic')
+ var qs = u.searchParams.toString()
+ return u.pathname + (qs ? '?' + qs : '')
}
function renderTabs (activeTab) {
@@ -531,7 +644,8 @@ function renderTabs (activeTab) {
{ id: 'overview', label: 'Overview', href: buildTabUrl(null) },
{ id: 'infrastructure', label: 'By Platform', href: buildTabUrl('platform') },
{ id: 'version', label: 'By Version', href: buildTabUrl('version') },
- { id: 'history', label: 'History', href: buildTabUrl('date') }
+ { id: 'history', label: 'History', href: buildTabUrl('date') },
+ { id: 'classic', label: 'Classic', href: buildClassicTabUrl() }
]
var html = '
'
@@ -545,7 +659,7 @@ function renderTabs (activeTab) {
}
function renderSortControl (currentSort) {
- var html = '