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:
1 parent
5cfea703fb
commit
b83a19e9cf
20 files changed
+580
-56
No files matched your search
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user