import { trackFeatureUse } from './analytics.js';
export function createHelpIcon(text) {
const span = document.createElement('span');
span.className = 'help-icon';
span.textContent = '?';
span.setAttribute('role', 'img');
span.setAttribute('aria-label', text);
span.setAttribute('tabindex', '0');
let tip = null;
let tracked = false;
const show = () => {
if (tip) return;
if (!tracked) { tracked = true; trackFeatureUse('help_icon_hover', { context: text.slice(0, 40) }); }
tip = document.createElement('div');
tip.className = 'help-tooltip';
tip.textContent = text;
span.appendChild(tip);
};
const hide = () => {
if (tip) { tip.remove(); tip = null; }
};
span.addEventListener('mouseenter', show);
span.addEventListener('mouseleave', hide);
span.addEventListener('focus', show);
span.addEventListener('blur', hide);
return span;
}
export function createTabIntro(tabId, description, tips = []) {
const storageKey = `idb-tab-intro-${tabId}`;
const isCollapsed = localStorage.getItem(storageKey) === 'collapsed';
const wrapper = document.createElement('div');
wrapper.className = isCollapsed ? '' : 'tab-intro';
const update = (collapsed) => {
if (collapsed) {
wrapper.className = '';
wrapper.innerHTML = '';
const expandBtn = document.createElement('button');
expandBtn.className = 'text-gray-600 hover:text-innodb-cyan text-xs flex items-center gap-1 mt-1 transition-colors';
expandBtn.innerHTML = `<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke-width="2"/><path stroke-width="2" d="M12 16v-4m0-4h.01"/></svg> Show tab info`;
expandBtn.setAttribute('aria-expanded', 'false');
expandBtn.addEventListener('click', () => {
localStorage.removeItem(storageKey);
trackFeatureUse('tab_intro_toggle', { tab: tabId, action: 'expand' });
update(false);
});
wrapper.appendChild(expandBtn);
} else {
wrapper.className = 'tab-intro';
let html = `<div class="flex items-start justify-between gap-3">
<div>${description}`;
if (tips.length > 0) {
html += '<ul>' + tips.map((t) => `<li>${t}</li>`).join('') + '</ul>';
}
html += `</div>
<button class="text-gray-600 hover:text-gray-400 flex-shrink-0 mt-0.5" aria-label="Collapse tab info" aria-expanded="true" title="Hide">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>`;
wrapper.innerHTML = html;
wrapper.querySelector('button').addEventListener('click', () => {
localStorage.setItem(storageKey, 'collapsed');
trackFeatureUse('tab_intro_toggle', { tab: tabId, action: 'collapse' });
update(true);
});
}
};
update(isCollapsed);
return wrapper;
}
export function createLegend(items) {
const div = document.createElement('div');
div.className = 'help-legend';
for (const item of items) {
const el = document.createElement('span');
el.className = 'help-legend-item';
el.innerHTML = `<span class="help-legend-swatch ${item.className}"></span>${item.label}`;
div.appendChild(el);
}
return div;
}
export function isFirstVisit() {
return localStorage.getItem('idb-welcomed') !== 'true';
}
export function markWelcomed() {
localStorage.setItem('idb-welcomed', 'true');
}
export function insertTabIntro(container, tabId) {
const info = TAB_DESCRIPTIONS[tabId];
if (!info) return;
const wrapper = container.querySelector('.p-6') || container;
const header = wrapper.querySelector('.flex');
if (header) {
const intro = createTabIntro(tabId, info.description, info.tips);
header.after(intro);
}
}
export const TAB_DESCRIPTIONS = {
overview: {
description: 'Tablespace summary — page count, page types, space ID, and file-level metadata. Start here to understand your tablespace structure.',
tips: ['Check Vendor to confirm MySQL, Percona, or MariaDB origin', 'Page type distribution shows what the tablespace is used for'],
},
pages: {
description: 'Detailed page-level analysis — headers, record counts, fill factors, and decoded data. Click any page to inspect its contents.',
tips: ['Use the page number input to jump to a specific page', 'Click "Decoded Records" on INDEX pages to see row data'],
},
checksums: {
description: 'Validates page checksums using CRC-32C, legacy InnoDB, or MariaDB full_crc32 algorithms. Invalid checksums indicate possible data corruption.',
tips: ['Green = valid, Red = corrupt or tampered', 'LSN mismatches may indicate torn writes'],
},
sdi: {
description: 'Raw SDI (Serialized Dictionary Information) metadata from MySQL 8.0+ tablespaces. Contains table and index definitions in JSON format.',
tips: ['Pre-8.0 tablespaces do not contain SDI data', 'Use the Schema tab for a formatted view of this data'],
},
schema: {
description: 'Reconstructed DDL (CREATE TABLE) from tablespace metadata. Shows columns, indexes, and foreign keys.',
tips: ['Copy the DDL to recreate the table structure', 'Column types are extracted from SDI metadata'],
},
hex: {
description: 'Raw hex view of page bytes. Use byte offset search to locate specific data patterns.',
tips: ['Navigate by page number to inspect specific pages', 'Colored output: cyan = offset, green = hex bytes, amber = ASCII'],
},
recovery: {
description: 'Assesses page-level recoverability and estimates salvageable records. Shows corruption classification per page.',
tips: ['Corruption types: zero-fill, random noise, torn write, bitrot', 'Higher salvageable record count = more data to recover'],
},
heatmap: {
description: 'Visual map of all pages colored by type. Click any cell to jump to that page. In diff mode, highlights modified, added, and removed pages.',
tips: ['Each cell represents one page — hover for details', 'Diff overlay shows changes between two tablespace files'],
},
health: {
description: 'B+Tree index health metrics — fill factor, fragmentation, garbage ratio, and bloat grades (A through F). Low fill factors or high bloat suggest optimization opportunities.',
tips: ['Click an index row to filter the Pages tab to that index', 'Toggle the B+Tree visualization for a graphical view'],
},
verify: {
description: 'Structural integrity checks — page chain continuity, LSN ordering, and format validation. Findings are categorized by severity.',
tips: ['Page chain breaks indicate structural damage', 'LSN ordering issues may signal incomplete crash recovery'],
},
compat: {
description: 'Version compatibility analysis. Auto-detects the source MySQL version and checks for upgrade or downgrade issues.',
tips: ['Source version is detected from SDI mysqld_version_id', 'Select a target version to see compatibility findings'],
},
undo: {
description: 'Undo tablespace analysis — rollback segments, transaction history, and undo record details.',
tips: ['Requires an undo tablespace file (.ibu)', 'Shows active vs cached rollback segments'],
},
spatial: {
description: 'R-Tree index visualization for spatial (GIS) data. Shows minimum bounding rectangles for each spatial index entry.',
tips: ['Only visible when spatial indexes are present', 'Zoom and pan to explore large spatial datasets'],
},
undelete: {
description: 'Recovers deleted records from tablespace pages and undo logs. Confidence scores indicate how reliable each recovered record is.',
tips: ['Filter by confidence to focus on high-quality recoveries', 'Export recovered data as CSV, JSON, or SQL INSERT statements'],
},
simulate: {
description: 'Simulates InnoDB crash recovery at levels 1 through 6. Shows which pages would be affected and predicted recovery outcomes.',
tips: ['Level 1 = skip corrupt pages, Level 6 = skip redo log application', 'Use this to understand crash recovery impact before modifying innodb_force_recovery'],
},
timeline: {
description: 'Unified modification timeline correlating redo log, undo log, and binary log events into a single chronological view.',
tips: ['Drop multiple log files to see cross-referenced events', 'Filter by page number or transaction to narrow the view'],
},
redolog: {
description: 'Redo log block analysis — LSN ranges, checkpoint info, and log record types.',
tips: ['Blocks are 512-byte units within the redo log', 'Empty blocks indicate unused log space'],
},
binlog: {
description: 'Binary log event listing — transactions, row changes, DDL statements, and replication metadata. Drop a .ibd tablespace to correlate row events with specific pages.',
tips: [
'Events are shown in file order with timestamps',
'CRC-32C checksum validation per event',
'Drop a tablespace (.ibd) file to see which page each row event affects',
'Click a page number to navigate to that page in the Pages tab',
],
},
diff: {
description: 'Side-by-side comparison of two tablespace files. Shows page-level differences in headers, checksums, and content.',
tips: ['Drop two .ibd files to enable this tab', 'Modified pages show which header fields changed'],
},
backup: {
description: 'Compares page LSNs between a backup and current tablespace to identify pages changed since the backup was taken.',
tips: ['Drop two files: backup (older) and current', 'Useful for validating incremental backup coverage'],
},
audit: {
description: 'Directory-wide integrity analysis across multiple tablespace files. Shows health summaries, checksum mismatches, and bloat scores.',
tips: ['Drop 3 or more .ibd files to enable audit mode', 'Sort by fill factor or bloat grade to find problem tables'],
},
};