Skip to content

Commit 427f5b8

Browse files
author
Paul C
committed
feat: WolfNote capture produces clean professional sysadmin reports
Extracts structured data (tables, stats, text) from the current view instead of dumping raw HTML. Output is a self-contained styled document with dark theme, monospace font, section headers, metadata table, and proper margins constrained to 800px max-width.
1 parent 666c4ab commit 427f5b8

1 file changed

Lines changed: 96 additions & 6 deletions

File tree

web/js/app.js

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21279,16 +21279,106 @@ async function wolfnoteFromCurrentView() {
2127921279
if (!visible) return;
2128021280

2128121281
const pageTitle = document.getElementById('page-title')?.textContent || 'WolfStack';
21282+
const hostname = document.getElementById('hostname-display')?.textContent || '';
2128221283
const timestamp = new Date().toLocaleString();
2128321284
const title = `${pageTitle} — ${timestamp}`;
2128421285

21285-
// Clone the visible content and extract meaningful HTML
21286-
const clone = visible.cloneNode(true);
21287-
// Remove scripts, hidden elements, and buttons to clean up
21288-
clone.querySelectorAll('script, style, .modal-overlay, button, .btn').forEach(el => el.remove());
21286+
// Extract clean data from the page instead of raw HTML
21287+
const sections = [];
21288+
21289+
// Extract card contents — each .card is a section
21290+
visible.querySelectorAll('.card').forEach(card => {
21291+
const header = card.querySelector('.card-header h3, .card-header h4');
21292+
const heading = header ? header.textContent.trim() : '';
21293+
21294+
// Extract tables as clean HTML tables
21295+
const tables = card.querySelectorAll('table.data-table, table');
21296+
tables.forEach(tbl => {
21297+
const rows = [];
21298+
tbl.querySelectorAll('thead tr').forEach(tr => {
21299+
const cells = [];
21300+
tr.querySelectorAll('th').forEach(th => {
21301+
const text = th.textContent.trim();
21302+
if (text && text !== 'Actions') cells.push(`<th style="padding:8px 12px;text-align:left;border-bottom:2px solid #444;font-weight:600;font-size:13px;">${escapeHtml(text)}</th>`);
21303+
});
21304+
if (cells.length) rows.push(`<tr style="background:#2a2a3a;">${cells.join('')}</tr>`);
21305+
});
21306+
tbl.querySelectorAll('tbody tr').forEach(tr => {
21307+
const cells = [];
21308+
tr.querySelectorAll('td').forEach((td, i) => {
21309+
// Skip the last column if it's Actions
21310+
const headerRow = tbl.querySelector('thead tr');
21311+
const ths = headerRow ? headerRow.querySelectorAll('th') : [];
21312+
if (ths[i] && ths[i].textContent.trim() === 'Actions') return;
21313+
const text = td.textContent.trim();
21314+
cells.push(`<td style="padding:6px 12px;border-bottom:1px solid #333;font-size:13px;">${escapeHtml(text)}</td>`);
21315+
});
21316+
if (cells.length) rows.push(`<tr>${cells.join('')}</tr>`);
21317+
});
21318+
if (rows.length) {
21319+
sections.push({ heading, html: `<table style="width:100%;border-collapse:collapse;margin:8px 0 16px 0;">${rows.join('')}</table>` });
21320+
}
21321+
});
2128921322

21290-
// Get the HTML content, wrapped with margins
21291-
const content = `<div style="margin: 16px 20px;"><h2>${escapeHtml(pageTitle)}</h2><p style="color:#888;font-size:12px;">Captured from WolfStack on ${escapeHtml(timestamp)}</p><hr style="border:none;border-top:1px solid #333;margin:12px 0;">${clone.innerHTML}</div>`;
21323+
// If no tables, extract key-value pairs and text content
21324+
if (tables.length === 0) {
21325+
const body = card.querySelector('.card-body');
21326+
if (body) {
21327+
// Look for stat items, grid items, or just text
21328+
const items = [];
21329+
body.querySelectorAll('.stat-item, [class*="stat"], [class*="metric"]').forEach(el => {
21330+
const label = el.querySelector('.stat-label, [class*="label"]');
21331+
const value = el.querySelector('.stat-value, [class*="value"]');
21332+
if (label && value) items.push(`<li><strong>${escapeHtml(label.textContent.trim())}</strong>: ${escapeHtml(value.textContent.trim())}</li>`);
21333+
});
21334+
if (items.length) {
21335+
sections.push({ heading, html: `<ul style="margin:8px 0 16px 0;padding-left:20px;line-height:1.8;">${items.join('')}</ul>` });
21336+
} else {
21337+
// Fallback: extract visible text content
21338+
const text = body.innerText.trim();
21339+
if (text && text.length > 5) {
21340+
sections.push({ heading, html: `<p style="margin:8px 0 16px 0;line-height:1.7;white-space:pre-wrap;">${escapeHtml(text.substring(0, 3000))}</p>` });
21341+
}
21342+
}
21343+
}
21344+
}
21345+
});
21346+
21347+
// If no cards found, extract raw text as fallback
21348+
if (sections.length === 0) {
21349+
const text = visible.innerText.trim();
21350+
if (text) sections.push({ heading: '', html: `<p style="line-height:1.7;white-space:pre-wrap;">${escapeHtml(text.substring(0, 5000))}</p>` });
21351+
}
21352+
21353+
// Build a professional sysadmin report document
21354+
const sectionCount = sections.length;
21355+
const body = sections.map((s, i) =>
21356+
(s.heading ? `<h3 style="margin:28px 0 10px 0;font-size:15px;font-weight:600;color:#e2e8f0;letter-spacing:0.3px;text-transform:uppercase;border-left:3px solid #6366f1;padding-left:12px;">${escapeHtml(s.heading)}</h3>` : '') + s.html +
21357+
(i < sectionCount - 1 ? '<div style="border-bottom:1px solid #1e293b;margin:16px 0;"></div>' : '')
21358+
).join('');
21359+
21360+
const content = `<div style="max-width:800px;margin:0 auto;padding:36px 44px;font-family:'SF Mono',SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:#cbd5e1;background:#0f172a;border-radius:12px;line-height:1.6;border:1px solid #1e293b;">
21361+
<div style="display:flex;align-items:center;gap:12px;margin-bottom:20px;">
21362+
<div style="width:40px;height:40px;border-radius:8px;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0;">&#128058;</div>
21363+
<div>
21364+
<h1 style="margin:0;font-size:20px;font-weight:700;color:#f1f5f9;letter-spacing:-0.3px;">WolfStack &mdash; ${escapeHtml(pageTitle)}</h1>
21365+
<p style="margin:2px 0 0 0;font-size:12px;color:#64748b;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">Infrastructure Report${hostname ? ' &mdash; ' + escapeHtml(hostname) : ''}</p>
21366+
</div>
21367+
</div>
21368+
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;font-size:12px;color:#94a3b8;">
21369+
<tr>
21370+
<td style="padding:6px 0;border-bottom:1px solid #1e293b;"><strong style="color:#cbd5e1;">Generated</strong></td>
21371+
<td style="padding:6px 0;border-bottom:1px solid #1e293b;">${escapeHtml(timestamp)}</td>
21372+
<td style="padding:6px 0;border-bottom:1px solid #1e293b;"><strong style="color:#cbd5e1;">View</strong></td>
21373+
<td style="padding:6px 0;border-bottom:1px solid #1e293b;">${escapeHtml(pageTitle)}</td>
21374+
</tr>
21375+
</table>
21376+
${body}
21377+
<div style="margin-top:32px;padding-top:16px;border-top:1px solid #1e293b;display:flex;justify-content:space-between;align-items:center;">
21378+
<span style="font-size:11px;color:#475569;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">This report was auto-generated by WolfStack. Data reflects the state at the time of capture.</span>
21379+
<span style="font-size:11px;color:#6366f1;font-weight:600;">wolfstack.org</span>
21380+
</div>
21381+
</div>`;
2129221382

2129321383
try {
2129421384
const resp = await fetch('/api/wolfnote/notes', {

0 commit comments

Comments
 (0)