Building a real-time dashboard with WSX
<!DOCTYPE html>
<html>
<head>
<title>WSX Real-Time Dashboard</title>
<script src="/wsx.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.dashboard {
max-width: 1200px;
margin: 0 auto;
}
.dashboard-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 30px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-align: center;
}
.stat-value {
font-size: 2.5em;
font-weight: bold;
color: #2563eb;
margin: 10px 0;
}
.stat-label {
color: #6b7280;
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-change {
font-size: 0.8em;
margin-top: 5px;
}
.stat-change.positive {
color: #10b981;
}
.stat-change.negative {
color: #ef4444;
}
.chart-container {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.chart-container h3 {
margin-top: 0;
color: #374151;
}
.recent-activity {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.activity-item {
display: flex;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #f3f4f6;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-time {
color: #6b7280;
font-size: 0.8em;
margin-left: auto;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 10px;
}
.status-online {
background-color: #10b981;
}
.status-offline {
background-color: #ef4444;
}
.refresh-btn {
background: #2563eb;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
.refresh-btn:hover {
background: #1d4ed8;
}
</style>
</head>
<body>
<div wx-config='{"url": "ws://localhost:3000/ws"}'>
<div class="dashboard">
<div class="dashboard-header">
<h1>Real-Time Dashboard</h1>
<div>
<button
class="refresh-btn"
wx-send="refresh-dashboard"
wx-target="#dashboard-content"
>
Refresh Data
</button>
</div>
</div>
<div id="dashboard-content">
<div id="stats-grid" class="stats-grid">
<!-- Stats will be populated by WSX -->
</div>
<div class="chart-container">
<h3>Real-Time Metrics</h3>
<canvas id="metricsChart" width="400" height="200"></canvas>
</div>
<div class="recent-activity">
<h3>Recent Activity</h3>
<div id="activity-feed">
<!-- Activity will be populated by WSX -->
</div>
</div>
</div>
</div>
</div>
<script>
// Initialize chart
const ctx = document.getElementById("metricsChart").getContext("2d");
const metricsChart = new Chart(ctx, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "Active Users",
data: [],
borderColor: "#2563eb",
backgroundColor: "rgba(37, 99, 235, 0.1)",
tension: 0.4,
},
{
label: "Requests/min",
data: [],
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
tension: 0.4,
},
],
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
},
},
},
});
// Auto-load dashboard on page load
window.addEventListener("load", () => {
// Trigger initial load
window.wsx.send("load-dashboard", "#dashboard-content");
});
</script>
</body>
</html>
import { createExpressWSXServer } from "@wsx-sh/express";
import express from "express";
const wsx = createExpressWSXServer();
const app = wsx.getApp();
// Mock data storage
let dashboardData = {
stats: {
activeUsers: 1247,
totalRevenue: 45320.5,
conversionRate: 3.2,
serverLoad: 68,
},
metrics: {
labels: [],
activeUsers: [],
requestsPerMinute: [],
},
activities: [],
};
// Generate initial mock data
function generateMockData() {
const now = new Date();
// Generate last 20 data points
for (let i = 19; i >= 0; i--) {
const time = new Date(now - i * 60000); // Each point is 1 minute apart
dashboardData.metrics.labels.push(time.toLocaleTimeString());
dashboardData.metrics.activeUsers.push(
Math.floor(Math.random() * 500) + 800
);
dashboardData.metrics.requestsPerMinute.push(
Math.floor(Math.random() * 200) + 100
);
}
// Generate recent activities
const activities = [
"User registered",
"Order completed",
"Payment processed",
"User logged in",
"Product viewed",
"Cart abandoned",
"Support ticket created",
"Review submitted",
];
for (let i = 0; i < 10; i++) {
dashboardData.activities.push({
id: i,
message: activities[Math.floor(Math.random() * activities.length)],
time: new Date(now - Math.random() * 3600000).toLocaleTimeString(),
type: Math.random() > 0.5 ? "positive" : "neutral",
});
}
}
generateMockData();
// Load dashboard data
wsx.on("load-dashboard", async (request, connection) => {
const statsHtml = `
<div class="stat-card">
<div class="stat-label">Active Users</div>
<div class="stat-value">${dashboardData.stats.activeUsers.toLocaleString()}</div>
<div class="stat-change positive">+5.2% from yesterday</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Revenue</div>
<div class="stat-value">$${dashboardData.stats.totalRevenue.toLocaleString()}</div>
<div class="stat-change positive">+12.3% from yesterday</div>
</div>
<div class="stat-card">
<div class="stat-label">Conversion Rate</div>
<div class="stat-value">${dashboardData.stats.conversionRate}%</div>
<div class="stat-change negative">-0.5% from yesterday</div>
</div>
<div class="stat-card">
<div class="stat-label">Server Load</div>
<div class="stat-value">${dashboardData.stats.serverLoad}%</div>
<div class="stat-change neutral">No change</div>
</div>
`;
const activitiesHtml = dashboardData.activities
.map(
(activity) => `
<div class="activity-item">
<div class="status-indicator ${
activity.type === "positive" ? "status-online" : "status-offline"
}"></div>
<div>${activity.message}</div>
<div class="activity-time">${activity.time}</div>
</div>
`
)
.join("");
return [
{
id: request.id,
target: "#stats-grid",
html: statsHtml,
},
{
id: request.id,
target: "#activity-feed",
html: activitiesHtml,
},
];
});
// Refresh dashboard
wsx.on("refresh-dashboard", async (request, connection) => {
// Update stats with slight variations
dashboardData.stats.activeUsers += Math.floor(Math.random() * 20) - 10;
dashboardData.stats.totalRevenue += Math.random() * 1000 - 500;
dashboardData.stats.conversionRate += (Math.random() - 0.5) * 0.1;
dashboardData.stats.serverLoad += Math.floor(Math.random() * 10) - 5;
// Ensure realistic bounds
dashboardData.stats.activeUsers = Math.max(
800,
Math.min(2000, dashboardData.stats.activeUsers)
);
dashboardData.stats.totalRevenue = Math.max(
0,
dashboardData.stats.totalRevenue
);
dashboardData.stats.conversionRate = Math.max(
0,
Math.min(10, dashboardData.stats.conversionRate)
);
dashboardData.stats.serverLoad = Math.max(
0,
Math.min(100, dashboardData.stats.serverLoad)
);
// Add new metric data point
const now = new Date();
dashboardData.metrics.labels.push(now.toLocaleTimeString());
dashboardData.metrics.activeUsers.push(dashboardData.stats.activeUsers);
dashboardData.metrics.requestsPerMinute.push(
Math.floor(Math.random() * 200) + 100
);
// Keep only last 20 data points
if (dashboardData.metrics.labels.length > 20) {
dashboardData.metrics.labels.shift();
dashboardData.metrics.activeUsers.shift();
dashboardData.metrics.requestsPerMinute.shift();
}
// Generate new activity
const activities = [
"User registered",
"Order completed",
"Payment processed",
"User logged in",
"Product viewed",
];
dashboardData.activities.unshift({
id: Date.now(),
message: activities[Math.floor(Math.random() * activities.length)],
time: now.toLocaleTimeString(),
type: Math.random() > 0.5 ? "positive" : "neutral",
});
// Keep only last 10 activities
dashboardData.activities = dashboardData.activities.slice(0, 10);
// Return updated data
const statsHtml = `
<div class="stat-card">
<div class="stat-label">Active Users</div>
<div class="stat-value">${dashboardData.stats.activeUsers.toLocaleString()}</div>
<div class="stat-change positive">+5.2% from yesterday</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Revenue</div>
<div class="stat-value">$${dashboardData.stats.totalRevenue.toLocaleString()}</div>
<div class="stat-change positive">+12.3% from yesterday</div>
</div>
<div class="stat-card">
<div class="stat-label">Conversion Rate</div>
<div class="stat-value">${dashboardData.stats.conversionRate.toFixed(
1
)}%</div>
<div class="stat-change negative">-0.5% from yesterday</div>
</div>
<div class="stat-card">
<div class="stat-label">Server Load</div>
<div class="stat-value">${dashboardData.stats.serverLoad}%</div>
<div class="stat-change neutral">No change</div>
</div>
`;
const activitiesHtml = dashboardData.activities
.map(
(activity) => `
<div class="activity-item">
<div class="status-indicator ${
activity.type === "positive" ? "status-online" : "status-offline"
}"></div>
<div>${activity.message}</div>
<div class="activity-time">${activity.time}</div>
</div>
`
)
.join("");
return [
{
id: request.id,
target: "#stats-grid",
html: statsHtml,
},
{
id: request.id,
target: "#activity-feed",
html: activitiesHtml,
},
];
});
// Auto-update dashboard every 30 seconds
setInterval(() => {
const connections = wsx.getConnections();
connections.forEach((connection) => {
// Update metrics data
const now = new Date();
dashboardData.metrics.labels.push(now.toLocaleTimeString());
dashboardData.metrics.activeUsers.push(
Math.floor(Math.random() * 500) + 800
);
dashboardData.metrics.requestsPerMinute.push(
Math.floor(Math.random() * 200) + 100
);
// Keep only last 20 data points
if (dashboardData.metrics.labels.length > 20) {
dashboardData.metrics.labels.shift();
dashboardData.metrics.activeUsers.shift();
dashboardData.metrics.requestsPerMinute.shift();
}
// Send chart update
const chartUpdate = {
type: "chart-update",
data: {
labels: dashboardData.metrics.labels,
datasets: [
{
label: "Active Users",
data: dashboardData.metrics.activeUsers,
},
{
label: "Requests/min",
data: dashboardData.metrics.requestsPerMinute,
},
],
},
};
connection.send(JSON.stringify(chartUpdate));
});
}, 30000);
// Get specific metric data
wsx.on("get-metric-data", async (request, connection) => {
return {
id: request.id,
target: request.target,
html: JSON.stringify({
labels: dashboardData.metrics.labels,
datasets: [
{
label: "Active Users",
data: dashboardData.metrics.activeUsers,
borderColor: "#2563eb",
backgroundColor: "rgba(37, 99, 235, 0.1)",
},
{
label: "Requests/min",
data: dashboardData.metrics.requestsPerMinute,
borderColor: "#10b981",
backgroundColor: "rgba(16, 185, 129, 0.1)",
},
],
}),
};
});
app.use(express.static("public"));
app.listen(3000, () => {
console.log("Dashboard server running on http://localhost:3000");
});
// User dashboard customization
wsx.on("load-user-dashboard", async (request, connection) => {
const user = connection.sessionData?.user;
if (!user) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Please log in to view your dashboard</div>`,
};
}
// Get user-specific data
const userStats = await getUserStats(user.id);
const userActivities = await getUserActivities(user.id);
const dashboardHtml = `
<div class="user-dashboard">
<h2>Welcome back, ${user.name}!</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">Your Orders</div>
<div class="stat-value">${userStats.orders}</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Spent</div>
<div class="stat-value">$${userStats.totalSpent.toFixed(2)}</div>
</div>
<div class="stat-card">
<div class="stat-label">Loyalty Points</div>
<div class="stat-value">${userStats.loyaltyPoints}</div>
</div>
</div>
<div class="recent-activity">
<h3>Your Recent Activity</h3>
${userActivities
.map(
(activity) => `
<div class="activity-item">
<div>${activity.description}</div>
<div class="activity-time">${activity.timestamp}</div>
</div>
`
)
.join("")}
</div>
</div>
`;
return {
id: request.id,
target: request.target,
html: dashboardHtml,
};
});
// Alert system
const alertThresholds = {
serverLoad: 80,
errorRate: 5,
responseTime: 1000,
};
function checkAlerts() {
const alerts = [];
if (dashboardData.stats.serverLoad > alertThresholds.serverLoad) {
alerts.push({
type: "warning",
message: `Server load is high: ${dashboardData.stats.serverLoad}%`,
timestamp: new Date(),
});
}
return alerts;
}
wsx.on("get-alerts", async (request, connection) => {
const alerts = checkAlerts();
const alertsHtml = alerts
.map(
(alert) => `
<div class="alert alert-${alert.type}">
<strong>${alert.type.toUpperCase()}</strong>
<span>${alert.message}</span>
<small>${alert.timestamp.toLocaleTimeString()}</small>
</div>
`
)
.join("");
return {
id: request.id,
target: request.target,
html: alertsHtml || '<div class="no-alerts">No active alerts</div>',
};
});
// Send alerts to admins
setInterval(() => {
const alerts = checkAlerts();
if (alerts.length > 0) {
const adminConnections = wsx
.getConnections()
.filter((conn) => conn.sessionData?.user?.role === "admin");
adminConnections.forEach((conn) => {
alerts.forEach((alert) => {
const alertHtml = `
<div class="alert alert-${alert.type}">
${alert.message}
</div>
`;
wsx.sendToConnection(
conn.id,
"#alerts-container",
alertHtml,
"afterbegin"
);
});
});
}
}, 60000); // Check every minute
// Interactive widget system
wsx.on("add-widget", async (request, connection) => {
const { widgetType, position } = request.data;
let widgetHtml = "";
switch (widgetType) {
case "sales-chart":
widgetHtml = `
<div class="widget sales-chart-widget" data-widget-id="${Date.now()}">
<div class="widget-header">
<h3>Sales Chart</h3>
<button class="remove-widget" wx-send="remove-widget">×</button>
</div>
<div class="widget-content">
<canvas id="sales-chart-${Date.now()}"></canvas>
</div>
</div>
`;
break;
case "top-products":
widgetHtml = `
<div class="widget top-products-widget" data-widget-id="${Date.now()}">
<div class="widget-header">
<h3>Top Products</h3>
<button class="remove-widget" wx-send="remove-widget">×</button>
</div>
<div class="widget-content">
<div class="product-list">
<!-- Top products will be loaded here -->
</div>
</div>
</div>
`;
break;
case "user-analytics":
widgetHtml = `
<div class="widget user-analytics-widget" data-widget-id="${Date.now()}">
<div class="widget-header">
<h3>User Analytics</h3>
<button class="remove-widget" wx-send="remove-widget">×</button>
</div>
<div class="widget-content">
<div class="analytics-grid">
<div class="metric">
<span class="label">New Users</span>
<span class="value">234</span>
</div>
<div class="metric">
<span class="label">Returning Users</span>
<span class="value">1,013</span>
</div>
</div>
</div>
</div>
`;
break;
}
return {
id: request.id,
target: `#dashboard-grid`,
html: widgetHtml,
swap: "beforeend",
};
});
wsx.on("remove-widget", async (request, connection) => {
const widgetId = request.data.widgetId;
return {
id: request.id,
target: `[data-widget-id="${widgetId}"]`,
html: "",
swap: "outerHTML",
};
});
// System performance monitoring
const systemMetrics = {
cpu: [],
memory: [],
network: [],
disk: [],
};
function collectSystemMetrics() {
// Simulate collecting real system metrics
const now = Date.now();
systemMetrics.cpu.push({
timestamp: now,
value: Math.random() * 100,
});
systemMetrics.memory.push({
timestamp: now,
value: Math.random() * 100,
});
systemMetrics.network.push({
timestamp: now,
value: Math.random() * 1000,
});
systemMetrics.disk.push({
timestamp: now,
value: Math.random() * 100,
});
// Keep only last 100 data points
Object.keys(systemMetrics).forEach((key) => {
if (systemMetrics[key].length > 100) {
systemMetrics[key].shift();
}
});
}
// Collect metrics every 5 seconds
setInterval(collectSystemMetrics, 5000);
wsx.on("get-system-metrics", async (request, connection) => {
const user = connection.sessionData?.user;
if (!user?.isAdmin) {
return {
id: request.id,
target: request.target,
html: `<div class="error">Admin access required</div>`,
};
}
const latest = {
cpu: systemMetrics.cpu[systemMetrics.cpu.length - 1]?.value || 0,
memory: systemMetrics.memory[systemMetrics.memory.length - 1]?.value || 0,
network:
systemMetrics.network[systemMetrics.network.length - 1]?.value || 0,
disk: systemMetrics.disk[systemMetrics.disk.length - 1]?.value || 0,
};
const metricsHtml = `
<div class="system-metrics">
<div class="metric-card">
<div class="metric-label">CPU Usage</div>
<div class="metric-value">${latest.cpu.toFixed(1)}%</div>
<div class="metric-bar">
<div class="metric-fill" style="width: ${latest.cpu}%"></div>
</div>
</div>
<div class="metric-card">
<div class="metric-label">Memory Usage</div>
<div class="metric-value">${latest.memory.toFixed(1)}%</div>
<div class="metric-bar">
<div class="metric-fill" style="width: ${latest.memory}%"></div>
</div>
</div>
<div class="metric-card">
<div class="metric-label">Network I/O</div>
<div class="metric-value">${latest.network.toFixed(0)} KB/s</div>
<div class="metric-bar">
<div class="metric-fill" style="width: ${Math.min(
latest.network / 10,
100
)}%"></div>
</div>
</div>
<div class="metric-card">
<div class="metric-label">Disk Usage</div>
<div class="metric-value">${latest.disk.toFixed(1)}%</div>
<div class="metric-bar">
<div class="metric-fill" style="width: ${latest.disk}%"></div>
</div>
</div>
</div>
`;
return {
id: request.id,
target: request.target,
html: metricsHtml,
};
});