Add 10 UX/feature improvements across dashboard, status page, and API

- Incident acknowledge: Ack button in dashboard rail and ResourceDetail incidents table
- Status page 90-day uptime history bars (teal ≥99%, amber ≥95%, red <95%, grey = no data)
- Theme setting applied: data-theme attribute wired to saved setting with CSS variables + light mode overrides
- Sparkline replaced with colored status bars (teal=up, red=down, grey=unknown) per check tick
- ResourceDetail chart time-range selector: 1h / 6h / 24h / 7d toggle fetches from checks endpoint
- Bulk actions: checkbox per row, select-all header, bulk delete and maintenance toggle bar
- CSV export adds 6 new fields (http_method, http_body, heartbeat_timeout, is_public, consecutive_failures_threshold, response_time_threshold)
- CSV import handles those 6 new fields on INSERT
- Check detail expansion: click expandable rows in Recent Checks to show full details JSON
- Browser push notifications: request permission opt-in, new Notification() on WS message when tab unfocused

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-31 11:01:24 -04:00
1 parent 5cfea703fb
commit b83a19e9cf
20 files changed
+580 -56

No files matched your search

+21 -1
View File
@@ -10,6 +10,9 @@ function NotificationCenter() {
const [filter, setFilter] = useState('all'); // all, unread, read
const [loading, setLoading] = useState(false);
const [wsConnected, setWsConnected] = useState(false);
const [pushPermission, setPushPermission] = useState(
'Notification' in window ? Notification.permission : 'unsupported'
);
useEffect(() => {
loadNotifications();
@@ -41,8 +44,11 @@ function NotificationCenter() {
try {
const message = JSON.parse(event.data);
if (message.type === 'notification') {
// New notification received - reload notifications
loadNotifications();
if ('Notification' in window && Notification.permission === 'granted' && !document.hasFocus()) {
const n = message.data || {};
new Notification(n.title || 'SkyWatch Alert', { body: n.message || '', icon: '/favicon.ico' });
}
}
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
@@ -103,6 +109,12 @@ function NotificationCenter() {
}
};
const handleRequestPush = async () => {
if (!('Notification' in window)) return;
const perm = await Notification.requestPermission();
setPushPermission(perm);
};
const handleClearAll = async () => {
if (!window.confirm(`Clear all ${filter} notifications?`)) return;
@@ -173,6 +185,14 @@ function NotificationCenter() {
)}
</div>
{pushPermission !== 'granted' && pushPermission !== 'unsupported' && (
<div className="push-prompt">
<button className="filter-btn" onClick={handleRequestPush}>
{pushPermission === 'denied' ? '🔕 Push blocked' : '🔔 Enable push alerts'}
</button>
</div>
)}
<div className="notification-list">
{loading ? (
<p style={{ textAlign: 'center', color: '#999', padding: '1rem' }}>Loading...</p>