Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions custom-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
const POWERED_BY_TEXT = 'Powered by Meshery Authors';
const POWERED_BY_SELECTOR =
'a[href="https://allurereport.org"], a[href="https://allurereport.org/"]';
const CUSTOM_NAV_ID = 'meshery-report-nav';
const LEGACY_NAV_BUTTON_TEXT = 'report';
const LEGACY_NAV_PANEL_TOKENS = ['report', 'graphs', 'timeline'];
const SECTION_ICON_SELECTOR =
'button, [role="button"], [role="menuitemcheckbox"], [role="menuitem"], [class*="menu-item"]';
const OBSERVER_OPTIONS = { childList: true, subtree: true };
let brandingScheduled = false;
let legacyNavHiddenOnce = false;

function createMesheryLogo(className, size) {
const logo = document.createElement('img');
Expand Down Expand Up @@ -68,6 +72,128 @@
return changed;
}

function normalizePathname(pathname) {
const resolvedPathname = new URL(pathname, window.location.href).pathname;
const withoutIndex = resolvedPathname.replace(/\/index\.html$/, '/');
return withoutIndex === '/' ? withoutIndex : withoutIndex.replace(/\/+$/, '');
}

function getReportNavigationItems() {
if (!Array.isArray(window.mesheryReportNav)) {
return [];
}

return window.mesheryReportNav.filter((item) => item && item.href && item.label);
}

function createReportNavigation() {
const navItems = getReportNavigationItems();

if (!navItems.length) {
return null;
}

const nav = document.createElement('nav');
nav.id = CUSTOM_NAV_ID;
nav.className = 'meshery-report-nav';
nav.setAttribute('aria-label', 'Dashboard navigation');

const list = document.createElement('div');
list.className = 'meshery-report-nav__list';

const currentPath = normalizePathname(window.location.pathname);

navItems.forEach((item) => {
const link = document.createElement('a');
const itemPath = normalizePathname(item.href);

link.className = 'meshery-report-nav__link';
link.href = item.href;
link.textContent = item.label;

if (itemPath === currentPath) {
link.classList.add('meshery-report-nav__link--active');
link.setAttribute('aria-current', 'page');
}

list.appendChild(link);
});

nav.appendChild(list);
return nav;
}

function insertReportNavigation() {
if (document.getElementById(CUSTOM_NAV_ID)) {
return false;
}

const nav = createReportNavigation();

if (!nav) {
return false;
}

const appRoot = document.getElementById('app');

if (appRoot && appRoot.parentNode) {
appRoot.parentNode.insertBefore(nav, appRoot);
} else {
document.body.prepend(nav);
}

return true;
}

function hideLegacyReportMenu() {
if (legacyNavHiddenOnce) {
return false;
}

let changed = false;

document.querySelectorAll('button').forEach((button) => {
if (button.dataset.mesheryLegacyNavHidden === 'true') {
return;
}

if (normalizeText(button.textContent) !== LEGACY_NAV_BUTTON_TEXT) {
return;
}
Comment on lines +148 to +162
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because applyBranding() is typically triggered multiple times (often via a MutationObserver in this kind of customization script), scanning all buttons and panels each time can become expensive on large Allure pages. Consider adding a single page-level guard (e.g., a module-scoped boolean like legacyNavHiddenOnce) and early-return once the legacy menu has been hidden, and/or narrowing the selectors to the known navigation container instead of querying the entire document each time.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 0611949.

hideLegacyReportMenu() now uses a module-scoped legacyNavHiddenOnce guard and exits early after the legacy nav has been hidden, so applyBranding() no longer keeps rescanning the full page on later observer runs.

Screenshot: updated navigation


button.dataset.mesheryLegacyNavHidden = 'true';
button.hidden = true;
button.setAttribute('aria-hidden', 'true');
button.style.display = 'none';
changed = true;
});

document.querySelectorAll('[role="complementary"], aside').forEach((panel) => {
if (panel.dataset.mesheryLegacyNavHidden === 'true') {
return;
}

const panelText = normalizeText(panel.textContent);
const isLegacyNavPanel = LEGACY_NAV_PANEL_TOKENS.every((token) => panelText.includes(token));

if (!isLegacyNavPanel) {
return;
}

panel.dataset.mesheryLegacyNavHidden = 'true';
panel.hidden = true;
panel.setAttribute('aria-hidden', 'true');
panel.style.display = 'none';
changed = true;
});

if (changed) {
legacyNavHiddenOnce = true;
}

return changed;
}

function replaceLoaderLogo() {
let changed = false;

Expand Down Expand Up @@ -110,6 +236,8 @@
}

function applyBranding() {
insertReportNavigation();
hideLegacyReportMenu();
replacePoweredBy();
replaceLoaderLogo();
replaceSectionPickerLogos();
Expand Down
48 changes: 48 additions & 0 deletions custom-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,54 @@
text-decoration: none;
}

