Overall Uptime (24h)
โ
Last 24 hours
Recent Scans
โ
Last 5 results
' },
{ priority: 'medium', issue: 'Missing alt attributes on 3 images', fix: 'Add descriptive alt attributes to all images for screen reader accessibility.', impact: '+5 accessibility score', effort: 'Low', category: 'Accessibility', code_snippet: '
' },
{ priority: 'low', issue: 'Page title missing keywords', fix: 'Include your primary target keyword in the page title, ideally within the first 60 characters.', impact: '+3 SEO score', effort: 'Low', category: 'SEO', code_snippet: 'Affordable Web Design Services | YourBrand' },
]
};
function authHeaders() {
return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token };
}
async function apiFetch(url, opts = {}) {
const res = await fetch(url, { ...opts, headers: { ...authHeaders(), ...(opts.headers || {}) } });
const data = await res.json();
// All clients have full access โ no plan restrictions
if (false && data.upgrade_required) {
showToast('โฌ๏ธ ' + data.message, 'error');
return null;
}
return data;
}
function scoreColor(s) {
if (!s && s !== 0) return 'var(--text-muted)';
if (s >= 80) return 'var(--green)';
if (s >= 60) return 'var(--gold)';
if (s >= 40) return 'var(--orange)';
return 'var(--coral)';
}
function scorePill(s) {
if (!s && s !== 0) return 'โ';
const cls = s >= 80 ? 'good' : s >= 60 ? 'ok' : 'bad';
return `${s}`;
}
function timeAgo(dateStr) {
if (!dateStr) return 'Never';
const d = new Date(dateStr);
const now = new Date();
const diff = Math.floor((now - d) / 1000);
if (diff < 60) return 'Just now';
if (diff < 3600) return Math.floor(diff/60) + 'm ago';
if (diff < 86400) return Math.floor(diff/3600) + 'h ago';
return Math.floor(diff/86400) + 'd ago';
}
// โโ Views โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function showView(name) {
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
document.querySelectorAll('.sidebar-item').forEach(s => s.classList.remove('active'));
document.getElementById('view-' + name).classList.add('active');
const navEl = document.getElementById('nav-' + name);
if (navEl) navEl.classList.add('active');
// Load data for view
if (name === 'overview') loadOverview();
else if (name === 'sites') loadSites();
else if (name === 'alerts') loadAlerts();
else if (name === 'competitive') loadCompetitive();
else if (name === 'autofix') loadAutofix();
else if (name === 'reports') loadReports();
}
// โโ Plan โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function loadPlan() {
try {
const data = await apiFetch(API + '/v2/my-plan');
if (!data || !data.plan) return;
currentPlan = data.plan;
document.getElementById('planName').textContent = (currentPlan.label || 'Free') + ' Plan';
document.getElementById('planSites').textContent = (currentPlan.sites >= 999999 ? 'Unlimited' : currentPlan.sites) + ' sites ยท ' + (currentPlan.pages >= 999999 ? 'Unlimited' : currentPlan.pages.toLocaleString()) + ' pages';
} catch (e) {
// Keep default plan badge values already in HTML
}
}
// โโ Overview โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function renderOverviewData(sites, overallUptime, unreadAlerts, recentScans) {
allSites = sites;
document.getElementById('statSites').textContent = allSites.length;
document.getElementById('statSitesLimit').textContent = 'of ' + (currentPlan.sites >= 999999 ? 'โ' : currentPlan.sites);
document.getElementById('statUptime').textContent = overallUptime != null ? overallUptime + '%' : '99.9%';
document.getElementById('statAlerts').textContent = unreadAlerts || 0;
document.getElementById('statScans').textContent = recentScans ? recentScans.length : 0;
const alertCount = unreadAlerts || 0;
const badge = document.getElementById('alertCount');
if (alertCount > 0) { badge.style.display = 'flex'; badge.textContent = alertCount; } else { badge.style.display = 'none'; }
const grid = document.getElementById('overviewSiteCards');
if (!allSites.length) {
grid.innerHTML = `๐
No sites yet
Add your first site to start automated monitoring.
`;
} else {
grid.innerHTML = allSites.slice(0, 6).map(renderSiteCard).join('');
}
const scans = recentScans || [];
const tableDiv = document.getElementById('recentScansTable');
if (!scans.length) {
tableDiv.innerHTML = 'No scans yet. Run a scan โ
';
} else {
tableDiv.innerHTML = `| URL | Overall | Performance | SEO | Scanned |
${scans.map(s => `
| ${s.url} |
${scorePill(s.overall_score)} |
${s.performance_score || 'โ'} |
${s.seo_score || 'โ'} |
${timeAgo(s.created_at)} |
`).join('')}
`;
}
}
async function loadOverview() {
try {
const data = await apiFetch(API + '/v2/dashboard-stats');
if (!data || !data.success) {
renderOverviewData(SAMPLE_SITES, 99.9, 1, SAMPLE_RECENT_SCANS);
return;
}
renderOverviewData(data.sites || [], data.overall_uptime, data.unread_alerts, data.recent_scans);
} catch (e) {
renderOverviewData(SAMPLE_SITES, 99.9, 1, SAMPLE_RECENT_SCANS);
}
}
function renderSiteCard(site) {
const scores = site.latest_scores || {};
const statusClass = site.uptime_monitoring ? (site.uptime_pct == null ? 'unknown' : site.uptime_pct >= 99 ? 'up' : 'down') : 'unknown';
return `
${site.name || new URL(site.url).hostname}
${site.url}
${scores.overall != null ? `
${scores.overall||'โ'}
Overall
${scores.performance||'โ'}
Perf
${scores.security||'โ'}
Sec
${scores.accessibility||'โ'}
A11y
` : '
No scans yet โ run a scan
'}
${site.scan_frequency === 'manual' ? '๐
Manual' : (site.scan_frequency === 'daily' ? 'โฑ Daily auto-scan' : 'โฑ Weekly auto-scan')}
${site.uptime_monitoring ? `๐ก Uptime: ${site.uptime_pct != null ? site.uptime_pct + '%' : 'pending'}` : ''}
Last: ${timeAgo(site.last_scan_at)}
`;
}
// โโ Sites List โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function loadSites() {
let sites;
try {
const data = await apiFetch(API + '/v2/sites');
if (!data || !data.success) { sites = SAMPLE_SITES; } else { sites = data.sites; }
} catch (e) { sites = SAMPLE_SITES; }
allSites = sites;
const container = document.getElementById('sitesList');
if (!allSites.length) {
container.innerHTML = `๐
No sites added yet
Add sites to start automated scanning and monitoring.
`;
return;
}
container.innerHTML = allSites.map(site => `
${site.name || new URL(site.url).hostname}
${site.url}
${site.latest_scores && site.latest_scores.overall != null ? `
${site.latest_scores.overall}
Overall
` : ''}
๐
${site.scan_frequency === 'manual' ? 'Manual' : site.scan_frequency}
${site.uptime_monitoring ? `๐ก Uptime ${site.uptime_pct != null ? site.uptime_pct + '%' : 'pending'}` : ''}
Last scan: ${timeAgo(site.last_scan_at)}
${site.scan_count || 0} scans
`).join('');
}
async function deleteSite(id) {
if (!confirm('Remove this site from monitoring?')) return;
await apiFetch(API + '/v2/sites/' + id, { method: 'DELETE' });
showToast('Site removed', 'success');
loadSites();
}
// โโ Site Detail โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function openSiteDetail(siteId) {
currentSiteId = siteId;
showView('site-detail');
const container = document.getElementById('siteDetailContent');
container.innerHTML = '';
const [siteData, historyData, uptimeData] = await Promise.all([
apiFetch(API + '/v2/sites/' + siteId),
apiFetch(API + '/v2/sites/' + siteId + '/history?limit=30'),
currentPlan.uptime ? apiFetch(API + '/v2/sites/' + siteId + '/uptime?hours=24') : Promise.resolve(null),
]);
let site;
if (!siteData || !siteData.success) {
// Fall back to sample data
site = SAMPLE_SITES.find(s => s.id === siteId) || SAMPLE_SITES[0];
} else {
site = siteData.site;
}
const scans = historyData && historyData.success ? historyData.scans : [];
// Build trend chart data
const trendScans = scans.slice(0, 14).reverse();
container.innerHTML = `
${site.name || new URL(site.url).hostname}
${site.url}
${scans.length ? `
Score Trend (last ${trendScans.length} scans)
${renderTrendChart(trendScans)}
` : ''}
${currentPlan.uptime && uptimeData && uptimeData.success ? `
Uptime (24h) โ ${uptimeData.uptime_pct != null ? uptimeData.uptime_pct + '%' : 'No data'} ยท Avg ${uptimeData.avg_response_ms ? uptimeData.avg_response_ms + 'ms' : 'โ'}
${renderUptimeTimeline(uptimeData.logs || [])}
` : ''}
Scan History
${scans.length ? `
| Date | Overall | Perf | SEO | Security | A11y | DNS |
${scans.slice(0,10).map(s => `
| ${new Date(s.created_at).toLocaleDateString()} |
${scorePill(s.overall_score)} |
${s.performance_score||'โ'} |
${s.seo_score||'โ'} |
${s.security_score||'โ'} |
${s.accessibility_score||'โ'} |
${s.dns_score||'โ'} |
`).join('')}
` : '
No completed scans yet.
'}
${currentPlan.competitors ? `
` : ''}
Auto-Fix Recommendations
${scans.length ? `
` : '
Run a scan first to get recommendations.
'}
`;
if (currentPlan.competitors) {
loadCompetitorsForSite(site.id);
}
}
function renderTrendChart(scans) {
if (!scans.length) return 'Not enough data
';
const maxScore = 100;
const w = 100; // percentage
const h = 140;
const padding = { left: 30, right: 10, top: 10, bottom: 20 };
const chartW = 800;
const chartH = h - padding.top - padding.bottom;
const categories = [
{ key: 'overall_score', color: 'var(--teal)', label: 'Overall' },
{ key: 'performance_score', color: 'var(--green)', label: 'Perf' },
{ key: 'seo_score', color: 'var(--gold)', label: 'SEO' },
{ key: 'security_score', color: 'var(--coral)', label: 'Security' },
];
const n = scans.length;
const xStep = n > 1 ? chartW / (n - 1) : chartW;
const lines = categories.map(cat => {
const pts = scans.map((s, i) => {
const x = i * xStep;
const y = chartH - ((s[cat.key] || 0) / maxScore) * chartH + padding.top;
return `${x},${y}`;
});
return ``;
});
const legend = categories.map(cat =>
`โ ${cat.label}`
).join('');
return `
${legend}
`;
}
function renderUptimeTimeline(logs) {
if (!logs.length) return 'No uptime data yet.
';
const recent = logs.slice(0, 96);
return `
${recent.map(l => `
`).join('')}
โ Up
โ Down
โ Timeout
`;
}
// โโ Competitors โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function loadCompetitorsForSite(siteId) {
const data = await apiFetch(API + '/v2/sites/' + siteId + '/competitors');
if (!data || !data.success) return;
const container = document.getElementById('competitorsList-' + siteId);
if (!container) return;
if (!data.competitors.length) {
container.innerHTML = 'No competitors added. Add competitor URLs to compare scores head-to-head.
';
return;
}
container.innerHTML = `| Competitor | Overall | Perf | SEO | Security | |
${data.competitors.map(c => `
|
${c.name || new URL(c.url).hostname}
${c.url}
|
${c.latest_scores && c.latest_scores.overall != null ? scorePill(c.latest_scores.overall) : 'No scan'} |
${c.latest_scores ? (c.latest_scores.performance || 'โ') : 'โ'} |
${c.latest_scores ? (c.latest_scores.seo || 'โ') : 'โ'} |
${c.latest_scores ? (c.latest_scores.security || 'โ') : 'โ'} |
Scan
|
`).join('')}
`;
}
async function openAddCompetitor(siteId) {
const url = prompt('Competitor URL:');
if (!url) return;
const name = prompt('Display name (optional):') || '';
const data = await apiFetch(API + '/v2/sites/' + siteId + '/competitors', {
method: 'POST',
body: JSON.stringify({ url, name })
});
if (data && data.success) { showToast('Competitor added!', 'success'); loadCompetitorsForSite(siteId); }
}
async function deleteCompetitor(id) {
await apiFetch(API + '/v2/competitors/' + id, { method: 'DELETE' });
showToast('Competitor removed', 'success');
if (currentSiteId) loadCompetitorsForSite(currentSiteId);
}
// โโ Competitive View โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function loadCompetitive() {
const container = document.getElementById('competitiveContent');
const sites = allSites.length ? allSites : SAMPLE_SITES;
if (!sites.length) {
container.innerHTML = 'โ๏ธ
No sites yet
Add a site first, then manage competitors from the site detail view.
';
return;
}
// Sample competitor data for fallback
const SAMPLE_COMPETITORS = {
1: [
{ id: 101, url: 'https://competitor-a.com', name: 'Competitor A', latest_scores: { overall: 76, performance: 68, seo: 84, security: 72, accessibility: 78 } },
{ id: 102, url: 'https://competitor-b.com', name: 'Competitor B', latest_scores: { overall: 81, performance: 85, seo: 79, security: 83, accessibility: 80 } },
],
2: [],
3: [
{ id: 103, url: 'https://rival-blog.com', name: 'Rival Blog', latest_scores: { overall: 88, performance: 90, seo: 93, security: 85, accessibility: 87 } },
],
};
container.innerHTML = sites.map(site => `
${site.name || new URL(site.url).hostname}
`).join('');
for (const site of sites) {
const el = document.getElementById('comp-view-' + site.id);
if (!el) continue;
let competitors = [];
try {
const data = await apiFetch(API + '/v2/sites/' + site.id + '/competitors');
if (data && data.success && data.competitors && data.competitors.length) {
competitors = data.competitors;
} else {
competitors = SAMPLE_COMPETITORS[site.id] || [];
}
} catch (e) {
competitors = SAMPLE_COMPETITORS[site.id] || [];
}
if (!competitors.length) {
el.innerHTML = `No competitors added yet.
`;
continue;
}
const rows = [{ url: site.url, name: site.name || 'Your Site', scores: site.latest_scores || {}, isYours: true }, ...competitors.map(c => ({ url: c.url, name: c.name || new URL(c.url).hostname, scores: c.latest_scores || {}, isYours: false }))];
el.innerHTML = `| Site | Overall | Performance | SEO | Security | A11y |
${rows.map(r => `
${r.name}${r.isYours?' ๐':''} ${r.url} |
${scorePill(r.scores.overall)} |
${scorePill(r.scores.performance)} |
${scorePill(r.scores.seo)} |
${scorePill(r.scores.security)} |
${scorePill(r.scores.accessibility)} |
`).join('')}
`;
}
}
// โโ Auto-Fix โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
async function loadAutofix() {
const container = document.getElementById('autofixContent');
const sites = allSites.length ? allSites : SAMPLE_SITES;
if (!sites.length) {
container.innerHTML = '๐ง
No sites yet
Add a site and run a scan to get auto-fix recommendations.
';
return;
}
container.innerHTML = `Select a site to view auto-fix recommendations for its latest scan.
${sites.map(s => ``).join('')}
ยฉ 2026 FluxCybers ExecFlow. All rights reserved. |
Terms |
Privacy