researcher28's picture
You are a senior frontend engineer.
d0330d4 verified
// Main dashboard application
class Dashboard {
constructor() {
this.widgets = [];
this.intervals = {};
this.modal = new bootstrap.Modal(document.getElementById('widgetModal'));
this.init();
}
async init() {
// Initialize datetime display
this.updateDateTime();
setInterval(() => this.updateDateTime(), 1000);
// Load widgets configuration
await this.loadWidgets();
// Render all widgets
this.renderWidgets();
// Update last updated time
document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
}
async loadWidgets() {
try {
const response = await fetch('widgets.json');
this.widgets = await response.json();
console.log('Widgets loaded successfully:', this.widgets);
} catch (error) {
console.error('Error loading widgets:', error);
// Fallback to default widgets if loading fails
this.widgets = [
{
id: 'default-metric',
type: 'metric',
title: 'Default Metric',
size: 'small',
color: 'blue',
refreshInterval: 5000,
format: 'number',
historyLabel: '24h',
currentValue: 1000,
history: Array(7).fill(0).map((_, i) => 900 + i * 20)
},
{
id: 'default-uptime',
type: 'uptime',
title: 'Default Uptime',
size: 'medium',
color: 'green',
refreshInterval: 10000,
format: 'status',
historyLabel: '7d',
currentValue: 'online',
latency: 100,
uptime: 0.999,
history: Array(7).fill(1)
},
{
id: 'default-news',
type: 'news',
title: 'Default News',
size: 'large',
color: 'yellow',
refreshInterval: 30000,
format: 'list',
historyLabel: '',
items: ['News item 1', 'News item 2', 'News item 3']
}
];
}
}
renderWidgets() {
const container = document.getElementById('widgets-container');
container.innerHTML = '';
this.widgets.forEach(widget => {
const widgetElement = this.createWidgetElement(widget);
container.appendChild(widgetElement);
// Set up refresh interval for each widget
this.setupWidgetRefresh(widget);
});
}
createWidgetElement(widget) {
// Create column based on widget size
const col = document.createElement('div');
let colClass = 'col-md-4 col-lg-4';
if (widget.size === 'medium') {
colClass = 'col-md-6 col-lg-6';
} else if (widget.size === 'large') {
colClass = 'col-md-12 col-lg-12';
}
col.className = colClass;
// Create widget card
const card = document.createElement('div');
card.className = `widget-card ${widget.size} widget-bg-${widget.color}`;
card.dataset.widgetId = widget.id;
card.style.cursor = 'pointer';
// Add click event to open modal
card.addEventListener('click', () => this.openWidgetModal(widget));
// Widget header
const header = document.createElement('div');
header.className = 'widget-header';
const title = document.createElement('h3');
title.className = 'widget-title';
title.innerHTML = `
<span class="badge-color" style="background-color: var(--bs-${widget.color})"></span>
${widget.title}
`;
const refreshBadge = document.createElement('span');
refreshBadge.className = 'badge bg-secondary';
refreshBadge.textContent = `Every ${widget.refreshInterval / 1000}s`;
header.appendChild(title);
header.appendChild(refreshBadge);
// Widget body
const body = document.createElement('div');
body.className = 'widget-body';
if (widget.type === 'metric') {
this.renderMetricWidget(body, widget);
} else if (widget.type === 'uptime') {
this.renderUptimeWidget(body, widget);
} else if (widget.type === 'news') {
this.renderNewsWidget(body, widget);
}
// Add header and body to card
card.appendChild(header);
card.appendChild(body);
// Add card to column
col.appendChild(card);
return col;
}
renderMetricWidget(container, widget) {
// Format value based on type
let formattedValue;
if (widget.format === 'currency') {
formattedValue = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(widget.currentValue);
} else if (widget.format === 'percent') {
formattedValue = new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(widget.currentValue);
} else {
formattedValue = new Intl.NumberFormat('en-US').format(widget.currentValue);
}
// Calculate change percentage
const change = ((widget.history[widget.history.length - 1] - widget.history[0]) / widget.history[0]) * 100;
const changeFormatted = new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
signDisplay: 'always'
}).format(change / 100);
// Create value display
const valueDisplay = document.createElement('div');
valueDisplay.className = 'value-display';
valueDisplay.textContent = formattedValue;
// Create change indicator
const changeIndicator = document.createElement('div');
changeIndicator.className = 'value-change';
changeIndicator.innerHTML = `
${changeFormatted} <span class="ms-1">${widget.historyLabel}</span>
`;
changeIndicator.style.color = change >= 0 ? '#28a745' : '#dc3545';
// Create chart container
const chartContainer = document.createElement('div');
chartContainer.className = 'chart-container';
const canvas = document.createElement('canvas');
chartContainer.appendChild(canvas);
// Add elements to container
container.appendChild(valueDisplay);
container.appendChild(changeIndicator);
container.appendChild(chartContainer);
// Render chart
this.renderSparkline(canvas, widget);
}
renderUptimeWidget(container, widget) {
// Status badge
const statusBadge = document.createElement('span');
statusBadge.className = `status-badge status-${widget.currentValue}`;
statusBadge.textContent = widget.currentValue.toUpperCase();
// Stats row
const statsRow = document.createElement('div');
statsRow.className = 'uptime-stats';
// Uptime stat
const uptimeStat = document.createElement('div');
uptimeStat.className = 'uptime-stat';
uptimeStat.innerHTML = `
<div class="uptime-value">${(widget.uptime * 100).toFixed(2)}%</div>
<div class="uptime-label">UPTIME</div>
`;
// Latency stat
const latencyStat = document.createElement('div');
latencyStat.className = 'uptime-stat';
latencyStat.innerHTML = `
<div class="uptime-value">${widget.latency}ms</div>
<div class="uptime-label">LATENCY</div>
`;
// History stat
const historyStat = document.createElement('div');
historyStat.className = 'uptime-stat';
historyStat.innerHTML = `
<div class="uptime-value">${widget.historyLabel}</div>
<div class="uptime-label">HISTORY</div>
`;
// Add stats to row
statsRow.appendChild(uptimeStat);
statsRow.appendChild(latencyStat);
statsRow.appendChild(historyStat);
// Chart container
const chartContainer = document.createElement('div');
chartContainer.className = 'chart-container mt-3';
const canvas = document.createElement('canvas');
chartContainer.appendChild(canvas);
// Add elements to container
container.appendChild(statusBadge);
container.appendChild(statsRow);
container.appendChild(chartContainer);
// Render chart
this.renderUptimeChart(canvas, widget);
}
renderNewsWidget(container, widget) {
// News items list
const list = document.createElement('div');
widget.items.forEach(item => {
const newsItem = document.createElement('div');
newsItem.className = 'news-item';
newsItem.textContent = item;
list.appendChild(newsItem);
});
// Refresh info
const refreshInfo = document.createElement('div');
refreshInfo.className = 'text-muted mt-2 text-end';
refreshInfo.style.fontSize = '0.8rem';
refreshInfo.textContent = `Updated ${new Date().toLocaleTimeString()}`;
container.appendChild(list);
container.appendChild(refreshInfo);
}
renderSparkline(canvas, widget) {
const ctx = canvas.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: widget.history.map((_, i) => ''),
datasets: [{
data: widget.history,
borderColor: `var(--bs-${widget.color})`,
borderWidth: 2,
fill: false,
tension: 0.4,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: { enabled: false }
},
scales: {
x: { display: false },
y: { display: false }
}
}
});
}
renderUptimeChart(canvas, widget) {
const ctx = canvas.getContext('2d');
const labels = widget.history.map((_, i) => '');
const data = widget.history.map(val => val === 1 ? 100 : 0);
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
data: data,
borderColor: `var(--bs-${widget.color})`,
backgroundColor: `rgba(var(--bs-${widget.color}-rgb), 0.1)`,
borderWidth: 2,
fill: true,
tension: 0.1,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: { enabled: false }
},
scales: {
x: { display: false },
y: {
display: false,
min: 0,
max: 100
}
}
}
});
}
setupWidgetRefresh(widget) {
// Clear existing interval if any
if (this.intervals[widget.id]) {
clearInterval(this.intervals[widget.id]);
}
// Set up new interval
this.intervals[widget.id] = setInterval(() => {
this.updateWidgetData(widget);
}, widget.refreshInterval);
}
updateWidgetData(widget) {
// Generate new mock data
const updatedWidget = this.generateNewData(widget);
// Find the widget element
const widgetElement = document.querySelector(`[data-widget-id="${widget.id}"]`);
if (!widgetElement) return;
// Update the widget content
const body = widgetElement.querySelector('.widget-body');
body.innerHTML = '';
if (widget.type === 'metric') {
this.renderMetricWidget(body, updatedWidget);
} else if (widget.type === 'uptime') {
this.renderUptimeWidget(body, updatedWidget);
} else if (widget.type === 'news') {
this.renderNewsWidget(body, updatedWidget);
}
// Update widget in our array
const index = this.widgets.findIndex(w => w.id === widget.id);
if (index !== -1) {
this.widgets[index] = updatedWidget;
}
// Update last update time
document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
}
generateNewData(widget) {
const now = new Date();
const clone = JSON.parse(JSON.stringify(widget));
if (clone.type === 'metric') {
// Generate new current value within 5% of previous value (up or down)
const changeFactor = 1 + (Math.random() * 0.1 - 0.05);
clone.currentValue = clone.currentValue * changeFactor;
// Update history - remove first element and add new one
clone.history.shift();
clone.history.push(clone.currentValue);
} else if (clone.type === 'uptime') {
// Randomly change status (1% chance)
if (Math.random() < 0.01) {
clone.currentValue = clone.currentValue === 'online' ? 'offline' : 'online';
}
// Random latency variation
clone.latency = Math.max(10, clone.latency + (Math.random() * 40 - 20));
// Update history
clone.history.shift();
clone.history.push(clone.currentValue === 'online' ? 1 : 0);
// Update uptime percentage
const uptimeDays = clone.history.filter(val => val === 1).length;
clone.uptime = uptimeDays / clone.history.length;
} else if (clone.type === 'news') {
// Rotate news items
clone.items.shift();
// Add new item
const newsTemplates = [
'New regulation affects ${sector} market',
'${company} announces partnership with ${partner}',
'${coin} price surges ${percent}% in ${time}',
'Industry experts predict ${trend} for ${sector}',
'${company} reports ${percent}% ${change} in revenue',
'Market analysts see ${trend} in ${sector} sector'
];
const sectors = ['crypto', 'finance', 'tech', 'AI', 'blockchain', 'DeFi'];
const companies = ['Binance', 'Coinbase', 'FTX', 'Kraken', 'MicroStrategy', 'Tesla'];
const coins = ['BTC', 'ETH', 'SOL', 'ADA', 'DOT', 'AVAX'];
const template = newsTemplates[Math.floor(Math.random() * newsTemplates.length)];
const newsItem = template
.replace('${sector}', sectors[Math.floor(Math.random() * sectors.length)])
.replace('${company}', companies[Math.floor(Math.random() * companies.length)])
.replace('${partner}', companies[Math.floor(Math.random() * companies.length)])
.replace('${coin}', coins[Math.floor(Math.random() * coins.length)])
.replace('${percent}', (Math.random() * 20).toFixed(2))
.replace('${time}', ['24h', '7d', '30d'][Math.floor(Math.random() * 3)])
.replace('${trend}', ['growth', 'decline', 'volatility', 'stability'][Math.floor(Math.random() * 4)])
.replace('${change}', ['increase', 'decrease'][Math.floor(Math.random() * 2)]);
clone.items.push(newsItem);
}
return clone;
}
openWidgetModal(widget) {
// Set modal title
document.getElementById('modalTitle').textContent = widget.title;
// Create modal content based on widget type
const modalBody = document.getElementById('modalBody');
modalBody.innerHTML = '';
if (widget.type === 'metric') {
this.renderMetricModal(modalBody, widget);
} else if (widget.type === 'uptime') {
this.renderUptimeModal(modalBody, widget);
} else if (widget.type === 'news') {
this.renderNewsModal(modalBody, widget);
}
// Show modal
this.modal.show();
}
renderMetricModal(container, widget) {
// Format value
let formattedValue;
if (widget.format === 'currency') {
formattedValue = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(widget.currentValue);
} else if (widget.format === 'percent') {
formattedValue = new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(widget.currentValue);
} else {
formattedValue = new Intl.NumberFormat('en-US').format(widget.currentValue);
}
// Create expanded view
const expandedView = document.createElement('div');
// Current value
const currentValue = document.createElement('div');
currentValue.className = 'text-center mb-4';
currentValue.innerHTML = `
<h2 class="display-4 fw-bold mb-2">${formattedValue}</h2>
<span class="badge bg-${widget.color}">Current Value</span>
`;
// History chart
const chartContainer = document.createElement('div');
chartContainer.className = 'chart-container mt-4';
chartContainer.style.height = '300px';
const canvas = document.createElement('canvas');
chartContainer.appendChild(canvas);
// Add to container
expandedView.appendChild(currentValue);
expandedView.appendChild(chartContainer);
container.appendChild(expandedView);
// Render detailed chart
this.renderDetailedMetricChart(canvas, widget);
}
renderUptimeModal(container, widget) {
// Create expanded view
const expandedView = document.createElement('div');
// Status
const status = document.createElement('div');
status.className = 'text-center mb-4';
status.innerHTML = `
<h2 class="display-4 fw-bold mb-2 text-${widget.currentValue === 'online' ? 'success' : 'danger'}">
${widget.currentValue.toUpperCase()}
</h2>
<span class="badge bg-${widget.color}">Current Status</span>
`;
// Stats row
const statsRow = document.createElement('div');
statsRow.className = 'd-flex justify-content-around mb-4';
// Uptime stat
const uptimeStat = document.createElement('div');
uptimeStat.className = 'text-center';
uptimeStat.innerHTML = `
<div class="fs-1 fw-bold">${(widget.uptime * 100).toFixed(2)}%</div>
<div class="text-muted">Uptime</div>
`;
// Latency stat
const latencyStat = document.createElement('div');
latencyStat.className = 'text-center';
latencyStat.innerHTML = `
<div class="fs-1 fw-bold">${widget.latency}ms</div>
<div class="text-muted">Latency</div>
`;
// History period
const historyStat = document.createElement('div');
historyStat.className = 'text-center';
historyStat.innerHTML = `
<div class="fs-1 fw-bold">${widget.historyLabel}</div>
<div class="text-muted">History</div>
`;
// Add stats to row
statsRow.appendChild(uptimeStat);
statsRow.appendChild(latencyStat);
statsRow.appendChild(historyStat);
// Detailed chart
const chartContainer = document.createElement('div');
chartContainer.className = 'chart-container mt-4';
chartContainer.style.height = '300px';
const canvas = document.createElement('canvas');
chartContainer.appendChild(canvas);
// Add to container
expandedView.appendChild(status);
expandedView.appendChild(statsRow);
expandedView.appendChild(chartContainer);
container.appendChild(expandedView);
// Render detailed chart
this.renderDetailedUptimeChart(canvas, widget);
}
renderNewsModal(container, widget) {
// Create expanded view
const expandedView = document.createElement('div');
// Title
const title = document.createElement('h3');
title.className = 'text-center mb-4';
title.textContent = `${widget.title} - Detailed View`;
// News items
const newsList = document.createElement('div');
newsList.className = 'list-group';
widget.items.forEach((item, index) => {
const newsItem = document.createElement('div');
newsItem.className = 'list-group-item list-group-item-action bg-secondary border-dark';
newsItem.innerHTML = `
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">News Update #${index + 1}</h5>
<small>${new Date().toLocaleTimeString()}</small>
</div>
<p class="mb-1">${item}</p>
`;
newsList.appendChild(newsItem);
});
// Add to container
expandedView.appendChild(title);
expandedView.appendChild(newsList);
container.appendChild(expandedView);
}
renderDetailedMetricChart(canvas, widget) {
const ctx = canvas.getContext('2d');
const labels = widget.history.map((_, i) => `Day ${i + 1}`);
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: widget.title,
data: widget.history,
borderColor: `var(--bs-${widget.color})`,
backgroundColor: `rgba(var(--bs-${widget.color}-rgb), 0.1)`,
borderWidth: 2,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
},
tooltip: {
enabled: true,
mode: 'index',
intersect: false
}
},
scales: {
y: {
beginAtZero: false
}
}
}
});
}
renderDetailedUptimeChart(canvas, widget) {
const ctx = canvas.getContext('2d');
const labels = widget.history.map((_, i) => `Day ${i + 1}`);
const data = widget.history.map(val => val === 1 ? 100 : 0);
new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Uptime %',
data: data,
borderColor: `var(--bs-${widget.color})`,
backgroundColor: `rgba(var(--bs-${widget.color}-rgb), 0.5)`,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
position: 'top',
},
tooltip: {
enabled: true,
callbacks: {
label: function(context) {
return context.parsed.y === 100 ? 'Online' : 'Offline';
}
}
}
},
scales: {
y: {
min: 0,
max: 100,
ticks: {
callback: function(value) {
return value + '%';
}
}
}
}
}
});
}
updateDateTime() {
const now = new Date();
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
};
document.getElementById('datetime').textContent = now.toLocaleDateString('en-US', options);
}
}
// Initialize dashboard when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new Dashboard();
});