.meshery-report-nav {
background: var(--bg-base-primary, #ffffff);
border-bottom: 1px solid var(--on-border-primary, #cbd5e1);
padding: 0.75rem 1rem;
position: sticky;
top: 0;
z-index: 1000;
}

.meshery-report-nav__list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0 auto;
max-width: 1200px;
}

.meshery-report-nav__link {
background: var(--bg-control-secondary, rgba(148, 163, 184, 0.12));
border: 1px solid var(--on-border-muted, rgba(148, 163, 184, 0.3));
border-radius: 999px;
color: inherit;
font-size: 0.875rem;
line-height: 1.2;
padding: 0.5rem 0.875rem;
text-decoration: none;
transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
}

.meshery-report-nav__link:hover {
background: var(--bg-control-secondary-medium, rgba(148, 163, 184, 0.2));
border-color: var(--on-border-primary, rgba(100, 116, 139, 0.4));
}

.meshery-report-nav__link:focus-visible {
background: var(--bg-control-secondary-medium, rgba(148, 163, 184, 0.2));
border-color: var(--on-border-primary, rgba(100, 116, 139, 0.4));
outline: 2px solid currentColor;
outline-offset: 2px;
}

.meshery-report-nav__link--active {
background: var(--bg-control-secondary-medium, rgba(148, 163, 184, 0.2));
border-color: var(--on-border-primary, rgba(100, 116, 139, 0.4));
color: inherit;
font-weight: 600;
}

.meshery-powered-by {
align-items: center;
color: inherit;
Expand Down
80 changes: 78 additions & 2 deletions customize-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@ const customStyle = path.join(__dirname, 'custom-style.css');
const assetFiles = ['custom-script.js', 'custom-style.css'];
const skippedDirectories = new Set(['data', 'history', 'widgets']);
const styleTag = ' <link rel="stylesheet" href="custom-style.css">\n';
const navDataMarker = 'window.mesheryReportNav =';
const scriptTag = ' <script defer src="custom-script.js"></script>\n';
const navLabelOverrides = {
'': 'Home',
dashboard: 'Dashboard',
meshery: 'Meshery',
mesheryctl: 'Mesheryctl',
layer5Cloud: 'Layer5 Cloud',
kanvas: 'Kanvas',
};
const navOrder = new Map([
['', 0],
['dashboard', 1],
['meshery', 2],
['mesheryctl', 3],
['layer5Cloud', 4],
['kanvas', 5],
]);

function findReportPages(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
Expand Down Expand Up @@ -43,13 +60,70 @@ function injectBeforeClosingTag(html, tagName, snippet) {
return html.replace(closingTagPattern, (match) => `${snippet}${match}`);
}

function ensureInjectedMarkup(html) {
function formatReportLabel(relativeDir) {
if (relativeDir in navLabelOverrides) {
return navLabelOverrides[relativeDir];
}

const reportName = relativeDir.split(path.sep).filter(Boolean).pop() || navLabelOverrides[''];
return reportName
.replace(/[-_]+/g, ' ')
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
.replace(/\b\w/g, (letter) => letter.toUpperCase());
}

function toRelativeHref(fromDir, toDir) {
const relativePath = path.relative(fromDir, toDir).split(path.sep).join('/');
return relativePath ? `${relativePath}/` : './';
}

function compareNavEntries(left, right) {
const leftOrder = navOrder.get(left.relativeDir) ?? Number.MAX_SAFE_INTEGER;
const rightOrder = navOrder.get(right.relativeDir) ?? Number.MAX_SAFE_INTEGER;

if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}

return left.label.localeCompare(right.label);
}

function buildNavEntries(reportPages) {
return reportPages
.map((reportPage) => {
const reportPageDir = path.dirname(reportPage);
const relativeDir = path.relative(reportDir, reportPageDir);

return {
label: formatReportLabel(relativeDir),
relativeDir,
reportPageDir,
};
})
.sort(compareNavEntries);
}

function createNavDataScript(currentPageDir, navEntries) {
const navData = navEntries.map(({ label, relativeDir, reportPageDir }) => ({
label,
href: toRelativeHref(currentPageDir, reportPageDir),
}));
const serializedNavData = JSON.stringify(navData).replace(/</g, '\\u003c');

return ` <script>${navDataMarker} ${serializedNavData};</script>\n`;
}
Comment on lines +106 to +114
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Injecting raw JSON.stringify(navData) into an inline <script> can be broken (and become an XSS/HTML-injection vector) if any string value contains </script> or similar sequences. Consider either (a) emitting the JSON via <script type=\"application/json\" ...> and parsing it in custom-script.js, or (b) escaping < (e.g., replace < with \\u003c) in the serialized JSON before embedding it in HTML.

Copilot uses AI. Check for mistakes.

function ensureInjectedMarkup(html, navDataScript) {
let updated = html;

if (!updated.includes('custom-style.css')) {
updated = injectBeforeClosingTag(updated, 'head', styleTag);
}

if (!updated.includes(navDataMarker)) {
updated = injectBeforeClosingTag(updated, 'body', navDataScript);
}

if (!updated.includes('custom-script.js')) {
updated = injectBeforeClosingTag(updated, 'body', scriptTag);
}
Expand All @@ -74,11 +148,13 @@ if (!fs.existsSync(reportDir)) {

const reportPages = findReportPages(reportDir);
const copiedAssetDirs = new Set();
const navEntries = buildNavEntries(reportPages);

for (const reportPage of reportPages) {
const reportPageDir = path.dirname(reportPage);
const html = fs.readFileSync(reportPage, 'utf8');
const updatedHtml = ensureInjectedMarkup(html);
const navDataScript = createNavDataScript(reportPageDir, navEntries);
const updatedHtml = ensureInjectedMarkup(html, navDataScript);

if (!copiedAssetDirs.has(reportPageDir)) {
copyAssets(reportPageDir);
Expand Down
65 changes: 0 additions & 65 deletions history.json

This file was deleted.

Loading